mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Implement tests, fix JWT maxAge
This commit is contained in:
@@ -2370,15 +2370,12 @@ App::post('/v1/account/jwts')
|
||||
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic(new Document(['jwt' => $jwt->encode([
|
||||
// 'uid' => 1,
|
||||
// 'aud' => 'http://site.com',
|
||||
// 'scopes' => ['user'],
|
||||
// 'iss' => 'http://api.mysite.com',
|
||||
'exp' => \intval((new \DateTime())->add(new \DateInterval('PT900S'))->format('U')),
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $current->getId(),
|
||||
])]), Response::MODEL_JWT);
|
||||
|
||||
@@ -1585,8 +1585,10 @@ App::post('/v1/functions/:functionId/executions')
|
||||
}
|
||||
|
||||
if (!$current->isEmpty()) {
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = $jwtObj->encode([
|
||||
'exp' => \intval((new \DateTime())->add(new \DateInterval('PT' . $jwtExpiry . 'S'))->format('U')),
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $current->getId(),
|
||||
]);
|
||||
@@ -1594,8 +1596,9 @@ App::post('/v1/functions/:functionId/executions')
|
||||
}
|
||||
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 10);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
$apiKey = $jwtObj->encode([
|
||||
'exp' => \intval((new \DateTime())->add(new \DateInterval('PT' . $jwtExpiry . 'S'))->format('U')),
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $function->getAttribute('scopes', [])
|
||||
]);
|
||||
|
||||
@@ -2939,11 +2939,11 @@ App::post('/v1/messaging/messages/push')
|
||||
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
|
||||
}
|
||||
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
|
||||
$jwt = $encoder->encode([
|
||||
'iat' => \time(),
|
||||
'exp' => $expiry,
|
||||
'exp' => \intval($expiry),
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
@@ -3801,11 +3801,11 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
$expiry = (new \DateTime())->add(new \DateInterval('P15D'))->format('U');
|
||||
}
|
||||
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
|
||||
$jwt = $encoder->encode([
|
||||
'iat' => \time(),
|
||||
'exp' => $expiry,
|
||||
'exp' => \intval($expiry),
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
|
||||
@@ -1328,7 +1328,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'));
|
||||
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
|
||||
try {
|
||||
$decoded = $decoder->decode($jwt);
|
||||
@@ -1336,6 +1336,10 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if($decoded['exp'] < \time()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (
|
||||
$decoded['projectId'] !== $project->getId() ||
|
||||
$decoded['bucketId'] !== $bucketId ||
|
||||
|
||||
@@ -2105,11 +2105,11 @@ App::post('/v1/users/:userId/jwts')
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_JWT)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('sessionId', 'current', new UID(), 'Session ID. Use the string \'current\' to use the most recent session. Defaults to the most recent session.')
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.')
|
||||
->param('sessionId', 'current', new UID(), 'Session ID. Use the string \'current\' to use the most recent session. Defaults to the most recent session.', true)
|
||||
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $userId, int $duration, string $sessionId, Response $response, Database $dbForProject) {
|
||||
->action(function (string $userId, string $sessionId, int $duration, Response $response, Database $dbForProject) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -2136,13 +2136,14 @@ App::post('/v1/users/:userId/jwts')
|
||||
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic(new Document(['jwt' => $jwt->encode([
|
||||
'exp' => \intval((new \DateTime())->add(new \DateInterval('PT' . $duration . 'S'))->format('U')),
|
||||
'userId' => $user->getId(),
|
||||
'sessionId' => $session->getId(),
|
||||
'sessionId' => $session->getId()
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ App::init()
|
||||
if($keyType === API_KEY_DYNAMIC) {
|
||||
// Dynamic key
|
||||
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
|
||||
try {
|
||||
$payload = $jwtObj->decode($authKey);
|
||||
@@ -224,6 +224,10 @@ App::init()
|
||||
throw new Exception(Exception::API_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
if($payload['exp'] < \time()) {
|
||||
throw new Exception(Exception::API_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
$projectId = $payload['projectId'] ?? '';
|
||||
$tokenScopes = $payload['scopes'] ?? [];
|
||||
|
||||
|
||||
+18
-4
@@ -440,12 +440,10 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$s = Authorization::skip(fn () => $database->find('sessions', [
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
\var_dump($s);
|
||||
return $s;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1202,7 +1200,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
$authJWT = $request->getHeader('x-appwrite-jwt', '');
|
||||
|
||||
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
@@ -1220,6 +1218,22 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
|
||||
$user = new Document([]);
|
||||
}
|
||||
|
||||
$exp = $payload['exp'] ?? '';
|
||||
|
||||
// Fallback to 15m, just in case
|
||||
if(empty($exp)) {
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
$user = new Document([]);
|
||||
}
|
||||
} else {
|
||||
if($exp < \time()) {
|
||||
$user = new Document([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
|
||||
@@ -284,8 +284,9 @@ class Functions extends Action
|
||||
$runtime = $runtimes[$function->getAttribute('runtime')];
|
||||
|
||||
$jwtExpiry = $function->getAttribute('timeout', 900);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 10);
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 10);
|
||||
$apiKey = $jwtObj->encode([
|
||||
'exp' => \intval((new \DateTime())->add(new \DateInterval('PT' . $jwtExpiry . 'S'))->format('U')),
|
||||
'projectId' => $project->getId(),
|
||||
'scopes' => $function->getAttribute('scopes', [])
|
||||
]);
|
||||
|
||||
@@ -1553,6 +1553,137 @@ trait UsersBase
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testUsetJWT()
|
||||
{
|
||||
// Create user
|
||||
$userId = ID::unique();
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $userId,
|
||||
'email' => 'jwtuser@appwrite.io',
|
||||
'password' => 'password',
|
||||
], false);
|
||||
$this->assertEquals($user['headers']['status-code'], 201);
|
||||
|
||||
// Create two sessions
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'email' => 'jwtuser@appwrite.io',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($userId, $response['body']['userId']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$session1Id = $response['body']['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'email' => 'jwtuser@appwrite.io',
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertEquals($userId, $response['body']['userId']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$session2Id = $response['body']['$id'];
|
||||
|
||||
// Create JWT 1 for older session by ID
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'sessionId' => $session1Id
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['jwt']);
|
||||
$jwt1 = $response['body']['jwt'];
|
||||
|
||||
// Ensure JWT 1 works
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt1,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($userId, $response['body']['$id']);
|
||||
|
||||
// Create JWT 2 for latest session using default param
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'duration' => 5
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['jwt']);
|
||||
$jwt2 = $response['body']['jwt'];
|
||||
|
||||
// Ensure JWT 2 works
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt2,
|
||||
]));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($userId, $response['body']['$id']);
|
||||
|
||||
// Wait, ensure JWT 2 no longer works because of short duration
|
||||
|
||||
\sleep(10);
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt2,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
// Delete session, ensure JWT 1 no longer works because of session missing
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId . '/sessions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'sessionId' => $session1Id
|
||||
]);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-jwt' => $jwt1,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
// Cleanup after test
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 204);
|
||||
}
|
||||
|
||||
// TODO add test for session delete
|
||||
// TODO add test for all sessions delete
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user