mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
Fix FLEXIAPI-152 API Key usage clarification
This commit is contained in:
parent
42e7ed83c0
commit
7418d79b41
12 changed files with 114 additions and 21 deletions
|
|
@ -7,6 +7,7 @@ v1.5
|
|||
- Fix FLEXIAPI-156 Disable the Phone change web form when PHONE_AUTHENTICATION is disabled
|
||||
- Fix FLEXIAPI-155 Add a new accountServiceAccountUpdatedHook and accountServiceAccountDeletedHook
|
||||
- Fix FLEXIAPI-153 Add phone and email to be changed in the Activity panel
|
||||
- Fix FLEXIAPI-152 API Key usage clarification
|
||||
- Fix FLEXIAPI-151 Migrate to hCaptcha
|
||||
- Fix FLEXIAPI-150 Use the same account_id parameter for both API and Web routes
|
||||
- Fix FLEXIAPI-149 Add a toggle to disable phone check on username for admin endpoints and forms
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ class Account extends Authenticatable
|
|||
return ($this->activationExpiration && $this->activationExpiration->isExpired());
|
||||
}
|
||||
|
||||
public function generateApiKey(): ApiKey
|
||||
public function generateApiKey(?Request $request = null): ApiKey
|
||||
{
|
||||
$this->apiKey()->delete();
|
||||
|
||||
|
|
@ -364,6 +364,7 @@ class Account extends Authenticatable
|
|||
$apiKey->account_id = $this->id;
|
||||
$apiKey->last_used_at = Carbon::now();
|
||||
$apiKey->key = Str::random(40);
|
||||
$apiKey->ip = $request ? $request->ip() : '127.0.0.1';
|
||||
$apiKey->save();
|
||||
|
||||
return $apiKey;
|
||||
|
|
|
|||
|
|
@ -83,12 +83,7 @@ class CreateAdminAccount extends Command
|
|||
$account->created_at = Carbon::now()->subYears(3);
|
||||
$account->save();
|
||||
|
||||
$apiKey = new ApiKey;
|
||||
$apiKey->account_id = $account->id;
|
||||
$apiKey->last_used_at = Carbon::now();
|
||||
$apiKey->key = Str::random(10);
|
||||
$apiKey->save();
|
||||
|
||||
$account->generateApiKey();
|
||||
$account->updatePassword($password);
|
||||
|
||||
$this->info('Admin test account created: "' . $username . '@' . $domain . '" | Password: "' . $password . '" | API Key: "' . $apiKey->key . '"');
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ApiKeyController extends Controller
|
|||
public function update(Request $request)
|
||||
{
|
||||
$account = $request->user();
|
||||
$account->generateApiKey();
|
||||
$account->generateApiKey($request);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class ApiKeyController extends Controller
|
|||
public function generate(Request $request)
|
||||
{
|
||||
$account = $request->user();
|
||||
$account->generateApiKey();
|
||||
$account->generateApiKey($request);
|
||||
|
||||
$account->refresh();
|
||||
Cookie::queue('x-api-key', $account->apiKey->key, config('app.api_key_expiration_minutes'));
|
||||
|
|
@ -37,12 +37,12 @@ class ApiKeyController extends Controller
|
|||
return $account->apiKey->key;
|
||||
}
|
||||
|
||||
public function generateFromToken(string $token)
|
||||
public function generateFromToken(Request $request, string $token)
|
||||
{
|
||||
$authToken = AuthToken::where('token', $token)->valid()->firstOrFail();
|
||||
|
||||
if ($authToken->account) {
|
||||
$authToken->account->generateApiKey();
|
||||
$authToken->account->generateApiKey($request);
|
||||
|
||||
$authToken->account->refresh();
|
||||
Cookie::queue('x-api-key', $authToken->account->apiKey->key, config('app.api_key_expiration_minutes'));
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Illuminate\Validation\Rule;
|
|||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Http\Response;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Validator;
|
||||
|
||||
class AuthenticateDigestOrKey
|
||||
|
|
@ -37,7 +38,7 @@ class AuthenticateDigestOrKey
|
|||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if ($request->bearerToken() && Auth::check()) {
|
||||
return $next($request);
|
||||
|
|
@ -50,8 +51,9 @@ class AuthenticateDigestOrKey
|
|||
$query->withoutGlobalScopes();
|
||||
}])->where('key', $request->header('x-api-key') ?? $request->cookie('x-api-key'))->first();
|
||||
|
||||
if ($apiKey) {
|
||||
if ($apiKey && ($apiKey->ip == null || $apiKey->ip == $request->ip())) {
|
||||
$apiKey->last_used_at = Carbon::now();
|
||||
$apiKey->requests = $apiKey->requests + 1;
|
||||
$apiKey->save();
|
||||
|
||||
Auth::login($apiKey->account);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use App\ApiKey;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->string('ip')->nullable();
|
||||
$table->integer('requests')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('api_keys', function (Blueprint $table) {
|
||||
$table->dropColumn('ip');
|
||||
$table->dropColumn('requests');
|
||||
});
|
||||
}
|
||||
};
|
||||
2
flexiapi/public/css/style.css
vendored
2
flexiapi/public/css/style.css
vendored
|
|
@ -116,7 +116,7 @@ pre {
|
|||
color: var(--second-7);
|
||||
}
|
||||
|
||||
b {
|
||||
b, strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="large">
|
||||
<h2><i class="material-symbols-outlined">key</i>API Key</h2>
|
||||
|
||||
|
|
@ -16,15 +15,21 @@
|
|||
|
||||
<p>An unused key will expires after some times.</p>
|
||||
|
||||
@if ($account->apiKey)
|
||||
<h3>Current Api Key</h3>
|
||||
<form>
|
||||
<div>
|
||||
<input type="text" readonly value="{{ $account->apiKey->key }}">
|
||||
<label>Key</label>
|
||||
<small>Can only be used from the following ip: {{ $account->apiKey->ip }} | {{ $account->apiKey->requests }} requests</small>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('account.api_key.update') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
<div>
|
||||
<input readonly placeholder="No key yet, press Generate"
|
||||
@if ($account->apiKey) value="{{ $account->apiKey->key }}" @endif>
|
||||
<label>Key</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Generate</button>
|
||||
<button type="submit" class="btn btn-primary">@if ($account->apiKey)Refresh the current key @else Generate a new key @endif</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,40 @@
|
|||
|
||||
@include('admin.account.parts.tabs')
|
||||
|
||||
@if ($account->apiKey)
|
||||
<h3>Api Key</h3>
|
||||
<table class="third">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>Created</th>
|
||||
<th>Last usage</th>
|
||||
<th>IP</th>
|
||||
<th>Requests</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $account->apiKey->key }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $account->apiKey->created_at }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $account->apiKey->last_used_at }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $account->apiKey->ip ?? '-' }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $account->apiKey->requests }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
|
||||
@if ($account->accountCreationToken)
|
||||
<h3>Account Creation Token</h3>
|
||||
<table class="third">
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ The endpoints are accessible using three different models:
|
|||
|
||||
You can retrieve an API Key from @if (config('app.web_panel')) [your account panel]({{ route('account.login') }}) @else your account panel @endif or using <a href="#get-accountsmeapikey">the dedicated API endpoint</a>.
|
||||
|
||||
**The generated API Key will be restricted to the IP that generates it and will be destroyed if not used after some times.**
|
||||
|
||||
You can then use your freshly generated key by adding a new `x-api-key` header to your API requests:
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@
|
|||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Account;
|
||||
use App\ApiKey;
|
||||
use App\Password;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiAccountApiKeyTest extends TestCase
|
||||
|
|
@ -53,6 +56,31 @@ class ApiAccountApiKeyTest extends TestCase
|
|||
->assertPlainCookie('x-api-key', $password->account->apiKey->key);
|
||||
}
|
||||
|
||||
public function testRequest()
|
||||
{
|
||||
$account = Account::factory()->create();
|
||||
$account->generateApiKey();
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, '/api/accounts/me')
|
||||
->assertStatus(200);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, '/api/accounts/me')
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertDatabaseHas('api_keys', [
|
||||
'account_id' => $account->id,
|
||||
'requests' => 2
|
||||
]);
|
||||
|
||||
DB::table('api_keys')->update(['ip' => 'no_localhost']);
|
||||
|
||||
$this->keyAuthenticated($account)
|
||||
->json($this->method, '/api/accounts/me')
|
||||
->assertStatus(401);
|
||||
}
|
||||
|
||||
public function testAuthToken()
|
||||
{
|
||||
// Generate a public auth_token
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue