From 6565f2ff913c5a14f89ff696237cb3a66e0ae78f Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 12 Feb 2026 17:54:42 +0000 Subject: [PATCH] Derive console API key scopes from project API key Instead of hardcoding scopes in the console key, read them from the project API key. Added platforms.read and keys.read to the project key, and the console key now receives only scopes that the project key already has. --- src/Appwrite/Platform/Workers/Migrations.php | 48 ++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index fa0cb6ddb5..183c502fde 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -319,6 +319,8 @@ class Migrations extends Action 'functions.write', 'tokens.read', 'tokens.write', + 'platforms.read', + 'keys.read', ] ]); @@ -326,23 +328,37 @@ class Migrations extends Action } /** - * Generate a console-scoped dynamic API key for settings migration. - * - * This key allows the source adapter to read platforms and keys - * via the console API endpoints (same-instance only). - * - * @throws Exception + * Decode a dynamic API key and extract its scopes. */ - protected function generateConsoleAPIKey(string $targetProjectId): string + protected function decodeAPIKeyScopes(string $apiKey): array { + if (\str_contains($apiKey, '_')) { + [, $secret] = \explode('_', $apiKey, 2); + } else { + $secret = $apiKey; + } + + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400, 0); + $payload = $jwt->decode($secret); + + return $payload['scopes'] ?? []; + } + + /** + * Generate a console-scoped dynamic API key for settings migration. + * Scopes are derived from the project API key (same-instance only). + */ + protected function generateConsoleAPIKey(string $targetProjectId, array $scopes): string + { + if (empty($scopes)) { + return ''; + } + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400, 0); $apiKey = $jwt->encode([ 'projectId' => 'console', - 'scopes' => [ - 'platforms.read', - 'keys.read', - ], + 'scopes' => $scopes, 'targetProjectId' => $targetProjectId, ]); @@ -389,8 +405,14 @@ class Migrations extends Action // Only generate and set consoleApiKey for same-instance migrations // to avoid leaking a locally-signed JWT to untrusted remote servers if ($credentials['endpoint'] === $endpoint) { - $credentials['consoleApiKey'] = $this->generateConsoleAPIKey($credentials['projectId']); - $credentials['sourceProjectId'] = $credentials['projectId']; + // Read scopes from the project API key + $projectKeyScopes = $this->decodeAPIKeyScopes($credentials['apiKey']); + $consoleApiKey = $this->generateConsoleAPIKey($credentials['projectId'], $projectKeyScopes); + + if (!empty($consoleApiKey)) { + $credentials['consoleApiKey'] = $consoleApiKey; + $credentials['sourceProjectId'] = $credentials['projectId']; + } } }