Fix #92 Add two new endpoints regarding email account reset and account search per email for admins

This commit is contained in:
Timothée Jaussoin 2023-05-10 10:01:54 +00:00
parent 63e13502dc
commit 716789592e
8 changed files with 96 additions and 14 deletions

View file

@ -21,7 +21,6 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Carbon\Carbon;
@ -31,7 +30,6 @@ use App\Alias;
use App\ExternalAccount;
use App\Http\Requests\CreateAccountRequest;
use App\Http\Requests\UpdateAccountRequest;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Rules\BlacklistedUsername;
use App\Rules\IsNotPhoneNumber;
use App\Rules\NoUppercase;

View file

@ -33,11 +33,13 @@ use App\ActivationExpiration;
use App\Admin;
use App\Alias;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Mail\PasswordAuthentication;
use App\Rules\BlacklistedUsername;
use App\Rules\IsNotPhoneNumber;
use App\Rules\NoUppercase;
use App\Rules\SIPUsername;
use App\Rules\WithoutSpaces;
use Illuminate\Support\Facades\Mail;
class AccountController extends Controller
{
@ -56,6 +58,11 @@ class AccountController extends Controller
return Account::sip($sip)->firstOrFail();
}
public function searchByEmail(Request $request, string $email)
{
return Account::where('email', $email)->firstOrFail();
}
public function destroy($id)
{
$account = Account::findOrFail($id);
@ -183,7 +190,7 @@ class AccountController extends Controller
// Full reload
$account = Account::withoutGlobalScopes()->find($account->id);
Log::channel('events')->info('API: Admin: Account created', ['id' => $account->identifier]);
Log::channel('events')->info('API Admin: Account created', ['id' => $account->identifier]);
return response()->json($account->makeVisible(['confirmation_key', 'provisioning_token']));
}
@ -207,4 +214,18 @@ class AccountController extends Controller
return Account::findOrFail($id)->types()->detach($typeId);
}
public function recoverByEmail(int $id)
{
$account = Account::findOrFail($id);
$account->provision();
$account->confirmation_key = Str::random(WebAuthenticateController::$emailCodeSize);
$account->save();
Log::channel('events')->info('API Admin: Sending recovery email', ['id' => $account->identifier]);
Mail::to($account)->send(new PasswordAuthentication($account));
return response()->json($account->makeVisible(['confirmation_key', 'provisioning_token']));
}
}

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddEmailIndexToAccountsTable extends Migration
{
public function up()
{
Schema::table('accounts', function (Blueprint $table) {
$table->index('email');
});
}
public function down()
{
Schema::table('accounts', function (Blueprint $table) {
$table->dropIndex('accounts_email_index');
});
}
}

View file

@ -279,10 +279,18 @@ Retrieve all the accounts, paginated.
<span class="badge badge-warning">Admin</span>
Retrieve a specific account.
### `POST /accounts/{id}/recover-by-email`
<span class="badge badge-warning">Admin</span>
Send the account recovery email containing a fresh `provisioning_token` and `confirmation_key`
### `GET /accounts/{sip}/search`
<span class="badge badge-warning">Admin</span>
Search for a specific account by sip address.
### `GET /accounts/{email}/search-by-email`
<span class="badge badge-warning">Admin</span>
Search for a specific account by email.
### `DELETE /accounts/{id}`
<span class="badge badge-warning">Admin</span>
Delete a specific account and its related information.

View file

@ -84,9 +84,12 @@ Route::group(['middleware' => ['auth.digest_or_key']], function () {
Route::get('accounts/{id}/deactivate', 'Api\Admin\AccountController@deactivate');
Route::get('accounts/{id}/provision', 'Api\Admin\AccountController@provision');
Route::post('accounts/{id}/recover-by-email', 'Api\Admin\AccountController@recoverByEmail');
Route::post('accounts', 'Api\Admin\AccountController@store');
Route::get('accounts', 'Api\Admin\AccountController@index');
Route::get('accounts/{sip}/search', 'Api\Admin\AccountController@search');
Route::get('accounts/{email}/search-by-email', 'Api\Admin\AccountController@searchByEmail');
Route::get('accounts/{id}', 'Api\Admin\AccountController@show');
Route::delete('accounts/{id}', 'Api\Admin\AccountController@destroy');

View file

@ -188,9 +188,9 @@ class AccountProvisioningTest extends TestCase
->assertStatus(201)
->assertJson([
'token' => true
])->content();
]);
$authToken = json_decode($response)->token;
$authToken = $response->json('token');
$password = Password::factory()->create();
$password->account->generateApiKey();

View file

@ -64,9 +64,9 @@ class ApiAccountApiKeyTest extends TestCase
->assertStatus(201)
->assertJson([
'token' => true
])->content();
]);
$authToken = json_decode($response)->token;
$authToken = $response->json('token');
// Try to retrieve an API key from the un-attached auth_token
$response = $this->json($this->method, $this->route . '/' . $authToken)
@ -95,9 +95,9 @@ class ApiAccountApiKeyTest extends TestCase
->assertStatus(200)
->assertJson([
'api_key' => true
])->content();
]);
$apiKey = json_decode($response)->api_key;
$apiKey = $response->json('api_key');
// Re-retrieve
$this->json($this->method, $this->route . '/' . $authToken)
@ -106,8 +106,7 @@ class ApiAccountApiKeyTest extends TestCase
// Check the if the API key can be used for the account
$response = $this->withHeaders(['x-api-key' => $apiKey])
->json($this->method, '/api/accounts/me')
->assertStatus(200)
->content();
->assertStatus(200);
// Try with a wrong From
$response = $this->withHeaders([
@ -115,10 +114,9 @@ class ApiAccountApiKeyTest extends TestCase
'From' => 'sip:baduser@server.tld'
])
->json($this->method, '/api/accounts/me')
->assertStatus(200)
->content();
->assertStatus(200);
// Check if the account was correctly attached
$this->assertEquals(json_decode($response)->email, $password->account->email);
$this->assertEquals($response->json('email'), $password->account->email);
}
}

View file

@ -1007,6 +1007,38 @@ class ApiAccountTest extends TestCase
'id' => $password->account->id,
'activated' => true
]);
$this->keyAuthenticated($admin->account)
->get($this->route . '/' . $password->account->email . '/search-by-email')
->assertStatus(200)
->assertJson([
'id' => $password->account->id,
'activated' => true
]);
$this->keyAuthenticated($admin->account)
->get($this->route . '/wrong@email.com/search-by-email')
->assertStatus(404);
}
public function testRecoverByEmail()
{
$email = 'collision@email.com';
$account = Password::factory()->create();
$account->account->email = $email;
$account->account->save();
$admin = Admin::factory()->create();
$admin->account->generateApiKey();
$admin->account->save();
$response = $this->keyAuthenticated($admin->account)
->post($this->route . '/' . $account->id . '/recover-by-email')
->assertStatus(200);
$this->assertNotEquals($response->json('confirmation_key'), $account->confirmation_key);
$this->assertNotEquals($response->json('provisioning_token'), $account->provisioning_token);
}
public function testGetAll()