Skip to content

Feat 511 jwt support #784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions app/controllers/api/account.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Ahc\Jwt\JWT;
use Utopia\App;
use Utopia\Exception;
use Utopia\Config\Config;
Expand Down Expand Up @@ -637,6 +638,49 @@
;
});

App::post('/v1/account/jwt')
->desc('Create Account JWT')
->groups(['api', 'account'])
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/account/create-jwt.md')
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{param-userId}')
->inject('response')
->inject('user')
->action(function ($response, $user) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */

$tokens = $user->getAttribute('tokens', []);
$session = new Document();

foreach ($tokens as $token) { /** @var Appwrite\Database\Document $token */
if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$session = $token;
}
}

if($session->isEmpty()) {
throw new Exception('No valid session found', 401);
}

$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 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',
'userId' => $user->getId(),
'sessionId' => $session->getId(),
])]), Response::MODEL_JWT);
});

App::get('/v1/account')
->desc('Get Account')
->groups(['api', 'account'])
Expand Down
44 changes: 24 additions & 20 deletions app/controllers/general.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,27 +164,31 @@
$roles = Config::getParam('roles', []);
$scope = $route->getLabel('scope', 'none'); // Allowed scope for chosen route
$scopes = $roles[$role]['scopes']; // Allowed scopes for user role

// Check if given key match project API keys
$key = $project->search('secret', $request->getHeader('x-appwrite-key', ''), $project->getAttribute('keys', []));

/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if (null !== $key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => Auth::USER_STATUS_ACTIVATED,
'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);

$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));

Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
$authKey = $request->getHeader('x-appwrite-key', '');

if (!empty($authKey)) { // API Key authentication
// Check if given key match project API keys
$key = $project->search('secret', $authKey, $project->getAttribute('keys', []));

/*
* Try app auth when we have project key and no user
* Mock user to app and grant API key scopes in addition to default app scopes
*/
if ($key && $user->isEmpty()) {
$user = new Document([
'$id' => '',
'status' => Auth::USER_STATUS_ACTIVATED,
'email' => 'app.'.$project->getId().'@service.'.$request->getHostname(),
'password' => '',
'name' => $project->getAttribute('name', 'Untitled'),
]);

$role = Auth::USER_ROLE_APP;
$scopes = \array_merge($roles[$role]['scopes'], $key->getAttribute('scopes', []));

Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys.
}
}

if ($user->getId()) {
Expand Down
25 changes: 25 additions & 0 deletions app/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
require_once __DIR__.'/../vendor/autoload.php';
}

use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Database\Database;
use Appwrite\Database\Adapter\MySQL as MySQLAdapter;
Expand Down Expand Up @@ -412,6 +414,29 @@ function($value) {
}
}

$authJWT = $request->getHeader('x-appwrite-jwt', '');

if (!empty($authJWT)) { // JWT authentication
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.

try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception('Failed to verify JWT. '.$error->getMessage(), 401);
}

$jwtUserId = $payload['userId'] ?? '';
$jwtSessionId = $payload['sessionId'] ?? '';

if($jwtUserId && $jwtSessionId) {
$user = $projectDB->getDocument($jwtUserId);
}

if (empty($user->search('$id', $jwtSessionId, $user->getAttribute('tokens')))) { // Match JWT to active token
$user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]);
}
}

return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']);

Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"domnikl/statsd": "3.0.2",
"influxdb/influxdb-php": "1.15.1",
"phpmailer/phpmailer": "6.1.7",
"chillerlan/php-qrcode": "4.3.0"
"chillerlan/php-qrcode": "4.3.0",
"adhocore/jwt": "1.1.0"
},
"require-dev": {
"swoole/ide-helper": "4.5.5",
Expand Down
Loading