diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc9a99..440b5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 895ff56..68d0fd5 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -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; diff --git a/flexiapi/app/Console/Commands/CreateAdminAccount.php b/flexiapi/app/Console/Commands/CreateAdminAccount.php index 55954f5..a1e12de 100644 --- a/flexiapi/app/Console/Commands/CreateAdminAccount.php +++ b/flexiapi/app/Console/Commands/CreateAdminAccount.php @@ -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 . '"'); diff --git a/flexiapi/app/Http/Controllers/Account/ApiKeyController.php b/flexiapi/app/Http/Controllers/Account/ApiKeyController.php index b80ef80..700f182 100644 --- a/flexiapi/app/Http/Controllers/Account/ApiKeyController.php +++ b/flexiapi/app/Http/Controllers/Account/ApiKeyController.php @@ -34,7 +34,7 @@ class ApiKeyController extends Controller public function update(Request $request) { $account = $request->user(); - $account->generateApiKey(); + $account->generateApiKey($request); return redirect()->back(); } diff --git a/flexiapi/app/Http/Controllers/Api/Account/ApiKeyController.php b/flexiapi/app/Http/Controllers/Api/Account/ApiKeyController.php index f4406d0..0dbab97 100644 --- a/flexiapi/app/Http/Controllers/Api/Account/ApiKeyController.php +++ b/flexiapi/app/Http/Controllers/Api/Account/ApiKeyController.php @@ -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')); diff --git a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php index 32b27d4..0930b2c 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php +++ b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php @@ -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); diff --git a/flexiapi/database/migrations/2024_04_08_144054_add_request_counter_and_ip_to_api_keys_table.php b/flexiapi/database/migrations/2024_04_08_144054_add_request_counter_and_ip_to_api_keys_table.php new file mode 100644 index 0000000..47ddf92 --- /dev/null +++ b/flexiapi/database/migrations/2024_04_08_144054_add_request_counter_and_ip_to_api_keys_table.php @@ -0,0 +1,25 @@ +string('ip')->nullable(); + $table->integer('requests')->default(0); + }); + } + + public function down() + { + Schema::table('api_keys', function (Blueprint $table) { + $table->dropColumn('ip'); + $table->dropColumn('requests'); + }); + } +}; diff --git a/flexiapi/public/css/style.css b/flexiapi/public/css/style.css index 9aefbe7..29bc1b4 100644 --- a/flexiapi/public/css/style.css +++ b/flexiapi/public/css/style.css @@ -116,7 +116,7 @@ pre { color: var(--second-7); } -b { +b, strong { font-weight: bold; } diff --git a/flexiapi/resources/views/account/api_key.blade.php b/flexiapi/resources/views/account/api_key.blade.php index 29dbae6..450e7a6 100644 --- a/flexiapi/resources/views/account/api_key.blade.php +++ b/flexiapi/resources/views/account/api_key.blade.php @@ -7,7 +7,6 @@ @endsection @section('content') -

keyAPI Key

@@ -16,15 +15,21 @@

An unused key will expires after some times.

+ @if ($account->apiKey) +

Current Api Key

+
+
+ + + Can only be used from the following ip: {{ $account->apiKey->ip }} | {{ $account->apiKey->requests }} requests +
+
+ @endif +
@csrf
- apiKey) value="{{ $account->apiKey->key }}" @endif> - -
-
- +
diff --git a/flexiapi/resources/views/admin/account/activity/index.blade.php b/flexiapi/resources/views/admin/account/activity/index.blade.php index 3cde897..f1587a1 100644 --- a/flexiapi/resources/views/admin/account/activity/index.blade.php +++ b/flexiapi/resources/views/admin/account/activity/index.blade.php @@ -14,6 +14,40 @@ @include('admin.account.parts.tabs') +@if ($account->apiKey) +

Api Key

+ + + + + + + + + + + + + + + + + + + +
CodeCreatedLast usageIPRequests
+ {{ $account->apiKey->key }} + + {{ $account->apiKey->created_at }} + + {{ $account->apiKey->last_used_at }} + + {{ $account->apiKey->ip ?? '-' }} + + {{ $account->apiKey->requests }} +
+@endif + @if ($account->accountCreationToken)

Account Creation Token

diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index f1a8973..c2e6fac 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -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 the dedicated API endpoint. +**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: ``` diff --git a/flexiapi/tests/Feature/ApiAccountApiKeyTest.php b/flexiapi/tests/Feature/ApiAccountApiKeyTest.php index 9005329..057daf3 100644 --- a/flexiapi/tests/Feature/ApiAccountApiKeyTest.php +++ b/flexiapi/tests/Feature/ApiAccountApiKeyTest.php @@ -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