diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a59c7e..9692375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ v1.7 ---- +- Fix FLEXIAPI-205 Remove the deprecated endpoints, compatibility code documentation and tests. Drop the confirmation_key accounts column and activation_expirations table - Fix FLEXIAPI-206 Upgrade to Laravel 10, PHP 8.1 minimum and bump all the related dependencies, drop Debian 11 Bullseye - Fix FLEXIAPI-220 Migrate SIP Domains to Spaces - Fix GH-15 Add password import from CSV diff --git a/README.md b/README.md index 4f4bf71..3caaab2 100644 --- a/README.md +++ b/README.md @@ -104,9 +104,6 @@ You can also seed the tables with test accounts for the liblinphone test suite w To send SMS to the USA some providers need to validate their templates before transfering them, see [Sending SMS messages to the USA - OVH](https://help.ovhcloud.com/csm/en-ie-sms-sending-sms-to-usa?id=kb_article_view&sysparm_article=KB0051359). Here are the currently used SMS templates in the app to declare in your provider panel: - -- Creation code: `Your #APP_NAME# creation code is #CODE#`. Sent to confirm the creation of the account by SMS. -- Recovery code: `Your #APP_NAME# recovery code is #CODE#`. Sent to recover the account by SMS. - Validation code: `Your #APP_NAME# validation code is #CODE#`. Sent to validate the phone change by SMS. - Validation code with expiration: `Your #APP_NAME# validation code is #CODE#. The code is available for #CODE_MINUTES# minutes`. Sent to validate the phone change by SMS, include an expiration time. diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 1bde2c6..c454fd7 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -36,9 +36,9 @@ class Account extends Authenticatable use HasFactory; use Compoships; - protected $with = ['passwords', 'activationExpiration', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries']; - protected $hidden = ['expire_time', 'confirmation_key', 'pivot', 'currentProvisioningToken', 'currentRecoveryCode', 'dictionaryEntries']; - protected $appends = ['realm', 'confirmation_key_expires', 'provisioning_token', 'provisioning_token_expire_at', 'dictionary']; + protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries']; + protected $hidden = ['expire_time', 'pivot', 'currentProvisioningToken', 'currentRecoveryCode', 'dictionaryEntries']; + protected $appends = ['realm', 'provisioning_token', 'provisioning_token_expire_at', 'dictionary']; protected $casts = [ 'activated' => 'boolean', ]; @@ -111,11 +111,6 @@ class Account extends Authenticatable }); } - public function activationExpiration() - { - return $this->hasOne(ActivationExpiration::class); - } - public function apiKey() { return $this->hasOne(ApiKey::class)->whereNull('expires_after_last_used_minutes'); @@ -352,10 +347,6 @@ class Account extends Authenticatable /** * Utils */ - public function activationExpired(): bool - { - return ($this->activationExpiration && $this->activationExpiration->isExpired()); - } public function generateUserApiKey(?string $ip = null): ApiKey { diff --git a/flexiapi/app/ActivationExpiration.php b/flexiapi/app/ActivationExpiration.php deleted file mode 100644 index 362ed98..0000000 --- a/flexiapi/app/ActivationExpiration.php +++ /dev/null @@ -1,44 +0,0 @@ -. -*/ - -namespace App; - -use Carbon\Carbon; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; - -class ActivationExpiration extends Model -{ - use HasFactory; - - protected $casts = [ - 'expires' => 'datetime:Y-m-d H:i:s', - ]; - - public function account() - { - return $this->belongsTo(Account::class); - } - - public function isExpired() - { - $now = Carbon::now(); - return $this->expires->lessThan($now); - } -} diff --git a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php index 37e4c10..2b36a54 100644 --- a/flexiapi/app/Http/Controllers/Account/AuthenticateController.php +++ b/flexiapi/app/Http/Controllers/Account/AuthenticateController.php @@ -80,29 +80,6 @@ class AuthenticateController extends Controller return redirect()->back()->withErrors(['authentication' => __('Wrong username or password')]); } - /** - * Deprecated - */ - public function validateEmail(Request $request, string $code) - { - $request->merge(['code' => $code]); - $request->validate(['code' => 'required|size:' . self::$emailCodeSize]); - - $account = Account::where('confirmation_key', $code)->first(); - - if (!$account) { - return redirect()->route('account.login'); - } - - $account->confirmation_key = null; - $account->activated = true; - $account->save(); - - Auth::login($account); - - return redirect()->route('account.home'); - } - public function loginAuthToken(Request $request, ?string $token = null) { $authToken = null; diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index df28c8d..52200b0 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -51,10 +51,6 @@ class ProvisioningController extends Controller }) ->firstOrFail(); - if ($account->activationExpired()) { - abort(404); - } - $params = ['provisioning_token' => $provisioningToken]; if ($request->has('reset_password')) { @@ -130,7 +126,7 @@ class ProvisioningController extends Controller }) ->firstOrFail(); - if ($account->activationExpired() || ($provisioningToken != $account->provisioning_token)) { + if ($provisioningToken != $account->provisioning_token) { return abort(404); } diff --git a/flexiapi/app/Http/Controllers/Api/Account/AccountController.php b/flexiapi/app/Http/Controllers/Api/Account/AccountController.php index cd3597f..a1a9775 100644 --- a/flexiapi/app/Http/Controllers/Api/Account/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Account/AccountController.php @@ -20,29 +20,12 @@ namespace App\Http\Controllers\Api\Account; use Illuminate\Http\Request; -use Illuminate\Validation\Rule; -use Illuminate\Support\Str; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Mail; + use App\Http\Controllers\Controller; -use Carbon\Carbon; +use App\Http\Requests\Account\Create\Api\Request as ApiRequest; use App\Account; -use App\AccountCreationToken; - -use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; -use App\Http\Requests\Account\Create\Api\Request as ApiRequest; -use App\Libraries\OvhSMS; -use App\Mail\RegisterConfirmation; - -use App\Rules\AccountCreationToken as RulesAccountCreationToken; -use App\Rules\AccountCreationTokenNotExpired; -use App\Rules\BlacklistedUsername; -use App\Rules\FilteredPhone; -use App\Rules\NoUppercase; -use App\Rules\SIPUsername; -use App\Rules\PasswordAlgorithm; - use App\Services\AccountService; class AccountController extends Controller @@ -81,256 +64,11 @@ class AccountController extends Controller return abort(404, 'No TURN service configured'); } - /** - * /!\ Dangerous endpoint, disabled by default - */ - public function phoneInfo(Request $request, string $phone) - { - if (!config('app.dangerous_endpoints')) return abort(404); - - $request->merge(['phone' => $phone]); - $request->validate([ - 'phone' => ['required', 'phone', new FilteredPhone] - ]); - - $account = Account::where('domain', config('app.sip_domain')) - ->where(function ($query) use ($phone) { - $query->where('username', $phone) - ->orWhere('phone', $phone); - })->firstOrFail(); - - return \response()->json([ - 'activated' => $account->activated, - 'realm' => $account->realm, - 'phone' => (bool)$account->phone - ]); - } - - /** - * /!\ Dangerous endpoint, disabled by default - * Store directly the account and alias in the DB and send a SMS or email for the validation - */ - public function storePublic(Request $request) - { - if (!config('app.dangerous_endpoints')) return abort(404); - - $request->validate([ - 'username' => [ - 'required_without:phone', - new NoUppercase, - new BlacklistedUsername, - new SIPUsername, - Rule::unique('accounts', 'username')->where(function ($query) use ($request) { - $query->where('domain', $request->has('domain') ? $request->get('domain') : config('app.sip_domain')); - }), - Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) { - $query->where('domain', $request->has('domain') ? $request->get('domain') : config('app.sip_domain')); - }), - 'filled', - ], - 'algorithm' => ['required', new PasswordAlgorithm], - 'password' => 'required|filled', - 'domain' => 'min:3', - 'email' => config('app.account_email_unique') - ? 'required_without:phone|email|unique:accounts,email' - : 'required_without:phone|email', - 'phone' => [ - 'required_without:email', - 'required_without:username', - 'phone', - new FilteredPhone, - 'unique:accounts,phone', - 'unique:accounts,username', - ], - 'account_creation_token' => [ - 'required', - new RulesAccountCreationToken, - new AccountCreationTokenNotExpired - ] - ]); - - $account = new Account; - $account->username = !empty($request->get('username')) - ? $request->get('username') - : $request->get('phone'); - $account->email = $request->get('email'); - $account->activated = false; - $account->domain = $request->has('domain') - ? $request->get('domain') - : config('app.sip_domain'); - $account->ip_address = $request->ip(); - $account->created_at = Carbon::now(); - $account->user_agent = $request->header('User-Agent') ?? space()->name; - $account->save(); - - $account->updatePassword($request->get('password'), $request->get('algorithm')); - - $token = AccountCreationToken::where('token', $request->get('account_creation_token'))->first(); - $token->consume(); - $token->account_id = $account->id; - $token->save(); - - Log::channel('events')->info('API deprecated - Store public: AccountCreationToken redeemed', ['account_creation_token' => $token->toLog()]); - Log::channel('events')->info('API deprecated - Store public: Account created', ['id' => $account->identifier]); - - // Send validation by phone - if ($request->has('phone')) { - $account->phone = $request->get('phone'); - $account->confirmation_key = generatePin(); - $account->save(); - - Log::channel('events')->info('API deprecated: Account created using the public endpoint by phone', ['id' => $account->identifier]); - - $ovhSMS = new OvhSMS; - $ovhSMS->send($request->get('phone'), 'Your ' . space()->name . ' creation code is ' . $account->confirmation_key); - } elseif ($request->has('email')) { - // Send validation by email - $account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize); - $account->save(); - - Log::channel('events')->info('API deprecated - Store public: Account created using the public endpoint by email', ['id' => $account->identifier]); - - try { - Mail::to($account)->send(new RegisterConfirmation($account)); - } catch (\Exception $e) { - Log::channel('events')->info('API deprecated - Store public: Public Register Confirmation email not sent, check errors log', ['id' => $account->identifier]); - Log::error('Public Register Confirmation email not sent: ' . $e->getMessage()); - } - } - - // Full reload - return Account::withoutGlobalScopes()->find($account->id); - } - - /** - * /!\ Dangerous endpoint, disabled by default - */ - public function recoverByPhone(Request $request) - { - if (!config('app.dangerous_endpoints')) return abort(404); - - $request->validate([ - 'phone' => [ - 'required', 'phone', new FilteredPhone, 'exists:accounts,phone' - ], - 'account_creation_token' => [ - 'required', - new RulesAccountCreationToken, - new AccountCreationTokenNotExpired - ] - ]); - - $account = Account::where('phone', $request->get('phone'))->first(); - $account->confirmation_key = generatePin(); - $account->save(); - - $token = AccountCreationToken::where('token', $request->get('account_creation_token'))->first(); - $token->consume(); - $token->account_id = $account->id; - $token->save(); - - Log::channel('events')->info('API deprecated - Account recovery: AccountCreationToken redeemed', ['account_creation_token' => $token->toLog()]); - Log::channel('events')->info('API deprecated - Account recovery: Account recovery by phone', ['id' => $account->identifier]); - - $ovhSMS = new OvhSMS; - $ovhSMS->send($request->get('phone'), 'Your ' . space()->name . ' recovery code is ' . $account->confirmation_key); - } - - /** - * /!\ Dangerous endpoint, disabled by default - */ - public function recoverUsingKey(string $sip, string $recoveryKey) - { - if (!config('app.dangerous_endpoints')) return abort(404); - - list($username, $domain) = explode('@', $sip); - - $account = Account::where('domain', $domain) - ->where(function ($query) use ($username) { - $query->where('username', $username) - ->orWhere('phone', $username); - })->firstOrFail(); - - $confirmationKey = $account->confirmation_key; - $account->confirmation_key = null; - - if ($confirmationKey != $recoveryKey) abort(404); - - if ($account->activationExpired()) abort(403, 'Activation expired'); - - $account->activated = true; - $account->save(); - - $account->passwords->each(function ($i, $k) { - $i->makeVisible(['password']); - }); - - return $account; - } - public function store(ApiRequest $request) { return (new AccountService)->store($request); } - /** - * Deprecated - */ - public function activateEmail(Request $request, string $sip) - { - // For retro-compatibility - if ($request->has('code')) { - $request->merge(['confirmation_key' => $request->get('code')]); - } - - $request->validate([ - 'confirmation_key' => 'required|size:' . WebAuthenticateController::$emailCodeSize - ]); - - $account = Account::sip($sip) - ->where('confirmation_key', $request->get('confirmation_key')) - ->firstOrFail(); - - if ($account->activationExpired()) abort(403, 'Activation expired'); - - $account->activated = true; - $account->confirmation_key = null; - $account->save(); - - Log::channel('events')->info('API: Account activated by email', ['id' => $account->identifier]); - - return $account; - } - - /** - * Deprecated - */ - public function activatePhone(Request $request, string $sip) - { - // For retro-compatibility - if ($request->has('code')) { - $request->merge(['confirmation_key' => $request->get('code')]); - } - - $request->validate([ - 'confirmation_key' => 'required|digits:4' - ]); - - $account = Account::sip($sip) - ->where('confirmation_key', $request->get('confirmation_key')) - ->firstOrFail(); - - if ($account->activationExpired()) abort(403, 'Activation expired'); - - $account->activated = true; - $account->confirmation_key = null; - $account->save(); - - Log::channel('events')->info('API: Account activated by phone', ['id' => $account->identifier]); - - return $account; - } - public function show(Request $request) { return Account::where('id', $request->user()->id) diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php index b7ec760..65afd37 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php @@ -156,7 +156,7 @@ class AccountController extends Controller Log::channel('events')->info('API Admin: Account updated', ['id' => $account->identifier]); - return $account->makeVisible(['confirmation_key', 'provisioning_token']); + return $account->makeVisible(['provisioning_token']); } public function typeAdd(int $accountId, int $typeId) diff --git a/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php b/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php index 333ecb0..899034b 100644 --- a/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php +++ b/flexiapi/app/Http/Requests/Account/Create/Api/AsAdminRequest.php @@ -36,10 +36,6 @@ class AsAdminRequest extends Request $rules['algorithm'] = ['required', new PasswordAlgorithm()]; $rules['admin'] = 'boolean|nullable'; $rules['activated'] = 'boolean|nullable'; - $rules['confirmation_key_expires'] = [ - 'date_format:Y-m-d H:i:s', - 'nullable', - ]; if (config('app.allow_phone_number_username_admin_api') == true) { array_splice( diff --git a/flexiapi/app/Mail/RegisterConfirmation.php b/flexiapi/app/Mail/RegisterConfirmation.php deleted file mode 100644 index d89cc21..0000000 --- a/flexiapi/app/Mail/RegisterConfirmation.php +++ /dev/null @@ -1,47 +0,0 @@ -. -*/ - -namespace App\Mail; - -use Illuminate\Bus\Queueable; -use Illuminate\Mail\Mailable; -use Illuminate\Queue\SerializesModels; - -use App\Account; - -class RegisterConfirmation extends Mailable -{ - use Queueable, SerializesModels; - - private $account; - - public function __construct(Account $account) - { - $this->account = $account; - } - - public function build() - { - return $this->view('mails.register_confirmation') - ->text('mails.register_confirmation_text') - ->with([ - 'link' => route('account.authenticate.email_confirm', [$this->account->confirmation_key]) - ]); - } -} diff --git a/flexiapi/app/Services/AccountService.php b/flexiapi/app/Services/AccountService.php index a860991..86928f2 100644 --- a/flexiapi/app/Services/AccountService.php +++ b/flexiapi/app/Services/AccountService.php @@ -21,9 +21,7 @@ namespace App\Services; use App\Account; use App\AccountCreationToken; -use App\ActivationExpiration; use App\EmailChangeCode; -use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; use App\Http\Requests\Account\Create\Request as CreateRequest; use App\Http\Requests\Account\Update\Request as UpdateRequest; use App\Libraries\OvhSMS; @@ -38,7 +36,6 @@ use Carbon\Carbon; use Illuminate\Support\Facades\Mail; use Illuminate\Http\Request; use Illuminate\Validation\Rule; -use Illuminate\Support\Str; class AccountService { @@ -79,21 +76,9 @@ class AccountService } } - if ($account->activated == false) { - $account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize); - } - $account->save(); if ($request->asAdmin) { - if ((!$request->has('activated') || !(bool)$request->get('activated')) - && $request->has('confirmation_key_expires')) { - $actionvationExpiration = new ActivationExpiration(); - $actionvationExpiration->account_id = $account->id; - $actionvationExpiration->expires = $request->get('confirmation_key_expires'); - $actionvationExpiration->save(); - } - if ($request->has('dictionary')) { foreach ($request->get('dictionary') as $key => $value) { $account->setDictionaryEntry($key, $value); diff --git a/flexiapi/app/Services/BlockingService.php b/flexiapi/app/Services/BlockingService.php index b391a54..317d873 100644 --- a/flexiapi/app/Services/BlockingService.php +++ b/flexiapi/app/Services/BlockingService.php @@ -76,11 +76,6 @@ class BlockingService Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->toDateTimeString() )->count(); - // Deprecated, also detect if the account itself was updated recently, might be because of the confirmation_key change - if (Carbon::now()->subMinutes(config('app.blocking_time_period_check'))->isBefore($this->account->updated_at)) { - $events++; - } - return $events; } } diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index 77df630..c4da9ff 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -61,11 +61,6 @@ return [ 'blocking_time_period_check' => env('BLOCKING_TIME_PERIOD_CHECK', 30), 'blocking_amount_events_authorized_during_period' => env('BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD', 5), - /** - * /!\ Enable dangerous endpoints required for fallback - */ - 'dangerous_endpoints' => env('APP_DANGEROUS_ENDPOINTS', false), - /* |-------------------------------------------------------------------------- | Application Environment diff --git a/flexiapi/database/factories/AccountFactory.php b/flexiapi/database/factories/AccountFactory.php index 2070ec0..2f031db 100644 --- a/flexiapi/database/factories/AccountFactory.php +++ b/flexiapi/database/factories/AccountFactory.php @@ -44,7 +44,6 @@ class AccountFactory extends Factory 'display_name' => $this->faker->name, 'domain' => $domain->domain, 'user_agent' => $this->faker->userAgent, - 'confirmation_key' => Str::random(WebAuthenticateController::$emailCodeSize), 'ip_address' => $this->faker->ipv4, 'created_at' => $this->faker->dateTimeBetween('-1 year'), 'dtmf_protocol' => array_rand(Account::$dtmfProtocols), diff --git a/flexiapi/database/migrations/2025_04_10_145002_drop_activation_expirations_table_and_confirmation_key.php b/flexiapi/database/migrations/2025_04_10_145002_drop_activation_expirations_table_and_confirmation_key.php new file mode 100644 index 0000000..cfceb36 --- /dev/null +++ b/flexiapi/database/migrations/2025_04_10_145002_drop_activation_expirations_table_and_confirmation_key.php @@ -0,0 +1,34 @@ +dropColumn('confirmation_key'); + }); + } + + public function down() + { + Schema::table('accounts', function (Blueprint $table) { + $table->string('confirmation_key', 14)->nullable(); + }); + + Schema::create('activation_expirations', function (Blueprint $table) { + $table->id(); + $table->integer('account_id')->unsigned(); + $table->dateTime('expires'); + $table->timestamps(); + + $table->foreign('account_id')->references('id') + ->on('accounts')->onDelete('cascade'); + }); + } +}; diff --git a/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php b/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php index df76ffd..d7e3e89 100644 --- a/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php +++ b/flexiapi/database/seeds/LiblinphoneTesterAccoutSeeder.php @@ -41,7 +41,6 @@ class LiblinphoneTesterAccoutSeeder extends Seeder $element->domain, $element->phone ?? null, $element->activated ?? true, - $element->confirmation_key ?? null ) ); @@ -71,7 +70,6 @@ class LiblinphoneTesterAccoutSeeder extends Seeder $element->domain, $element->phone ?? null, $element->activated ?? true, - $element->confirmation_key ?? null ) ); @@ -104,7 +102,7 @@ class LiblinphoneTesterAccoutSeeder extends Seeder private function generateAccountArray( int $id, string $username, string $domain, string $phone = null, - bool $activated = true, string $confirmationKey = null + bool $activated = true ): array { return [ 'id' => $id, @@ -114,7 +112,6 @@ class LiblinphoneTesterAccoutSeeder extends Seeder 'email' => rawurlencode($username) . '@' . $domain, 'activated' => $activated, 'ip_address' => '', - 'confirmation_key' => $confirmationKey, 'user_agent' => 'FlexiAPI Seeder', 'created_at' => '2010-01-03 04:30:43' ]; diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index b9ad4f4..2189105 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -312,23 +312,6 @@ Return `404` if the token is non existing or invalid. ## Accounts -### `POST /accounts/public` -Deprecated @if(!config('app.dangerous_endpoints'))Disabled@endif Public Unsecure endpoint - -Create an account. -Return `422` if the parameters are invalid. -Send an email with the activation key if `email` is set, send an SMS otherwise. - -JSON parameters: - -* `username` **required** if `phone` not set, unique username, minimum 6 characters -* `password` **required** minimum 6 characters -* `algorithm` **required**, values can be `SHA-256` or `MD5` -* `domain` if not set the value is enforced to the default registration domain set in the global configuration -* `email` optional if `phone` set, an email, set an email to the account, must be unique if `ACCOUNT_EMAIL_UNIQUE` is set to `true` -* `phone` **required** if `username` not set, optional if `email` set, a valid phone number, set a phone number to the account -* `account_creation_token` the unique `account_creation_token` - ### `POST /accounts/with-account-creation-token` Public @@ -352,62 +335,6 @@ JSON parameters: Retrieve public information about the account. Return `404` if the account doesn't exists. -### `GET /accounts/{phone}/info-by-phone` -Deprecated @if(!config('app.dangerous_endpoints'))Disabled@endif Public Unsecure endpoint - -Retrieve public information about the account. -Return `404` if the account doesn't exists. - -Return `phone: true` if the returned account has a phone number. - -### `POST /accounts/recover-by-phone` -Deprecated @if(!config('app.dangerous_endpoints'))Disabled@endif Public Unsecure endpoint - -Send a SMS with a recovery PIN code to the `phone` number provided. -Return `404` if the account doesn't exists. - -Can only be used once, a new `recover_key` need to be requested to be called again. - -JSON parameters: - -* `phone` **required**, the phone number to send the SMS to -* `account_creation_token` the unique `account_creation_token` - -### `GET /accounts/{sip}/recover/{recover_key}` -Deprecated @if(!config('app.dangerous_endpoints'))Disabled@endif Public Unsecure endpoint - -Activate the account if the correct `recover_key` is provided. - -The `sip` parameter can be the default SIP account or the phone based one. - -Return the account information (including the hashed password) if valid. - -Return `404` if the account doesn't exists. - -### `POST /accounts/{sip}/activate/email` -Deprecated Public - -Use `POST /accounts/me/email/request` instead. - -Activate an account using a secret code received by email. -Return `404` if the account doesn't exists or if the code is incorrect, the validated account otherwise. - -JSON parameters: - -* `confirmation_key` the confirmation key - -### `POST /accounts/{sip}/activate/phone` -Deprecated Public - -Use `POST /accounts/me/phone/request` instead. - -Activate an account using a pin code received by phone. -Return `404` if the account doesn't exists or if the code is incorrect, the validated account otherwise. - -JSON parameters: - -* `confirmation_key` the PIN code - ### `GET /accounts/me/api_key/{auth_token}` Public @@ -459,7 +386,7 @@ JSON parameters: ### `POST /accounts` Admin -To create an account directly from the API. Deprecated If `activated` is set to `false` a random generated `confirmation_key` and `provisioning_token` will be returned to allow further activation using the public endpoints and provision the account. Check `confirmation_key_expires` to also set an expiration date on that `confirmation_key`. +To create an account directly from the API. Return `403` if the `max_accounts` limit of the corresponding Space is reached. @@ -476,7 +403,6 @@ JSON parameters: * `phone` optional, a valid phone number, set a phone number to the account * `dtmf_protocol` optional, values must be `sipinfo`, `sipmessage` or `rfc2833` * `dictionary` optional, an associative array attached to the account, see also the related endpoints. -* Deprecated `confirmation_key_expires` optional, a datetime of this format: Y-m-d H:i:s. Only used when `activated` is not used or `false`. Enforces an expiration date on the returned `confirmation_key`. After that datetime public email or phone activation endpoints will return `403`. ### `PUT /accounts/{id}` Admin diff --git a/flexiapi/resources/views/mails/register_confirmation.blade.php b/flexiapi/resources/views/mails/register_confirmation.blade.php deleted file mode 100644 index de919e5..0000000 --- a/flexiapi/resources/views/mails/register_confirmation.blade.php +++ /dev/null @@ -1,19 +0,0 @@ - - - Register on {{ space()->name }} - - -

Hello,

-

- You just created an account on {{ space()->name }} using your email account.
- Please follow the unique link bellow to set up your password and finish the registration process. -

-

- {{ $link }} -

-

- Regards,
- {{ config('mail.signature') }} -

- - diff --git a/flexiapi/resources/views/mails/register_confirmation_text.blade.php b/flexiapi/resources/views/mails/register_confirmation_text.blade.php deleted file mode 100644 index 12b0c3a..0000000 --- a/flexiapi/resources/views/mails/register_confirmation_text.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -Hello, - -You just created an account on {{ space()->name }} using your email account. -Please follow the unique link bellow to set up your password and finish the registration process. - -{{ $link }} - -Regards, -{{ config('mail.signature') }} \ No newline at end of file diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php index 5f7b8a6..3677aba 100644 --- a/flexiapi/routes/api.php +++ b/flexiapi/routes/api.php @@ -47,16 +47,6 @@ Route::post('accounts/with-account-creation-token', 'Api\Account\AccountControll Route::get('accounts/{sip}/info', 'Api\Account\AccountController@info'); -// Deprecated endpoints -Route::post('accounts/{sip}/activate/email', 'Api\Account\AccountController@activateEmail'); -Route::post('accounts/{sip}/activate/phone', 'Api\Account\AccountController@activatePhone'); - -// Deprecated endpoints /!\ Dangerous endpoints -Route::post('accounts/public', 'Api\Account\AccountController@storePublic'); -Route::get('accounts/{sip}/recover/{recovery_key}', 'Api\Account\AccountController@recoverUsingKey'); -Route::post('accounts/recover-by-phone', 'Api\Account\AccountController@recoverByPhone'); -Route::get('accounts/{phone}/info-by-phone', 'Api\Account\AccountController@phoneInfo'); - Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store'); Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt'); diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 7148b13..f339de8 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -60,9 +60,6 @@ Route::middleware(['web_panel_enabled', 'space.check'])->group(function () { Route::get('reset_password/{token}', 'Account\ResetPasswordEmailController@change')->name('account.reset_password_email.change'); Route::post('reset_password', 'Account\ResetPasswordEmailController@reset')->name('account.reset_password_email.reset'); - // Deprecated - Route::get('authenticate/email/{code}', 'Account\AuthenticateController@validateEmail')->name('account.authenticate.email_confirm'); - Route::prefix('creation_token')->controller(CreationRequestTokenController::class)->group(function () { Route::get('check/{token}', 'check')->name('account.creation_request_token.check'); Route::post('validate', 'validateToken')->name('account.creation_request_token.validate'); diff --git a/flexiapi/tests/Feature/AccountBlockingTest.php b/flexiapi/tests/Feature/AccountBlockingTest.php index 5da6e6d..b1737e1 100644 --- a/flexiapi/tests/Feature/AccountBlockingTest.php +++ b/flexiapi/tests/Feature/AccountBlockingTest.php @@ -32,7 +32,7 @@ class AccountBlockingTest extends TestCase $account = Account::factory()->withConsumedAccountCreationToken()->create(); $account->generateUserApiKey(); - config()->set('app.blocking_amount_events_authorized_during_period', 2); + config()->set('app.blocking_amount_events_authorized_during_period', 1); $this->keyAuthenticated($account) ->json($this->method, $this->route . '/me/phone/request', [ diff --git a/flexiapi/tests/Feature/ApiAccountTest.php b/flexiapi/tests/Feature/ApiAccountTest.php index 2872aa7..cbb5e94 100644 --- a/flexiapi/tests/Feature/ApiAccountTest.php +++ b/flexiapi/tests/Feature/ApiAccountTest.php @@ -20,9 +20,7 @@ namespace Tests\Feature; use App\Account; -use App\AccountCreationToken; use App\AccountTombstone; -use App\ActivationExpiration; use App\Password; use App\Space; use Carbon\Carbon; @@ -182,7 +180,6 @@ class ApiAccountTest extends TestCase 'activated' => false ]); - $this->assertFalse(empty($response1['confirmation_key'])); $this->assertFalse(empty($response1['provisioning_token'])); } @@ -327,7 +324,6 @@ class ApiAccountTest extends TestCase 'activated' => false ]); - $this->assertFalse(empty($response1['confirmation_key'])); $this->assertFalse(empty($response1['provisioning_token'])); } @@ -394,7 +390,6 @@ class ApiAccountTest extends TestCase 'admin' => true, ]); - $this->assertTrue(!empty($response1['confirmation_key'])); $this->assertFalse(empty($response1['provisioning_token'])); } @@ -532,15 +527,13 @@ class ApiAccountTest extends TestCase $username = 'username'; $response0 = $this->generateFirstResponse($password); - $response1 = $this->generateSecondResponse($password, $response0) + $this->generateSecondResponse($password, $response0) ->json($this->method, $this->route, [ 'username' => $username, 'algorithm' => 'SHA-256', 'password' => 'blabla', 'activated' => true, - ]); - - $response1 + ]) ->assertStatus(200) ->assertJson([ 'id' => 2, @@ -548,8 +541,6 @@ class ApiAccountTest extends TestCase 'domain' => config('app.sip_domain'), 'activated' => true, ]); - - $this->assertTrue(empty($response1['confirmation_key'])); } public function testNotActivated() @@ -575,7 +566,6 @@ class ApiAccountTest extends TestCase 'activated' => false, ]); - $this->assertFalse(empty($response1['confirmation_key'])); $this->assertFalse(empty($response1['provisioning_token'])); } @@ -630,68 +620,6 @@ class ApiAccountTest extends TestCase ->assertStatus(404); } - public function testActivateEmail() - { - $confirmationKey = '0123456789abc'; - $password = Password::factory()->create(); - $password->account->generateUserApiKey(); - $password->account->confirmation_key = $confirmationKey; - $password->account->activated = false; - $password->account->save(); - - $expiration = new ActivationExpiration(); - $expiration->account_id = $password->account->id; - $expiration->expires = Carbon::now()->subYear(); - $expiration->save(); - - $this->get($this->route . '/' . $password->account->identifier . '/info') - ->assertStatus(200) - ->assertJson([ - 'activated' => false - ]); - - $this->keyAuthenticated($password->account) - ->json($this->method, $this->route . '/blabla/activate/email', [ - 'confirmation_key' => $confirmationKey - ]) - ->assertStatus(404); - - $activateEmailRoute = $this->route . '/' . $password->account->identifier . '/activate/email'; - - $this->keyAuthenticated($password->account) - ->json($this->method, $activateEmailRoute, [ - 'confirmation_key' => $confirmationKey . 'longer' - ]) - ->assertStatus(422); - - $this->keyAuthenticated($password->account) - ->json($this->method, $activateEmailRoute, [ - 'confirmation_key' => 'X123456789abc' - ]) - ->assertStatus(404); - - // Expired - $this->keyAuthenticated($password->account) - ->json($this->method, $activateEmailRoute, [ - 'confirmation_key' => $confirmationKey - ]) - ->assertStatus(403); - - $expiration->delete(); - - $this->keyAuthenticated($password->account) - ->json($this->method, $activateEmailRoute, [ - 'confirmation_key' => $confirmationKey - ]) - ->assertStatus(200); - - $this->get($this->route . '/' . $password->account->identifier . '/info') - ->assertStatus(200) - ->assertJson([ - 'activated' => true - ]); - } - public function testUniqueEmailAdmin() { $email = 'collision@email.com'; @@ -794,329 +722,6 @@ class ApiAccountTest extends TestCase ]); } - /** - * /!\ Dangerous endpoints - */ - public function testRecover() - { - $confirmationKey = '0123'; - $password = Password::factory()->create(); - $password->account->generateUserApiKey(); - $password->account->confirmation_key = $confirmationKey; - $password->account->activated = false; - $password->account->save(); - - config()->set('app.dangerous_endpoints', true); - - $this->assertDatabaseHas('accounts', [ - 'username' => $password->account->username, - 'domain' => $password->account->domain, - 'activated' => false - ]); - - $this->get($this->route . '/' . $password->account->identifier . '/recover/' . $confirmationKey) - ->assertJson(['passwords' => [[ - 'password' => $password->password, - 'algorithm' => $password->algorithm - ]]]) - ->assertStatus(200); - - $this->json('GET', $this->route . '/' . $password->account->identifier . '/recover/' . $confirmationKey) - ->assertStatus(404); - - $this->assertDatabaseHas('accounts', [ - 'username' => $password->account->username, - 'domain' => $password->account->domain, - 'confirmation_key' => null, - 'activated' => true - ]); - - // Recover by phone - - $newConfirmationKey = '1345'; - $phone = '+1234'; - - $password->account->confirmation_key = $newConfirmationKey; - $password->account->phone = $phone; - $password->account->save(); - - $this->get($this->route . '/' . $phone . '@' . $password->account->domain . '/recover/' . $newConfirmationKey) - ->assertJson(['passwords' => [[ - 'password' => $password->password, - 'algorithm' => $password->algorithm - ]]]) - ->assertStatus(200); - } - - public function testRecoverTwice() - { - $confirmationKey = '1234'; - - $password = Password::factory()->create(); - $password->account->generateUserApiKey(); - $password->account->confirmation_key = $confirmationKey; - $password->account->activated = false; - $password->account->save(); - - $this->get($this->route . '/' . $password->account->identifier . '/recover/wrongkey') - ->assertStatus(404); - - $this->get($this->route . '/' . $password->account->identifier . '/recover/' . $confirmationKey) - ->assertStatus(404); - } - - /** - * /!\ Dangerous endpoints - */ - public function testRecoverPhone() - { - $phone = '+33612312312'; - - $password = Password::factory()->create(); - $password->account->generateUserApiKey(); - $password->account->activated = false; - $password->account->phone = $phone; - $password->account->save(); - - config()->set('app.dangerous_endpoints', true); - - $this->json($this->method, $this->route . '/recover-by-phone', [ - 'phone' => $phone - ])->assertJsonValidationErrors(['account_creation_token']); - - $this->json($this->method, $this->route . '/recover-by-phone', [ - 'phone' => $phone, - 'account_creation_token' => 'wrong' - ])->assertJsonValidationErrors(['account_creation_token']); - - $token = AccountCreationToken::factory()->create(); - - // Wrong phone - $this->json($this->method, $this->route . '/recover-by-phone', [ - 'phone' => '+33612312313', // wrong phone number - 'account_creation_token' => $token->token - ])->assertJsonValidationErrors(['phone']); - - $this->json($this->method, $this->route . '/recover-by-phone', [ - 'phone' => $phone, - 'account_creation_token' => $token->token - ])->assertStatus(200); - - $password->account->refresh(); - - // Use the token a second time - $this->json($this->method, $this->route . '/recover-by-phone', [ - 'phone' => $phone, - 'account_creation_token' => $token->token - ])->assertStatus(422); - - $this->get($this->route . '/' . $password->account->identifier . '/recover/' . $password->account->confirmation_key) - ->assertStatus(200) - ->assertJson([ - 'activated' => true - ]); - - $this->get($this->route . '/' . $phone . '/info-by-phone') - ->assertStatus(200) - ->assertJson([ - 'activated' => true, - 'phone' => true - ]); - - $this->get($this->route . '/+1234/info-by-phone') - ->assertStatus(302); - - $this->get($this->route . '/+33612312312/info-by-phone') - ->assertStatus(200); - - $this->json('GET', $this->route . '/' . $password->account->identifier . '/info-by-phone') - ->assertJsonValidationErrors(['phone']); - - // Check the mixed username/phone resolution... - $password->account->username = $phone; - $password->account->phone = null; - $password->account->save(); - - $this->get($this->route . '/' . $phone . '/info-by-phone') - ->assertStatus(200) - ->assertJson([ - 'activated' => true, - 'phone' => false - ]); - - $this->assertDatabaseHas('account_creation_tokens', [ - 'used' => true, - 'account_id' => $password->account->id, - ]); - } - - /** - * /!\ Dangerous endpoints - */ - - public function testCreatePublic() - { - $username = 'publicuser'; - - config()->set('app.dangerous_endpoints', true); - - // Missing email - $this->json($this->method, $this->route . '/public', [ - 'username' => $username, - 'algorithm' => 'SHA-256', - 'password' => '2', - ])->assertJsonValidationErrors(['email']); - - $this->json($this->method, $this->route . '/public', [ - 'username' => $username, - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - ])->assertJsonValidationErrors(['account_creation_token']); - - $token = AccountCreationToken::factory()->create(); - $userAgent = 'User Agent Test'; - - $this->withHeaders([ - 'User-Agent' => $userAgent, - ])->json($this->method, $this->route . '/public', [ - 'username' => $username, - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - 'account_creation_token' => $token->token - ]) - ->assertStatus(200) - ->assertJson([ - 'activated' => false - ]); - - // Re-use the token - $this->withHeaders([ - 'User-Agent' => $userAgent, - ])->json($this->method, $this->route . '/public', [ - 'username' => $username . 'foo', - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - 'account_creation_token' => $token->token - ])->assertStatus(422); - - // Already created - $this->json($this->method, $this->route . '/public', [ - 'username' => $username, - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - ])->assertJsonValidationErrors(['username']); - - // Email is now unique - config()->set('app.account_email_unique', true); - - $this->json($this->method, $this->route . '/public', [ - 'username' => 'johndoe', - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - ])->assertJsonValidationErrors(['email']); - - $this->assertDatabaseHas('accounts', [ - 'username' => $username, - 'domain' => config('app.sip_domain'), - 'user_agent' => $userAgent - ]); - - $this->assertDatabaseHas('account_creation_tokens', [ - 'used' => true, - 'account_id' => Account::where('username', $username)->first()->id, - ]); - } - - public function testCreatePublicPhone() - { - $phone = '+33612312312'; - - config()->set('app.dangerous_endpoints', true); - - // Bad phone format - $this->json($this->method, $this->route . '/public', [ - 'phone' => 'username', - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - ])->assertJsonValidationErrors(['phone']); - - $token = AccountCreationToken::factory()->create(); - - $this->json($this->method, $this->route . '/public', [ - 'phone' => $phone, - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - 'account_creation_token' => $token->token - ]) - ->assertStatus(200) - ->assertJson([ - 'activated' => false - ]); - - // Already exists - $this->json($this->method, $this->route . '/public', [ - 'phone' => $phone, - 'algorithm' => 'SHA-256', - 'password' => '2', - 'email' => 'john@doe.tld', - ])->assertJsonValidationErrors(['phone']); - - $this->assertDatabaseHas('accounts', [ - 'username' => $phone, - 'phone' => $phone, - 'domain' => config('app.sip_domain') - ]); - } - - public function testActivatePhone() - { - $confirmationKey = '0123'; - $password = Password::factory()->create(); - $password->account->generateUserApiKey(); - $password->account->confirmation_key = $confirmationKey; - $password->account->activated = false; - $password->account->save(); - - $expiration = new ActivationExpiration(); - $expiration->account_id = $password->account->id; - $expiration->expires = Carbon::now()->subYear(); - $expiration->save(); - - $this->get($this->route . '/' . $password->account->identifier . '/info') - ->assertStatus(200) - ->assertJson([ - 'activated' => false - ]); - - // Expired - $this->keyAuthenticated($password->account) - ->json($this->method, $this->route . '/' . $password->account->identifier . '/activate/phone', [ - 'confirmation_key' => $confirmationKey - ]) - ->assertStatus(403); - - $expiration->delete(); - - $this->keyAuthenticated($password->account) - ->json($this->method, $this->route . '/' . $password->account->identifier . '/activate/phone', [ - 'confirmation_key' => $confirmationKey - ]) - ->assertStatus(200); - - $this->assertDatabaseHas('accounts', [ - 'username' => $password->account->username, - 'domain' => $password->account->domain, - 'activated' => true - ]); - } - public function testChangePassword() { $account = Account::factory()->create(); @@ -1275,52 +880,6 @@ class ApiAccountTest extends TestCase ]); } - public function testCodeExpires() - { - $admin = Account::factory()->admin()->create(); - $admin->generateUserApiKey(); - - // Activated, no no confirmation_key - $this->keyAuthenticated($admin) - ->json($this->method, $this->route, [ - 'username' => 'foobar', - 'algorithm' => 'SHA-256', - 'password' => '123456', - 'activated' => true, - 'confirmation_key_expires' => '2040-12-12 12:12:12' - ]) - ->assertStatus(200) - ->assertJson([ - 'confirmation_key_expires' => null - ]); - - // Bad datetime format - $this->keyAuthenticated($admin) - ->json($this->method, $this->route, [ - 'username' => 'foobar2', - 'algorithm' => 'SHA-256', - 'password' => '123456', - 'activated' => false, - 'confirmation_key_expires' => 'abc' - ]) - ->assertStatus(422); - - // Bad datetime format - $this->keyAuthenticated($admin) - ->json($this->method, $this->route, [ - 'username' => 'foobar2', - 'algorithm' => 'SHA-256', - 'password' => '123456', - 'activated' => false, - 'confirmation_key_expires' => '2040-12-12 12:12:12' - ]) - ->assertStatus(200) - ->assertJson([ - 'confirmation_key_expires' => '2040-12-12 12:12:12' - ]); - ; - } - public function testDelete() { $password = Password::factory()->create();