remove: jwt logic. serve the jwt directly from the model itself.

This commit is contained in:
Darshan
2025-05-13 15:41:26 +05:30
parent 06bf26682e
commit 4e9f137e55
8 changed files with 79 additions and 126 deletions
+5
View File
@@ -499,6 +499,11 @@ return [
'description' => 'The requested file token has expired.',
'code' => 401,
],
Exception::TOKEN_RESOURCE_INVALID => [
'name' => Exception::TOKEN_RESOURCE_INVALID,
'description' => 'The resource for the token is invalid.',
'code' => 400,
],
/** VCS */
Exception::INSTALLATION_NOT_FOUND => [
+33 -18
View File
@@ -925,31 +925,46 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
if ($token->isEmpty() || $token->getAttribute('secret') !== $secret) {
if ($token->isEmpty()) {
return new Document([]);
}
if ($token->getAttribute('resourceType') === TOKENS_RESOURCE_TYPE_FILES) {
$internalIds = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
$expiry = $token->getAttribute('expire');
if (count($internalIds) !== 2 || count($ids) !== 2) {
if ($expiry !== null) {
$now = new \DateTime();
$expiryDate = new \DateTime($expiry);
if ($expiryDate < $now) {
return new Document([]);
}
$accessedAt = $token->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), - APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
$token->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
}
return new Document([
'bucketId' => $ids[0],
'fileId' => $ids[1],
'bucketInternalId' => $internalIds[0],
'fileInternalId' => $internalIds[1],
]);
}
return match ($token->getAttribute('resourceType')) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
$internalIds = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
if (count($internalIds) !== 2 || count($ids) !== 2) {
return new Document([]);
}
$accessedAt = $token->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), - APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
$token->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
}
return new Document([
'bucketId' => $ids[0],
'fileId' => $ids[1],
'bucketInternalId' => $internalIds[0],
'fileInternalId' => $internalIds[1],
]);
})(),
default => throw new Exception(Exception::TOKEN_RESOURCE_INVALID),
};
}
return new Document([]);
}, ['project', 'dbForProject', 'request']);
+1 -1
View File
@@ -322,7 +322,7 @@ class Exception extends \Exception
/** Tokens */
public const TOKEN_NOT_FOUND = 'token_not_found';
public const TOKEN_EXPIRED = 'token_expired';
public const TOKEN_RESOURCE_INVALID = 'token_resource_invalid';
protected string $type = '';
protected array $errors = [];
@@ -71,7 +71,7 @@ class Create extends Action
->callback([$this, 'action']);
}
public function action(string $bucketId, string $fileId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents)
public function action(string $bucketId, string $fileId, ?string $expire, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents): void
{
/**
@@ -1,91 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Tokens\Http\Tokens\JWT;
use Ahc\Jwt\JWT;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class Get extends Action
{
use HTTP;
public static function getName()
{
return 'getTokenJWT';
}
public function __construct()
{
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/tokens/:tokenId/jwt')
->desc('Get token as JWT')
->groups(['api', 'tokens'])
->label('scope', 'tokens.read')
->label('usage.metric', 'tokens.{scope}.requests.read')
->label('usage.params', ['tokenId:{request.tokenId}'])
->label('sdk', new Method(
namespace: 'tokens',
group: 'tokens',
name: 'getJWT',
description: <<<EOT
Get a JWT based token by its unique ID. You can use the JWT to authenticate on behalf of the user.
EOT,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_JWT,
)
],
contentType: ContentType::JSON
))
->param('tokenId', '', new UID(), 'File token ID.')
->inject('response')
->inject('dbForProject')
->callback([$this, 'action']);
}
public function action(string $tokenId, Response $response, Database $dbForProject)
{
$token = $dbForProject->getDocument('resourceTokens', $tokenId);
if ($token->isEmpty()) {
throw new Exception(Exception::TOKEN_NOT_FOUND);
}
// calculate maxAge based on expiry date
$maxAge = PHP_INT_MAX;
$expire = $token->getAttribute('expire');
if ($expire !== null) {
$now = new \DateTime();
$expiryDate = new \DateTime($expire);
if ($expiryDate < $now) {
throw new Exception(Exception::TOKEN_EXPIRED);
}
$maxAge = $expiryDate->getTimestamp() - $now->getTimestamp();
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $maxAge, 10); // Instantiate with key, algo, maxAge and leeway.
$response
->setStatusCode(Response::STATUS_CODE_OK)
->dynamic(new Document(['jwt' => $jwt->encode([
'resourceType' => $token->getAttribute('resourceType'),
'resourceId' => $token->getAttribute('resourceId'),
'resourceInternalId' => $token->getAttribute('resourceInternalId'),
'tokenId' => $token->getId(),
'secret' => $token->getAttribute('secret')
])]), Response::MODEL_JWT);
}
}
@@ -6,7 +6,6 @@ use Appwrite\Platform\Modules\Tokens\Http\Tokens\Buckets\Files\Create as CreateF
use Appwrite\Platform\Modules\Tokens\Http\Tokens\Buckets\Files\XList as ListFileTokens;
use Appwrite\Platform\Modules\Tokens\Http\Tokens\Delete as DeleteToken;
use Appwrite\Platform\Modules\Tokens\Http\Tokens\Get as GetToken;
use Appwrite\Platform\Modules\Tokens\Http\Tokens\JWT\Get as GetTokenJWT;
use Appwrite\Platform\Modules\Tokens\Http\Tokens\Update as UpdateToken;
use Utopia\Platform\Service;
@@ -18,7 +17,6 @@ class Http extends Service
$this
->addAction(CreateFileToken::getName(), new CreateFileToken())
->addAction(GetToken::getName(), new GetToken())
->addAction(GetTokenJWT::getName(), new GetTokenJWT())
->addAction(ListFileTokens::getName(), new ListFileTokens())
->addAction(UpdateToken::getName(), new UpdateToken())
->addAction(DeleteToken::getName(), new DeleteToken())
@@ -2,8 +2,11 @@
namespace Appwrite\Utopia\Response\Model;
use Ahc\Jwt\JWT;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Document;
use Utopia\System\System;
class ResourceToken extends Model
{
@@ -47,6 +50,13 @@ class ResourceToken extends Model
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('secret', [
'type' => self::TYPE_STRING,
'description' => 'JWT encoded string.',
'default' => '',
// this is a secret but is converted to a JWT token when sent back to the client after filter.
'example' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
])
->addRule('accessedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Most recent access date in ISO 8601 format. This attribute is only updated again after ' . APP_RESOURCE_TOKEN_ACCESS / 60 / 60 . ' hours.',
@@ -56,6 +66,32 @@ class ResourceToken extends Model
;
}
public function filter(Document $document): Document
{
$maxAge = PHP_INT_MAX;
$expire = $document->getAttribute('expire');
if ($expire !== null) {
$now = new \DateTime();
$expiryDate = new \DateTime($expire);
// set 1 min if expired, we check for expiry later on route hooks for validation!
$maxAge = min(360, $expiryDate->getTimestamp() - $now->getTimestamp());
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $maxAge, 10);
$secret = $jwt->encode([
'tokenId' => $document->getId(),
'resourceId' => $document->getAttribute('resourceId'),
'resourceType' => $document->getAttribute('resourceType'),
'resourceInternalId' => $document->getAttribute('resourceInternalId'),
]);
$document->setAttribute('secret', $secret);
return $document;
}
/**
* Get Name
*
+3 -13
View File
@@ -66,7 +66,7 @@ trait TokensBase
return [
'fileId' => $fileId,
'bucketId' => $bucketId,
'tokenId' => $token['body']['$id'],
'token' => $token['body'],
'guestHeaders' => [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
@@ -143,22 +143,12 @@ trait TokensBase
*/
public function testPreviewFileWithToken(array $data): array
{
$token = $data['token'];
$fileId = $data['fileId'];
$tokenId = $data['tokenId'];
$bucketId = $data['bucketId'];
$guestHeaders = $data['guestHeaders'];
$adminHeaders = array_merge($guestHeaders, ['x-appwrite-key' => $this->getProject()['apiKey']]);
// Generate JWT as an admin user.
$tokenJWT = $this->client->call(
Client::METHOD_GET,
'/tokens/' . $tokenId . '/jwt/',
$adminHeaders
);
$this->assertEquals(200, $tokenJWT['headers']['status-code']);
$this->assertArrayHasKey('jwt', $tokenJWT['body']);
$tokenJWT = $tokenJWT['body']['jwt'];
$tokenJWT = $token['secret'];
// Generate a preview
$filePreview = $this->client->call(