mirror of
https://github.com/appwrite/appwrite
synced 2026-05-05 22:38:37 +00:00
commit
c4366c9de1
4 changed files with 53 additions and 32 deletions
|
|
@ -93,6 +93,13 @@ const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
|
|||
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
|
||||
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
|
||||
|
||||
/**
|
||||
* JWT for Resource Tokens.
|
||||
*/
|
||||
const RESOURCE_TOKEN_ALGORITHM = 'HS256';
|
||||
const RESOURCE_TOKEN_MAX_AGE = 86400 * 365 * 10; /* 10 years */
|
||||
const RESOURCE_TOKEN_LEEWAY = 10; // 10 seconds
|
||||
|
||||
/**
|
||||
* Token Expiration times.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1003,7 +1003,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
|||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
// Use a large but reasonable maxAge to avoid auto-exp when token has no expiry
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway.
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($tokenJWT);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class ResourceToken extends Model
|
|||
$expire = $document->getAttribute('expire');
|
||||
|
||||
// Use a large but reasonable maxAge to avoid auto-exp when we set explicit exp
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // 10 years
|
||||
|
||||
$payload = [
|
||||
'tokenId' => $document->getId(),
|
||||
|
|
@ -73,13 +73,13 @@ class ResourceToken extends Model
|
|||
'resourceInternalId' => $document->getAttribute('resourceInternalId'),
|
||||
];
|
||||
|
||||
$createdDate = new \DateTime($document->getCreatedAt());
|
||||
$payload['iat'] = $createdDate->getTimestamp();
|
||||
|
||||
// Set explicit expiration in JWT payload if we have an expiry date
|
||||
if ($expire !== null) {
|
||||
$expiryDate = new \DateTime($expire);
|
||||
$payload['exp'] = $expiryDate->getTimestamp();
|
||||
} else {
|
||||
// For infinite expiry, set 'iat' to prevent JWT library from auto-adding 'exp'
|
||||
$payload['iat'] = time();
|
||||
}
|
||||
|
||||
$secret = $jwt->encode($payload);
|
||||
|
|
|
|||
|
|
@ -72,37 +72,45 @@ class TokensConsoleClientTest extends Scope
|
|||
$this->assertEquals(400, $token['headers']['status-code']);
|
||||
$this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']);
|
||||
|
||||
// Success case: No expire date
|
||||
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'expire' => null,
|
||||
]);
|
||||
// Success cases: With & without expiry
|
||||
$expireList = [null, date('Y-m-d', strtotime("tomorrow"))];
|
||||
foreach ($expireList as $expire) {
|
||||
$token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'expire' => $expire,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $token['headers']['status-code']);
|
||||
$this->assertEquals('files', $token['body']['resourceType']);
|
||||
$this->assertNotEmpty($token['body']['$id']);
|
||||
$this->assertNotEmpty($token['body']['secret']);
|
||||
$this->assertEquals(201, $token['headers']['status-code']);
|
||||
$this->assertEquals('files', $token['body']['resourceType']);
|
||||
$this->assertNotEmpty($token['body']['$id']);
|
||||
$this->assertNotEmpty($token['body']['secret']);
|
||||
|
||||
// Verify the generated token JWT contains correct resource information
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge
|
||||
try {
|
||||
$payload = $jwt->decode($token['body']['secret']);
|
||||
$this->assertIsArray($payload, 'JWT payload should decode to an array');
|
||||
$this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId');
|
||||
$this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId');
|
||||
$this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType');
|
||||
$this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId');
|
||||
// Verify the generated token JWT contains correct resource information
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge
|
||||
try {
|
||||
$payload = $jwt->decode($token['body']['secret']);
|
||||
$this->assertIsArray($payload, 'JWT payload should decode to an array');
|
||||
$this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId');
|
||||
$this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId');
|
||||
$this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType');
|
||||
$this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId');
|
||||
$this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat');
|
||||
|
||||
$this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID');
|
||||
$this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format');
|
||||
$this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files');
|
||||
if (!empty($expire)) {
|
||||
$this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp');
|
||||
} else {
|
||||
$this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry');
|
||||
}
|
||||
|
||||
// For newly created tokens without expiry, should not have exp field
|
||||
$this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry');
|
||||
} catch (JWTException $e) {
|
||||
$this->fail('Failed to decode JWT: ' . $e->getMessage());
|
||||
$this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID');
|
||||
$this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format');
|
||||
$this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files');
|
||||
|
||||
} catch (JWTException $e) {
|
||||
$this->fail('Failed to decode JWT: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
|
|
@ -218,6 +226,11 @@ class TokensConsoleClientTest extends Scope
|
|||
$this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId');
|
||||
$this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType');
|
||||
$this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId');
|
||||
$this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat');
|
||||
|
||||
if (!empty($token['expire'])) {
|
||||
$this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp');
|
||||
}
|
||||
|
||||
$this->assertEquals($token['$id'], $payload['tokenId'], 'JWT tokenId should match token ID');
|
||||
$this->assertEquals($data['bucketId'] . ':' . $data['fileId'], $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format');
|
||||
|
|
|
|||
Loading…
Reference in a new issue