Changeset View
Changeset View
Standalone View
Standalone View
src/app/Http/Controllers/API/SignupController.php
<?php | <?php | ||||
namespace App\Http\Controllers\API; | namespace App\Http\Controllers\API; | ||||
use App\Http\Controllers\Controller; | use App\Http\Controllers\Controller; | ||||
use App\Jobs\SignupVerificationEmail; | use App\Jobs\SignupVerificationEmail; | ||||
use App\Jobs\SignupVerificationSMS; | use App\Jobs\SignupVerificationSMS; | ||||
use App\Domain; | use App\Domain; | ||||
use App\Plan; | use App\Plan; | ||||
use App\Rules\ExternalEmail; | |||||
use App\Rules\UserEmailDomain; | |||||
use App\Rules\UserEmailLocal; | |||||
use App\SignupCode; | use App\SignupCode; | ||||
use App\User; | use App\User; | ||||
use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||
use Illuminate\Support\Facades\DB; | use Illuminate\Support\Facades\DB; | ||||
use Illuminate\Support\Facades\Validator; | use Illuminate\Support\Facades\Validator; | ||||
use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||
/** | /** | ||||
▲ Show 20 Lines • Show All 54 Lines • ▼ Show 20 Lines | public function init(Request $request) | ||||
); | ); | ||||
if ($v->fails()) { | if ($v->fails()) { | ||||
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); | return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); | ||||
} | } | ||||
// Validate user email (or phone) | // Validate user email (or phone) | ||||
if ($error = $this->validatePhoneOrEmail($request->email, $is_phone)) { | if ($error = $this->validatePhoneOrEmail($request->email, $is_phone)) { | ||||
return response()->json(['status' => 'error', 'errors' => ['email' => __($error)]], 422); | return response()->json(['status' => 'error', 'errors' => ['email' => $error]], 422); | ||||
} | } | ||||
// Generate the verification code | // Generate the verification code | ||||
$code = SignupCode::create([ | $code = SignupCode::create([ | ||||
'data' => [ | 'data' => [ | ||||
'email' => $request->email, | 'email' => $request->email, | ||||
'name' => $request->name, | 'name' => $request->name, | ||||
'plan' => $request->plan, | 'plan' => $request->plan, | ||||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | public function signup(Request $request) | ||||
// Get the plan | // Get the plan | ||||
$plan = $this->getPlan(); | $plan = $this->getPlan(); | ||||
$is_domain = $plan->hasDomain(); | $is_domain = $plan->hasDomain(); | ||||
$login = $request->login; | $login = $request->login; | ||||
$domain = $request->domain; | $domain = $request->domain; | ||||
// Validate login | // Validate login | ||||
if ($errors = $this->validateLogin($login, $domain, $is_domain)) { | if ($errors = self::validateLogin($login, $domain, $is_domain)) { | ||||
$errors = $this->resolveErrors($errors); | |||||
return response()->json(['status' => 'error', 'errors' => $errors], 422); | return response()->json(['status' => 'error', 'errors' => $errors], 422); | ||||
} | } | ||||
// Get user name/email from the verification code database | // Get user name/email from the verification code database | ||||
$code_data = $v->getData(); | $code_data = $v->getData(); | ||||
$user_name = $code_data->name; | $user_name = $code_data->name; | ||||
$user_email = $code_data->email; | $user_email = $code_data->email; | ||||
Show All 29 Lines | public function signup(Request $request) | ||||
$this->code->delete(); | $this->code->delete(); | ||||
DB::commit(); | DB::commit(); | ||||
return UsersController::logonResponse($user); | return UsersController::logonResponse($user); | ||||
} | } | ||||
/** | /** | ||||
* Checks if the input string is a valid email address or a phone number | * Returns plan for the signup process | ||||
* | |||||
* @param string $input Email address or phone number | |||||
* @param bool $is_phone Will have been set to True if the string is valid phone number | |||||
* | * | ||||
* @return string Error message label on validation error | * @returns \App\Plan Plan object selected for current signup process | ||||
*/ | */ | ||||
protected function validatePhoneOrEmail($input, &$is_phone = false) | protected function getPlan() | ||||
{ | { | ||||
$is_phone = false; | if (!$this->plan) { | ||||
// Get the plan if specified and exists... | |||||
return $this->validateEmail($input); | if ($this->code && $this->code->data['plan']) { | ||||
$plan = Plan::where('title', $this->code->data['plan'])->first(); | |||||
// TODO: Phone number support | |||||
/* | |||||
if (strpos($input, '@')) { | |||||
return $this->validateEmail($input); | |||||
} | } | ||||
$input = str_replace(array('-', ' '), '', $input); | // ...otherwise use the default plan | ||||
if (empty($plan)) { | |||||
// TODO: Get default plan title from config | |||||
$plan = Plan::where('title', 'individual')->first(); | |||||
} | |||||
if (!preg_match('/^\+?[0-9]{9,12}$/', $input)) { | $this->plan = $plan; | ||||
return 'validation.noemailorphone'; | |||||
} | } | ||||
$is_phone = true; | return $this->plan; | ||||
*/ | |||||
} | } | ||||
/** | /** | ||||
* Email address validation | * Checks if the input string is a valid email address or a phone number | ||||
* | * | ||||
* @param string $email Email address | * @param string $input Email address or phone number | ||||
* @param bool $is_phone Will have been set to True if the string is valid phone number | |||||
* | * | ||||
* @return string Error message label on validation error | * @return string Error message on validation error | ||||
*/ | */ | ||||
protected function validateEmail($email) | protected static function validatePhoneOrEmail($input, &$is_phone = false): ?string | ||||
{ | { | ||||
$v = Validator::make(['email' => $email], ['email' => 'required|email']); | $is_phone = false; | ||||
$v = Validator::make( | |||||
['email' => $input], | |||||
['email' => ['required', 'string', new ExternalEmail()]] | |||||
); | |||||
if ($v->fails()) { | if ($v->fails()) { | ||||
return 'validation.emailinvalid'; | return $v->errors()->toArray()['email'][0]; | ||||
} | } | ||||
list($local, $domain) = explode('@', $email); | // TODO: Phone number support | ||||
/* | |||||
$input = str_replace(array('-', ' '), '', $input); | |||||
// don't allow @localhost and other no-fqdn | if (!preg_match('/^\+?[0-9]{9,12}$/', $input)) { | ||||
if (strpos($domain, '.') === false) { | return \trans('validation.noemailorphone'); | ||||
return 'validation.emailinvalid'; | |||||
} | } | ||||
$is_phone = true; | |||||
*/ | |||||
return null; | |||||
} | } | ||||
/** | /** | ||||
* Login (kolab identity) validation | * Login (kolab identity) validation | ||||
* | * | ||||
* @param string $login Login (local part of an email address) | * @param string $login Login (local part of an email address) | ||||
* @param string $domain Domain name | * @param string $domain Domain name | ||||
* @param bool $external Enables additional checks for domain part | * @param bool $external Enables additional checks for domain part | ||||
* | * | ||||
* @return array Error messages on validation error | * @return array Error messages on validation error | ||||
*/ | */ | ||||
protected function validateLogin($login, $domain, $external = false) | protected static function validateLogin($login, $domain, $external = false): ?array | ||||
{ | { | ||||
// don't allow @localhost and other no-fqdn | // Validate login part alone | ||||
if (empty($domain) || strpos($domain, '.') === false || stripos($domain, 'www.') === 0) { | $v = Validator::make( | ||||
return ['domain' => 'validation.domaininvalid']; | ['login' => $login], | ||||
} | ['login' => ['required', 'string', new UserEmailLocal($external)]] | ||||
); | |||||
// Local part validation | if ($v->fails()) { | ||||
if (!preg_match('/^[A-Za-z0-9_.-]+$/', $login)) { | return ['login' => $v->errors()->toArray()['login'][0]]; | ||||
return ['login' => 'validation.logininvalid']; | |||||
} | } | ||||
$domain = Str::lower($domain); | $domains = $external ? null : Domain::getPublicDomains(); | ||||
if (!$external) { | // Validate the domain | ||||
// Check if the local part is not one of exceptions | $v = Validator::make( | ||||
$exceptions = '/^(admin|administrator|sales|root)$/i'; | ['domain' => $domain], | ||||
if (preg_match($exceptions, $login)) { | ['domain' => ['required', 'string', new UserEmailDomain($domains)]] | ||||
return ['login' => 'validation.loginexists']; | ); | ||||
} | |||||
// Check if specified domain is allowed for signup | |||||
if (!in_array($domain, Domain::getPublicDomains())) { | |||||
return ['domain' => 'validation.domaininvalid']; | |||||
} | |||||
} else { | |||||
// Use email validator to validate the domain part | |||||
$v = Validator::make(['email' => 'user@' . $domain], ['email' => 'required|email']); | |||||
if ($v->fails()) { | if ($v->fails()) { | ||||
return ['domain' => 'validation.domaininvalid']; | return ['domain' => $v->errors()->toArray()['domain'][0]]; | ||||
} | } | ||||
// TODO: DNS registration check - maybe after signup? | $domain = Str::lower($domain); | ||||
// Check if domain is already registered with us | // Check if domain is already registered with us | ||||
if ($external) { | |||||
if (Domain::where('namespace', $domain)->first()) { | if (Domain::where('namespace', $domain)->first()) { | ||||
return ['domain' => 'validation.domainexists']; | return ['domain' => \trans('validation.domainexists')]; | ||||
} | } | ||||
} | } | ||||
// Check if user with specified login already exists | // Check if user with specified login already exists | ||||
// TODO: Aliases | |||||
$email = $login . '@' . $domain; | $email = $login . '@' . $domain; | ||||
if (User::findByEmail($email)) { | if (User::findByEmail($email)) { | ||||
return ['login' => 'validation.loginexists']; | return ['login' => \trans('validation.loginexists')]; | ||||
} | |||||
} | |||||
/** | |||||
* Returns plan for the signup process | |||||
* | |||||
* @returns \App\Plan Plan object selected for current signup process | |||||
*/ | |||||
protected function getPlan() | |||||
{ | |||||
if (!$this->plan) { | |||||
// Get the plan if specified and exists... | |||||
if ($this->code && $this->code->data['plan']) { | |||||
$plan = Plan::where('title', $this->code->data['plan'])->first(); | |||||
} | |||||
// ...otherwise use the default plan | |||||
if (empty($plan)) { | |||||
// TODO: Get default plan title from config | |||||
$plan = Plan::where('title', 'individual')->first(); | |||||
} | |||||
$this->plan = $plan; | |||||
} | |||||
return $this->plan; | |||||
} | |||||
/** | |||||
* Convert error labels to actual (localized) text | |||||
*/ | |||||
protected function resolveErrors(array $errors): array | |||||
{ | |||||
$result = []; | |||||
foreach ($errors as $idx => $label) { | |||||
$result[$idx] = __($label); | |||||
} | } | ||||
return $result; | return null; | ||||
} | } | ||||
} | } |