. */ namespace App\Services; use App\Account; use App\AccountCreationToken; use App\Alias; use App\EmailChangeCode; use App\Http\Requests\CreateAccountRequest; use App\Libraries\OvhSMS; use App\Mail\NewsletterRegistration; use App\Mail\RecoverByCode; use App\Mail\RegisterValidation; use App\PhoneChangeCode; use Illuminate\Support\Facades\Log; use App\Rules\AccountCreationToken as RulesAccountCreationToken; use App\Rules\WithoutSpaces; use Carbon\Carbon; use Illuminate\Support\Facades\Mail; use Illuminate\Http\Request; use Illuminate\Validation\Rule; class AccountService { public function __construct(public bool $api = true) { } /** * Account creation */ public function store(CreateAccountRequest $request): Account { $rules = []; $rules['password'] = 'confirmed'; $rules['email'] = 'confirmed'; $rules['privacy'] = 'accepted'; $rules['terms'] = 'accepted'; if ($this->api) { $rules = []; $rules['account_creation_token'] = ['required', new RulesAccountCreationToken]; } $request->validate($rules); if ($this->api) { $token = AccountCreationToken::where('token', $request->get('account_creation_token'))->first(); $token->used = true; $token->save(); } $account = new Account; $account->username = $request->get('username'); $account->activated = false; $account->domain = config('app.sip_domain'); $account->ip_address = $request->ip(); $account->created_at = Carbon::now(); $account->user_agent = config('app.name'); $account->dtmf_protocol = $request->get('dtmf_protocol'); $account->confirmation_key = generatePin(); $account->provision(); $account->save(); $account->updatePassword($request->get('password'), $request->has('algorithm') ? $request->get('algorithm') : 'SHA-256'); Log::channel('events')->info('Account Service: Account created', ['id' => $account->identifier]); if (!$this->api) { if (!empty(config('app.newsletter_registration_address')) && $request->has('newsletter')) { Mail::to(config('app.newsletter_registration_address'))->send(new NewsletterRegistration($account)); } } return Account::withoutGlobalScopes()->find($account->id); } /** * Link a phone number to an account */ public function requestPhoneChange(Request $request) { $request->validate([ 'phone' => [ 'required', 'unique:aliases,alias', 'unique:accounts,username', new WithoutSpaces, 'starts_with:+' ] ]); $account = $request->user(); $phoneChangeCode = $account->phoneChangeCode ?? new PhoneChangeCode(); $phoneChangeCode->account_id = $account->id; $phoneChangeCode->phone = $request->get('phone'); $phoneChangeCode->code = generatePin(); $phoneChangeCode->save(); Log::channel('events')->info('Account Service: Account phone change requested by SMS', ['id' => $account->identifier]); $ovhSMS = new OvhSMS; $ovhSMS->send($request->get('phone'), 'Your ' . config('app.name') . ' validation code is ' . $phoneChangeCode->code); } public function updatePhone(Request $request): ?Account { $request->validate($this->api ? [ 'code' => 'required|digits:4' ] : [ 'number_1' => 'required|digits:1', 'number_2' => 'required|digits:1', 'number_3' => 'required|digits:1', 'number_4' => 'required|digits:1' ]); $code = $this->api ? $request->get('code') : $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4'); $account = $request->user(); $phoneChangeCode = $account->phoneChangeCode()->firstOrFail(); if ($phoneChangeCode->code == $code) { $account->alias()->delete(); $alias = new Alias; $alias->alias = $phoneChangeCode->phone; $alias->domain = config('app.sip_domain'); $alias->account_id = $account->id; $alias->save(); Log::channel('events')->info('Account Service: Account phone changed using SMS', ['id' => $account->identifier]); $phoneChangeCode->delete(); $account->activated = true; $account->save(); $account->refresh(); return $account; } $phoneChangeCode->delete(); if ($this->api) { abort(403); } return null; } /** * Link an email to an account */ public function requestEmailChange(Request $request) { $rules = ['required', 'email', Rule::notIn([$request->user()->email])]; if (config('app.account_email_unique')) { array_push($rules, Rule::unique('accounts', 'email')); } $request->validate([ 'email' => $rules, ]); $account = $request->user(); $emailChangeCode = $account->emailChangeCode ?? new EmailChangeCode; $emailChangeCode->account_id = $account->id; $emailChangeCode->email = $request->get('email'); $emailChangeCode->code = generatePin(); $emailChangeCode->save(); Log::channel('events')->info('Account Service: Account email change requested by email', ['id' => $account->identifier]); Mail::to($emailChangeCode->email)->send(new RegisterValidation($account)); } public function updateEmail(Request $request): ?Account { $request->validate($this->api ? [ 'code' => 'required|digits:4' ] : [ 'number_1' => 'required|digits:1', 'number_2' => 'required|digits:1', 'number_3' => 'required|digits:1', 'number_4' => 'required|digits:1' ]); $code = $this->api ? $request->get('code') : $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4'); $account = $request->user(); $emailChangeCode = $account->emailChangeCode()->firstOrFail(); if ($emailChangeCode->validate($code)) { $account->email = $emailChangeCode->email; $account->save(); Log::channel('events')->info('Account Service: Account email changed using email', ['id' => $account->identifier]); $emailChangeCode->delete(); $account->activated = true; $account->save(); $account->refresh(); return $account; } $emailChangeCode->delete(); if ($this->api) { abort(403); } return null; } /** * Account recovery */ public function recoverByEmail(Account $account): Account { $account = $this->recoverAccount($account); Mail::to($account)->send(new RecoverByCode($account)); Log::channel('events')->info('Account Service: Sending recovery email', ['id' => $account->identifier]); return $account; } public function recoverByPhone(Account $account): Account { $account = $this->recoverAccount($account); $ovhSMS = new OvhSMS; $ovhSMS->send($account->phone, 'Your ' . config('app.name') . ' validation code is ' . $account->recovery_code); Log::channel('events')->info('Account Service: Sending recovery SMS', ['id' => $account->identifier]); return $account; } private function recoverAccount(Account $account): Account { $account->recovery_code = generatePin(); $account->provision(); $account->save(); return $account; } }