diff --git a/flexiapi/app/Http/Controllers/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Admin/AccountController.php
index d768cc0..b67313d 100644
--- a/flexiapi/app/Http/Controllers/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Admin/AccountController.php
@@ -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;
diff --git a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
index b81ec89..e7a6df7 100644
--- a/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
+++ b/flexiapi/app/Http/Controllers/Api/Admin/AccountController.php
@@ -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']));
+ }
}
diff --git a/flexiapi/database/migrations/2023_05_09_145418_add_email_index_to_accounts_table.php b/flexiapi/database/migrations/2023_05_09_145418_add_email_index_to_accounts_table.php
new file mode 100644
index 0000000..e76453c
--- /dev/null
+++ b/flexiapi/database/migrations/2023_05_09_145418_add_email_index_to_accounts_table.php
@@ -0,0 +1,22 @@
+index('email');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('accounts', function (Blueprint $table) {
+ $table->dropIndex('accounts_email_index');
+ });
+ }
+}
diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php
index b7cf3b7..dd81bad 100644
--- a/flexiapi/resources/views/api/documentation_markdown.blade.php
+++ b/flexiapi/resources/views/api/documentation_markdown.blade.php
@@ -279,10 +279,18 @@ Retrieve all the accounts, paginated.
Admin
Retrieve a specific account.
+### `POST /accounts/{id}/recover-by-email`
+Admin
+Send the account recovery email containing a fresh `provisioning_token` and `confirmation_key`
+
### `GET /accounts/{sip}/search`
Admin
Search for a specific account by sip address.
+### `GET /accounts/{email}/search-by-email`
+Admin
+Search for a specific account by email.
+
### `DELETE /accounts/{id}`
Admin
Delete a specific account and its related information.
diff --git a/flexiapi/routes/api.php b/flexiapi/routes/api.php
index 03e304d..92e6190 100644
--- a/flexiapi/routes/api.php
+++ b/flexiapi/routes/api.php
@@ -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');
diff --git a/flexiapi/tests/Feature/AccountProvisioningTest.php b/flexiapi/tests/Feature/AccountProvisioningTest.php
index c35c4af..189b84f 100644
--- a/flexiapi/tests/Feature/AccountProvisioningTest.php
+++ b/flexiapi/tests/Feature/AccountProvisioningTest.php
@@ -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();
diff --git a/flexiapi/tests/Feature/ApiAccountApiKeyTest.php b/flexiapi/tests/Feature/ApiAccountApiKeyTest.php
index 6a87c44..026b478 100644
--- a/flexiapi/tests/Feature/ApiAccountApiKeyTest.php
+++ b/flexiapi/tests/Feature/ApiAccountApiKeyTest.php
@@ -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);
}
}
diff --git a/flexiapi/tests/Feature/ApiAccountTest.php b/flexiapi/tests/Feature/ApiAccountTest.php
index 107ec88..0d9fd44 100644
--- a/flexiapi/tests/Feature/ApiAccountTest.php
+++ b/flexiapi/tests/Feature/ApiAccountTest.php
@@ -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()