1
0
Fork 0
mirror of synced 2024-06-13 16:24:47 +12:00

feat: ssr dx changes

This commit is contained in:
loks0n 2024-02-20 11:45:11 +00:00
parent 85b0ec7f77
commit fa28496b32
8 changed files with 117 additions and 48 deletions

View file

@ -282,6 +282,11 @@ return [
'description' => 'A target with the same ID already exists.',
'code' => 409,
],
Exception::USER_API_KEY_AND_SESSION_SET => [
'name' => Exception::USER_API_KEY_AND_SESSION_SET,
'description' => 'API key and a session used in the same request. For guest actions, use only API key. For user actions, use only a session.',
'code' => 403,
],
/** Teams */
Exception::TEAM_NOT_FOUND => [

View file

@ -9,8 +9,7 @@ $member = [
'console',
'graphql',
'sessions.write',
'accounts.read',
'accounts.write',
'account',
'teams.read',
'teams.write',
'documents.read',

View file

@ -1,12 +1,6 @@
<?php
return [ // List of publicly visible scopes
'accounts.read' => [
'description' => 'Access to read your active user account',
],
'accounts.write' => [
'description' => 'Access to create, update, and delete your active user account',
],
'sessions.write' => [
'description' => 'Access to create, update, and delete user sessions',
],

View file

@ -342,14 +342,11 @@ App::get('/v1/account/sessions/oauth2/:provider')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn($node) => (!$node['mock'])))) . '.')
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('token', false, new Boolean(true), 'Include token credentials in the final redirect, useful for server-side integrations, or when cookies are not available.', true)
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, mixed $token, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$token = in_array($token, ['true', true], true);
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
@ -388,7 +385,77 @@ App::get('/v1/account/sessions/oauth2/:provider')
$oauth2 = new $className($appId, $appSecret, $callback, [
'success' => $success,
'failure' => $failure,
'token' => $token,
'token' => false,
], $scopes);
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($oauth2->getLoginURL());
});
App::get('/v1/account/tokens/oauth2/:provider')
->desc('Create OAuth2 token')
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
->label('scope', 'sessions.write')
->label('sdk.auth', [])
->label('sdk.hideServer', true)
->label('sdk.namespace', 'account')
->label('sdk.method', 'createOAuth2Token')
->label('sdk.description', '/docs/references/account/create-token-oauth2.md')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn($node) => (!$node['mock'])))) . '.')
->param('success', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('failure', '', fn($clients) => new Host($clients), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
->inject('request')
->inject('response')
->inject('project')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
}
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
$appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag']));
}
if (empty($appId) || empty($appSecret)) {
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please configure the provider app ID and app secret key from your ' . APP_NAME . ' console to continue.');
}
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
if (!\class_exists($className)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
if (empty($success)) {
$success = $protocol . '://' . $request->getHostname() . $oauthDefaultSuccess;
}
if (empty($failure)) {
$failure = $protocol . '://' . $request->getHostname() . $oauthDefaultFailure;
}
$oauth2 = new $className($appId, $appSecret, $callback, [
'success' => $success,
'failure' => $failure,
'token' => true,
], $scopes);
$response
@ -898,7 +965,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
App::get('/v1/account/identities')
->desc('List Identities')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listIdentities')
@ -954,7 +1021,7 @@ App::get('/v1/account/identities')
App::delete('/v1/account/identities/:identityId')
->desc('Delete identity')
->groups(['api', 'account'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].identities.[identityId].delete')
->label('audits.event', 'identity.delete')
->label('audits.resource', 'identity/{request.$identityId}')
@ -1940,7 +2007,7 @@ App::post('/v1/account/sessions/anonymous')
App::post('/v1/account/jwt')
->desc('Create JWT')
->groups(['api', 'account', 'auth'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('auth.type', 'jwt')
->label('sdk.auth', [])
->label('sdk.namespace', 'account')
@ -1987,7 +2054,7 @@ App::post('/v1/account/jwt')
App::get('/v1/account')
->desc('Get account')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'get')
@ -2010,7 +2077,7 @@ App::get('/v1/account')
App::get('/v1/account/prefs')
->desc('Get account preferences')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
@ -2032,7 +2099,7 @@ App::get('/v1/account/prefs')
App::get('/v1/account/sessions')
->desc('List sessions')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listSessions')
@ -2067,7 +2134,7 @@ App::get('/v1/account/sessions')
App::get('/v1/account/logs')
->desc('List logs')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listLogs')
@ -2132,7 +2199,7 @@ App::get('/v1/account/logs')
App::get('/v1/account/sessions/:sessionId')
->desc('Get session')
->groups(['api', 'account'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSession')
@ -2174,7 +2241,7 @@ App::patch('/v1/account/name')
->desc('Update name')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.name')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@ -2207,7 +2274,7 @@ App::patch('/v1/account/password')
->desc('Update password')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.password')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -2276,7 +2343,7 @@ App::patch('/v1/account/email')
->desc('Update email')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.email')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@ -2368,7 +2435,7 @@ App::patch('/v1/account/phone')
->desc('Update phone')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.phone')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@ -2449,7 +2516,7 @@ App::patch('/v1/account/prefs')
->desc('Update preferences')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.prefs')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@ -2482,7 +2549,7 @@ App::patch('/v1/account/status')
->desc('Update status')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.status')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
@ -2524,7 +2591,7 @@ App::patch('/v1/account/status')
App::delete('/v1/account/sessions/:sessionId')
->desc('Delete session')
->groups(['api', 'account'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
@ -2604,7 +2671,7 @@ App::delete('/v1/account/sessions/:sessionId')
App::patch('/v1/account/sessions/:sessionId')
->desc('Update (or renew) a session')
->groups(['api', 'account'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[sessionId].update')
->label('audits.event', 'session.update')
->label('audits.resource', 'user/{response.userId}')
@ -2680,7 +2747,7 @@ App::patch('/v1/account/sessions/:sessionId')
App::delete('/v1/account/sessions')
->desc('Delete sessions')
->groups(['api', 'account'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
@ -3007,7 +3074,7 @@ App::put('/v1/account/recovery')
App::post('/v1/account/verification')
->desc('Create email verification')
->groups(['api', 'account'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
->label('audits.resource', 'user/{response.userId}')
@ -3227,7 +3294,7 @@ App::put('/v1/account/verification')
App::post('/v1/account/verification/phone')
->desc('Create phone verification')
->groups(['api', 'account', 'auth'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('auth.type', 'phone')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
@ -3398,7 +3465,7 @@ App::patch('/v1/account/mfa')
->desc('Update MFA')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3431,7 +3498,7 @@ App::patch('/v1/account/mfa')
App::get('/v1/account/mfa/factors')
->desc('List Factors')
->groups(['api', 'account', 'mfa'])
->label('scope', 'accounts.read')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'listFactors')
@ -3458,7 +3525,7 @@ App::post('/v1/account/mfa/:type')
->desc('Add Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3517,7 +3584,7 @@ App::put('/v1/account/mfa/:type')
->desc('Verify Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].update.mfa')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3573,7 +3640,7 @@ App::delete('/v1/account/mfa/:type')
->desc('Delete Authenticator')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete.mfa')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
@ -3622,7 +3689,7 @@ App::delete('/v1/account/mfa/:type')
App::post('/v1/account/mfa/challenge')
->desc('Create 2FA Challenge')
->groups(['api', 'account', 'mfa'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].challenges.[challengeId].create')
->label('audits.event', 'challenge.create')
->label('audits.resource', 'user/{response.userId}')
@ -3715,7 +3782,7 @@ App::post('/v1/account/mfa/challenge')
App::put('/v1/account/mfa/challenge')
->desc('Create MFA Challenge (confirmation)')
->groups(['api', 'account', 'mfa'])
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('event', 'users.[userId].sessions.[tokenId].create')
->label('audits.event', 'challenges.update')
->label('audits.resource', 'user/{response.userId}')
@ -3781,7 +3848,7 @@ App::delete('/v1/account')
->desc('Delete account')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete')
->label('scope', 'accounts.write')
->label('scope', 'account')
->label('audits.event', 'user.delete')
->label('audits.resource', 'user/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])

View file

@ -194,14 +194,14 @@ App::init()
$authKey = $request->getHeader('x-appwrite-key', '');
if (!empty($authKey)) { // API Key authentication
// Do not allow API key and session to be set at the same time
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if given key match project API keys
$key = $project->find('secret', $authKey, '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()) {
if ($key) {
$user = new Document([
'$id' => '',
'status' => true,

View file

@ -0,0 +1,5 @@
Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.
If authentication succeeds, `userId` and `secret` of a token will be appended to the success URL as query parameters. These can be used to create a new session using the [Create session](https://appwrite.io/docs/references/cloud/client-web/account#createSession) endpoint.
A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits).

View file

@ -96,6 +96,7 @@ class Exception extends \Exception
public const USER_PHONE_ALREADY_VERIFIED = 'user_phone_already_verified';
public const USER_TARGET_NOT_FOUND = 'user_target_not_found';
public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
public const USER_API_KEY_AND_SESSION_SET = 'user_key_and_session_set';
/** Teams */
public const TEAM_NOT_FOUND = 'team_not_found';

View file

@ -84,8 +84,6 @@ trait ProjectCustom
'rules.read',
'rules.write',
'sessions.write',
'accounts.write',
'accounts.read',
'targets.read',
'targets.write',
'providers.read',