From f433db17d0a879129efe0b5227f84adf89a11f79 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 16 Feb 2020 13:41:03 +0200 Subject: [PATCH] Updated naming from OAuth to OAuth2 --- CONTRIBUTING.md | 5 +- app/config/collections.php | 18 +- app/controllers/api/account.php | 108 +-- app/controllers/api/auth.php | 822 ------------------ app/controllers/api/projects.php | 18 +- app/controllers/api/users.php | 34 +- app/controllers/mock.php | 34 +- app/init.php | 2 +- app/sdks/dart/lib/services/account.dart | 4 +- .../examples/account/create-o-auth2session.md | 9 + app/sdks/javascript/src/sdk.js | 6 +- app/sdks/javascript/src/sdk.min.js | 4 +- app/views/console/index.phtml | 2 +- app/views/console/users/index.phtml | 32 +- .../account/create-session-oauth.md | 1 - .../account/create-session-oauth2.md | 1 + ...uth-provider.md => add-oauth2-provider.md} | 16 +- public/dist/scripts/app-all.js | 8 +- public/dist/scripts/app-dep.js | 8 +- public/images/{oauth => oauth2}/amazon.png | Bin public/images/{oauth => oauth2}/apple.png | Bin public/images/{oauth => oauth2}/bitbucket.png | Bin public/images/{oauth => oauth2}/bitly.png | Bin public/images/{oauth => oauth2}/discord.png | Bin public/images/{oauth => oauth2}/dropbox.png | Bin public/images/{oauth => oauth2}/facebook.png | Bin public/images/{oauth => oauth2}/github.png | Bin public/images/{oauth => oauth2}/gitlab.png | Bin public/images/{oauth => oauth2}/google.png | Bin public/images/{oauth => oauth2}/linkedin.png | Bin public/images/{oauth => oauth2}/microsoft.png | Bin public/images/{oauth => oauth2}/paypal.png | Bin .../images/{oauth => oauth2}/salesforce.png | Bin public/images/{oauth => oauth2}/slack.png | Bin public/images/{oauth => oauth2}/spotify.png | Bin public/images/{oauth => oauth2}/twitch.png | Bin public/images/{oauth => oauth2}/twitter.png | Bin public/images/{oauth => oauth2}/vk.png | Bin public/images/{oauth => oauth2}/yahoo.png | Bin public/images/{oauth => oauth2}/yandex.png | Bin public/scripts/dependencies/appwrite.js | 16 +- src/Auth/{OAuth.php => OAuth2.php} | 10 +- src/Auth/{OAuth => OAuth2}/Amazon.php | 7 +- src/Auth/{OAuth => OAuth2}/Apple.php | 6 +- src/Auth/{OAuth => OAuth2}/Bitbucket.php | 6 +- src/Auth/{OAuth => OAuth2}/Bitly.php | 8 +- src/Auth/{OAuth => OAuth2}/Discord.php | 6 +- src/Auth/{OAuth => OAuth2}/Dropbox.php | 7 +- src/Auth/{OAuth => OAuth2}/Facebook.php | 6 +- src/Auth/{OAuth => OAuth2}/GitHub.php | 6 +- src/Auth/{OAuth => OAuth2}/Gitlab.php | 6 +- src/Auth/{OAuth => OAuth2}/Google.php | 7 +- src/Auth/{OAuth => OAuth2}/LinkedIn.php | 6 +- src/Auth/{OAuth => OAuth2}/Microsoft.php | 7 +- src/Auth/{OAuth => OAuth2}/Mock.php | 12 +- src/Auth/{OAuth => OAuth2}/Paypal.php | 6 +- src/Auth/{OAuth => OAuth2}/Salesforce.php | 6 +- src/Auth/{OAuth => OAuth2}/Slack.php | 6 +- src/Auth/{OAuth => OAuth2}/Spotify.php | 6 +- src/Auth/{OAuth => OAuth2}/Twitch.php | 6 +- src/Auth/{OAuth => OAuth2}/Twitter.php | 0 src/Auth/{OAuth => OAuth2}/Vk.php | 6 +- src/Auth/{OAuth => OAuth2}/Yahoo.php | 6 +- src/Auth/{OAuth => OAuth2}/Yandex.php | 6 +- tests/e2e/Services/Account/AccountBase.php | 2 +- .../Account/AccountCustomClientTest.php | 10 +- 66 files changed, 250 insertions(+), 1058 deletions(-) delete mode 100644 app/controllers/api/auth.php create mode 100644 app/sdks/javascript/docs/examples/account/create-o-auth2session.md delete mode 100644 docs/references/account/create-session-oauth.md create mode 100644 docs/references/account/create-session-oauth2.md rename docs/tutorials/{add-oauth-provider.md => add-oauth2-provider.md} (62%) rename public/images/{oauth => oauth2}/amazon.png (100%) rename public/images/{oauth => oauth2}/apple.png (100%) rename public/images/{oauth => oauth2}/bitbucket.png (100%) rename public/images/{oauth => oauth2}/bitly.png (100%) rename public/images/{oauth => oauth2}/discord.png (100%) rename public/images/{oauth => oauth2}/dropbox.png (100%) rename public/images/{oauth => oauth2}/facebook.png (100%) rename public/images/{oauth => oauth2}/github.png (100%) rename public/images/{oauth => oauth2}/gitlab.png (100%) rename public/images/{oauth => oauth2}/google.png (100%) rename public/images/{oauth => oauth2}/linkedin.png (100%) rename public/images/{oauth => oauth2}/microsoft.png (100%) rename public/images/{oauth => oauth2}/paypal.png (100%) rename public/images/{oauth => oauth2}/salesforce.png (100%) rename public/images/{oauth => oauth2}/slack.png (100%) rename public/images/{oauth => oauth2}/spotify.png (100%) rename public/images/{oauth => oauth2}/twitch.png (100%) rename public/images/{oauth => oauth2}/twitter.png (100%) rename public/images/{oauth => oauth2}/vk.png (100%) rename public/images/{oauth => oauth2}/yahoo.png (100%) rename public/images/{oauth => oauth2}/yandex.png (100%) rename src/Auth/{OAuth.php => OAuth2.php} (94%) rename src/Auth/{OAuth => OAuth2}/Amazon.php (97%) rename src/Auth/{OAuth => OAuth2}/Apple.php (97%) rename src/Auth/{OAuth => OAuth2}/Bitbucket.php (97%) rename src/Auth/{OAuth => OAuth2}/Bitly.php (97%) rename src/Auth/{OAuth => OAuth2}/Discord.php (97%) rename src/Auth/{OAuth => OAuth2}/Dropbox.php (97%) rename src/Auth/{OAuth => OAuth2}/Facebook.php (97%) rename src/Auth/{OAuth => OAuth2}/GitHub.php (97%) rename src/Auth/{OAuth => OAuth2}/Gitlab.php (97%) rename src/Auth/{OAuth => OAuth2}/Google.php (97%) rename src/Auth/{OAuth => OAuth2}/LinkedIn.php (98%) rename src/Auth/{OAuth => OAuth2}/Microsoft.php (97%) rename src/Auth/{OAuth => OAuth2}/Mock.php (92%) rename src/Auth/{OAuth => OAuth2}/Paypal.php (98%) rename src/Auth/{OAuth => OAuth2}/Salesforce.php (97%) rename src/Auth/{OAuth => OAuth2}/Slack.php (97%) rename src/Auth/{OAuth => OAuth2}/Spotify.php (97%) rename src/Auth/{OAuth => OAuth2}/Twitch.php (97%) rename src/Auth/{OAuth => OAuth2}/Twitter.php (100%) rename src/Auth/{OAuth => OAuth2}/Vk.php (98%) rename src/Auth/{OAuth => OAuth2}/Yahoo.php (97%) rename src/Auth/{OAuth => OAuth2}/Yandex.php (97%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b9dd6fcc..f5c97fbf8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -169,5 +169,6 @@ docker exec appwrite /bin/bash -c '/usr/share/nginx/html/vendor/bin/phpunit' From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials: -* [Adding Support for a New OAuth Provider](./docs/tutorials/add-oauth-provider.md) -* [Appwrite Environment Variables](./docs/tutorials/add-oauth-provider.md) +* [Adding Support for a New OAuth2 Provider](./docs/tutorials/add-oauth2-provider.md) +* [Appwrite Environment Variables](./docs/tutorials/environment-variables.md) +* [Running in Production](./docs/tutorials/running-in-production.md) diff --git a/app/config/collections.php b/app/config/collections.php index f671cb747..63dae6a08 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1078,7 +1078,7 @@ $collections = [ ]; /* - * Add enabled OAuth providers to default data rules + * Add enabled OAuth2 providers to default data rules */ foreach ($providers as $key => $provider) { if (!$provider['enabled']) { @@ -1087,8 +1087,8 @@ foreach ($providers as $key => $provider) { $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth '.ucfirst($key).' ID', - 'key' => 'usersOauth'.ucfirst($key).'Appid', + 'label' => 'OAuth2 '.ucfirst($key).' ID', + 'key' => 'usersOauth2'.ucfirst($key).'Appid', 'type' => 'text', 'default' => '', 'required' => false, @@ -1097,8 +1097,8 @@ foreach ($providers as $key => $provider) { $collections[Database::SYSTEM_COLLECTION_PROJECTS]['rules'][] = [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth '.ucfirst($key).' Secret', - 'key' => 'usersOauth'.ucfirst($key).'Secret', + 'label' => 'OAuth2 '.ucfirst($key).' Secret', + 'key' => 'usersOauth2'.ucfirst($key).'Secret', 'type' => 'text', 'default' => '', 'required' => false, @@ -1107,8 +1107,8 @@ foreach ($providers as $key => $provider) { $collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth '.ucfirst($key).' ID', - 'key' => 'oauth'.ucfirst($key), + 'label' => 'OAuth2 '.ucfirst($key).' ID', + 'key' => 'oauth2'.ucfirst($key), 'type' => 'text', 'default' => '', 'required' => false, @@ -1117,8 +1117,8 @@ foreach ($providers as $key => $provider) { $collections[Database::SYSTEM_COLLECTION_USERS]['rules'][] = [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'OAuth '.ucfirst($key).' Access Token', - 'key' => 'oauth'.ucfirst($key).'AccessToken', + 'label' => 'OAuth2 '.ucfirst($key).' Access Token', + 'key' => 'oauth2'.ucfirst($key).'AccessToken', 'type' => 'text', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a49a1b18c..bbd4260c8 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -27,16 +27,16 @@ use OpenSSL\OpenSSL; include_once __DIR__ . '/../shared/api.php'; -$oauthKeys = []; +$oauth2Keys = []; -$utopia->init(function() use ($providers, &$oauthKeys) { +$utopia->init(function() use ($providers, &$oauth2Keys) { foreach ($providers as $key => $provider) { if (!$provider['enabled']) { continue; } - $oauthKeys[] = 'oauth'.ucfirst($key); - $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken'; + $oauth2Keys[] = 'oauth2'.ucfirst($key); + $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } }); @@ -54,7 +54,7 @@ $utopia->post('/v1/account') ->param('password', '', function () { return new Password(); }, 'User password.') ->param('name', '', function () { return new Text(100); }, 'User name.', true) ->action( - function ($email, $password, $name) use ($register, $request, $response, $audit, $projectDB, $project, $webhook, $oauthKeys) { + function ($email, $password, $name) use ($register, $request, $response, $audit, $projectDB, $project, $webhook, $oauth2Keys) { if ('console' === $project->getUid()) { $whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistIPs = $project->getAttribute('authWhitelistIPs'); @@ -132,7 +132,7 @@ $utopia->post('/v1/account') 'registration', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => Authorization::getRoles()])); } ); @@ -220,27 +220,27 @@ $utopia->post('/v1/account/sessions') } ); -$utopia->get('/v1/account/sessions/oauth/:provider') - ->desc('Create Account Session with OAuth') +$utopia->get('/v1/account/sessions/oauth2/:provider') + ->desc('Create Account Session with OAuth2') ->label('error', __DIR__.'/../../views/general/error.phtml') ->label('scope', 'public') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'createOAuthSession') - ->label('sdk.description', '/docs/references/account/create-session-oauth.md') + ->label('sdk.method', 'createOAuth2Session') + ->label('sdk.description', '/docs/references/account/create-session-oauth2.md') ->label('sdk.response.code', 301) ->label('sdk.response.type', 'text/html') ->label('sdk.location', true) ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth Provider. Currently, supported providers are: ' . implode(', ', array_keys(array_filter($providers, function($node) {return (!$node['mock']);}))).'.') + ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth2 Provider. Currently, supported providers are: ' . implode(', ', array_keys(array_filter($providers, function($node) {return (!$node['mock']);}))).'.') ->param('success', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.') ->param('failure', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.') ->action( function ($provider, $success, $failure) use ($response, $request, $project) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth/callback/'.$provider.'/'.$project->getUid(); - $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'); + $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getUid(); + $appId = $project->getAttribute('usersOauth2'.ucfirst($provider).'Appid', ''); + $appSecret = $project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'); $appSecret = json_decode($appSecret, true); @@ -253,53 +253,53 @@ $utopia->get('/v1/account/sessions/oauth/:provider') throw new Exception('Provider is undefined, configure provider app ID and app secret key to continue', 412); } - $classname = 'Auth\\OAuth\\'.ucfirst($provider); + $classname = 'Auth\\OAuth2\\'.ucfirst($provider); if (!class_exists($classname)) { throw new Exception('Provider is not supported', 501); } - $oauth = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure]); + $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure]); - $response->redirect($oauth->getLoginURL()); + $response->redirect($oauth2->getLoginURL()); } ); -$utopia->get('/v1/account/sessions/oauth/callback/:provider/:projectId') - ->desc('OAuth Callback') +$utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId') + ->desc('OAuth2 Callback') ->label('error', __DIR__.'/../../views/general/error.phtml') ->label('scope', 'public') ->label('docs', false) ->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID.') - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth provider.') - ->param('code', '', function () { return new Text(1024); }, 'OAuth code.') + ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth2 provider.') + ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) ->action( function ($projectId, $provider, $code, $state) use ($response, $request, $domain) { - $response->redirect($request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/v1/account/sessions/oauth/'.$provider.'/redirect?' + $response->redirect($request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); } ); -$utopia->get('/v1/account/sessions/oauth/:provider/redirect') - ->desc('OAuth Redirect') +$utopia->get('/v1/account/sessions/oauth2/:provider/redirect') + ->desc('OAuth2 Redirect') ->label('error', __DIR__.'/../../views/general/error.phtml') ->label('webhook', 'account.sessions.create') ->label('scope', 'public') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') ->label('docs', false) - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth provider.') - ->param('code', '', function () { return new Text(1024); }, 'OAuth code.') - ->param('state', '', function () { return new Text(2048); }, 'OAuth state params.', true) + ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth2 provider.') + ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') + ->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true) ->action( function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth/callback/'.$provider.'/'.$project->getUid(); + $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getUid(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $validateURL = new URL(); - $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'); + $appId = $project->getAttribute('usersOauth2'.ucfirst($provider).'Appid', ''); + $appSecret = $project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'); $appSecret = json_decode($appSecret, true); @@ -308,19 +308,19 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag'])); } - $classname = 'Auth\\OAuth\\'.ucfirst($provider); + $classname = 'Auth\\OAuth2\\'.ucfirst($provider); if (!class_exists($classname)) { throw new Exception('Provider is not supported', 501); } - $oauth = new $classname($appId, $appSecret, $callback); + $oauth2 = new $classname($appId, $appSecret, $callback); if (!empty($state)) { try { - $state = array_merge($defaultState, $oauth->parseState($state)); + $state = array_merge($defaultState, $oauth2->parseState($state)); } catch (\Exception $exception) { - throw new Exception('Failed to parse login state params as passed from OAuth provider'); + throw new Exception('Failed to parse login state params as passed from OAuth2 provider'); } } else { $state = $defaultState; @@ -334,7 +334,7 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') throw new Exception('Invalid redirect URL for failure login', 400); } $state['failure'] = null; - $accessToken = $oauth->getAccessToken($code); + $accessToken = $oauth2->getAccessToken($code); if (empty($accessToken)) { if (!empty($state['failure'])) { @@ -344,14 +344,14 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') throw new Exception('Failed to obtain access token'); } - $oauthID = $oauth->getUserID($accessToken); + $oauth2ID = $oauth2->getUserID($accessToken); - if (empty($oauthID)) { + if (empty($oauth2ID)) { if (!empty($state['failure'])) { $response->redirect($state['failure'], 301, 0); } - throw new Exception('Missing ID from OAuth provider', 400); + throw new Exception('Missing ID from OAuth2 provider', 400); } $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); @@ -365,13 +365,13 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') 'first' => true, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'oauth'.ucfirst($provider).'='.$oauthID, + 'oauth2'.ucfirst($provider).'='.$oauth2ID, ], ]) : $user; - if (empty($user)) { // No user logged in or with oauth provider ID, create new one or connect with account with same email - $name = $oauth->getUserName($accessToken); - $email = $oauth->getUserEmail($accessToken); + if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email + $name = $oauth2->getUserName($accessToken); + $email = $oauth2->getUserEmail($accessToken); $user = $projectDB->getCollection([ // Get user by provider email address 'limit' => 1, @@ -390,7 +390,7 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']], 'email' => $email, 'emailVerification' => true, - 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth provider + 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), 'password-update' => time(), 'registration' => time(), @@ -406,7 +406,7 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') } } - // Create session token, verify user account and update OAuth ID and Access Token + // Create session token, verify user account and update OAuth2 ID and Access Token $secret = Auth::tokenGenerator(); $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; @@ -421,8 +421,8 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect') ]); $user - ->setAttribute('oauth'.ucfirst($provider), $oauthID) - ->setAttribute('oauth'.ucfirst($provider).'AccessToken', $accessToken) + ->setAttribute('oauth2'.ucfirst($provider), $oauth2ID) + ->setAttribute('oauth2'.ucfirst($provider).'AccessToken', $accessToken) ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND) ; @@ -459,7 +459,7 @@ $utopia->get('/v1/account') ->label('sdk.description', '/docs/references/account/get.md') ->label('sdk.response', ['200' => 'user']) ->action( - function () use ($response, &$user, $oauthKeys) { + function () use ($response, &$user, $oauth2Keys) { $response->json(array_merge($user->getArrayCopy(array_merge( [ '$uid', @@ -467,7 +467,7 @@ $utopia->get('/v1/account') 'registration', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => Authorization::getRoles()])); } ); @@ -634,7 +634,7 @@ $utopia->patch('/v1/account/name') ->label('sdk.description', '/docs/references/account/update-name.md') ->param('name', '', function () { return new Text(100); }, 'User name.') ->action( - function ($name) use ($response, $user, $projectDB, $audit, $oauthKeys) { + function ($name) use ($response, $user, $projectDB, $audit, $oauth2Keys) { $user = $projectDB->updateDocument(array_merge($user->getArrayCopy(), [ 'name' => $name, ])); @@ -656,7 +656,7 @@ $utopia->patch('/v1/account/name') 'registration', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => Authorization::getRoles()])); } ); @@ -672,7 +672,7 @@ $utopia->patch('/v1/account/password') ->param('password', '', function () { return new Password(); }, 'New user password.') ->param('old-password', '', function () { return new Password(); }, 'Old user password.') ->action( - function ($password, $oldPassword) use ($response, $user, $projectDB, $audit, $oauthKeys) { + function ($password, $oldPassword) use ($response, $user, $projectDB, $audit, $oauth2Keys) { if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } @@ -698,7 +698,7 @@ $utopia->patch('/v1/account/password') 'registration', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => Authorization::getRoles()])); } ); @@ -714,7 +714,7 @@ $utopia->patch('/v1/account/email') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password.') ->action( - function ($email, $password) use ($response, $user, $projectDB, $audit, $oauthKeys) { + function ($email, $password) use ($response, $user, $projectDB, $audit, $oauth2Keys) { if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } @@ -756,7 +756,7 @@ $utopia->patch('/v1/account/email') 'registration', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => Authorization::getRoles()])); } ); diff --git a/app/controllers/api/auth.php b/app/controllers/api/auth.php deleted file mode 100644 index 09099261e..000000000 --- a/app/controllers/api/auth.php +++ /dev/null @@ -1,822 +0,0 @@ -post('/v1/auth/register') - ->desc('Register') - ->label('webhook', 'auth.register') - ->label('scope', 'auth') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'register') - ->label('sdk.description', '/docs/references/auth/register.md') - ->label('sdk.cookies', true) - ->label('abuse-limit', 10) - ->param('email', '', function () { return new Email(); }, 'Account email') - ->param('password', '', function () { return new Password(); }, 'User password') - ->param('confirm', '', function () use ($clients) { return new Host($clients); }, 'Confirmation URL to redirect user after confirm token has been sent to user email') // TODO add our own built-in confirm page - ->param('success', null, function () use ($clients) { return new Host($clients); }, 'Redirect when registration succeed', true) - ->param('failure', null, function () use ($clients) { return new Host($clients); }, 'Redirect when registration failed', true) - ->param('name', '', function () { return new Text(100); }, 'User name', true) - ->action( - function ($email, $password, $confirm, $success, $failure, $name) use ($request, $response, $register, $audit, $projectDB, $project, $webhook) { - if ('console' === $project->getUid()) { - $whitlistEmails = $project->getAttribute('authWhitelistEmails'); - $whitlistIPs = $project->getAttribute('authWhitelistIPs'); - $whitlistDomains = $project->getAttribute('authWhitelistDomains'); - - if (!empty($whitlistEmails) && !in_array($email, $whitlistEmails)) { - throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); - } - - if (!empty($whitlistIPs) && !in_array($request->getIP(), $whitlistIPs)) { - throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); - } - - if (!empty($whitlistDomains) && !in_array(substr(strrchr($email, '@'), 1), $whitlistDomains)) { - throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401); - } - } - - $profile = $projectDB->getCollection([ // Get user by email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!empty($profile)) { - if ($failure) { - $response->redirect($failure); // .'?message=User already registered' - - return; - } - - throw new Exception('User already registered', 400); - } - - $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $confirmSecret = Auth::tokenGenerator(); - $loginSecret = Auth::tokenGenerator(); - - Authorization::disable(); - - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'], - ], - 'email' => $email, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => Auth::passwordHash($password), - 'password-update' => time(), - 'registration' => time(), - 'confirm' => false, - 'reset' => false, - 'name' => $name, - ]); - - Authorization::enable(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - Authorization::setRole('user:'.$user->getUid()); - - $user - ->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]], - 'type' => Auth::TOKEN_TYPE_VERIFICATION, - 'secret' => Auth::hash($confirmSecret), // On way hash encryption to protect DB leak - 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND) - ->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($loginSecret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND) - ; - - $user = $projectDB->createDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving tokens to DB', 500); - } - - // Send email address confirmation email - - $confirm = Template::parseURL($confirm); - $confirm['query'] = Template::mergeQuery(((isset($confirm['query'])) ? $confirm['query'] : ''), ['userId' => $user->getUid(), 'token' => $confirmSecret]); - $confirm = Template::unParseURL($confirm); - - $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body')); - $body - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $name) - ->setParam('{{redirect}}', $confirm) - ; - - $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ - - $mail->addAddress($email, $name); - - $mail->Subject = Locale::getText('account.emails.verification.title'); - $mail->Body = $body->render(); - $mail->AltBody = strip_tags($body->render()); - - try { - $mail->send(); - } catch (\Exception $error) { - // if($failure) { - // $response->redirect($failure); - // return; - // } - - // throw new Exception('Problem sending mail: ' . $error->getMessage(), 500); - } - - $webhook - ->setParam('payload', [ - 'name' => $name, - 'email' => $email, - ]) - ; - - $audit - ->setParam('userId', $user->getUid()) - ->setParam('event', 'auth.register') - ; - - $response - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $loginSecret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null); - - if ($success) { - $response->redirect($success); - } - - $response->json(array('result' => 'success')); - } - ); - -$utopia->post('/v1/auth/register/confirm') - ->desc('Confirmation') - ->label('webhook', 'auth.confirm') - ->label('scope', 'public') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'confirm') - ->label('sdk.description', '/docs/references/auth/confirm.md') - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{param-userId}') - ->param('userId', '', function () { return new UID(); }, 'User unique ID') - ->param('token', '', function () { return new Text(256); }, 'Confirmation secret token') - ->action( - function ($userId, $token) use ($response, $request, $projectDB, $audit) { - $profile = $projectDB->getCollection([ // Get user by email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$uid='.$userId, - ], - ]); - - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } - - $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $token); - - if (!$token) { - throw new Exception('Confirmation token is not valid', 401); - } - - $profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [ - 'status' => Auth::USER_STATUS_ACTIVATED, - 'confirm' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - if (!$projectDB->deleteDocument($token)) { - throw new Exception('Failed to remove token from DB', 500); - } - - $audit->setParam('event', 'auth.confirm'); - - $response->json(array('result' => 'success')); - } - ); - -$utopia->post('/v1/auth/register/confirm/resend') - ->desc('Resend Confirmation') - ->label('scope', 'account') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'confirmResend') - ->label('sdk.description', '/docs/references/auth/confirm-resend.md') - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{param-userId}') - ->param('confirm', '', function () use ($clients) { return new Host($clients); }, 'Confirmation URL to redirect user to your app after confirm token has been sent to user email.') - ->action( - function ($confirm) use ($response, $request, $projectDB, $user, $register, $project) { - if ($user->getAttribute('confirm', false)) { - throw new Exception('Email address is already confirmed', 400); - } - - $secret = Auth::tokenGenerator(); - - $user->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getUid()], 'write' => ['user:'.$user->getUid()]], - 'type' => Auth::TOKEN_TYPE_VERIFICATION, - 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak - 'expire' => time() + Auth::TOKEN_EXPIRATION_CONFIRM, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND); - - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $confirm = Template::parseURL($confirm); - $confirm['query'] = Template::mergeQuery(((isset($confirm['query'])) ? $confirm['query'] : ''), ['userId' => $user->getUid(), 'token' => $secret]); - $confirm = Template::unParseURL($confirm); - - $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body')); - $body - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $user->getAttribute('name')) - ->setParam('{{redirect}}', $confirm) - ; - - $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ - - $mail->addAddress($user->getAttribute('email'), $user->getAttribute('name')); - - $mail->Subject = Locale::getText('account.emails.verification.title'); - $mail->Body = $body->render(); - $mail->AltBody = strip_tags($body->render()); - - try { - $mail->send(); - } catch (\Exception $error) { - //throw new Exception('Problem sending mail: ' . $error->getMessage(), 500); - } - - $response->json(array('result' => 'success')); - } - ); - -$utopia->post('/v1/auth/login') - ->desc('Login') - ->label('webhook', 'auth.login') - ->label('scope', 'auth') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'login') - ->label('sdk.description', '/docs/references/auth/login.md') - ->label('sdk.cookies', true) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},email:{param-email}') - ->param('email', '', function () { return new Email(); }, 'User account email address') - ->param('password', '', function () { return new Password(); }, 'User account password') - ->param('success', null, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.', true) - ->param('failure', null, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.', true) - ->action( - function ($email, $password, $success, $failure) use ($response, $request, $projectDB, $audit, $webhook) { - $profile = $projectDB->getCollection([ // Get user by email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!$profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { - $audit - //->setParam('userId', $profile->getUid()) - ->setParam('event', 'auth.failure') - ; - - if ($failure) { - $response->redirect($failure); - - return; - } - - throw new Exception('Invalid credentials', 401); // Wrong password or username - } - - $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $secret = Auth::tokenGenerator(); - - $profile->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$profile->getUid()], 'write' => ['user:'.$profile->getUid()]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND); - - Authorization::setRole('user:'.$profile->getUid()); - - $profile = $projectDB->updateDocument($profile->getArrayCopy()); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - $webhook - ->setParam('payload', [ - 'name' => $profile->getAttribute('name', ''), - 'email' => $profile->getAttribute('email', ''), - ]) - ; - - $audit - ->setParam('userId', $profile->getUid()) - ->setParam('event', 'auth.login') - ; - - $response - ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null); - - if ($success) { - $response->redirect($success); - } - - $response - ->json(array('result' => 'success')); - } - ); - -$utopia->get('/v1/auth/login/oauth/:provider') - ->desc('Login with OAuth') - ->label('error', __DIR__.'/../views/general/error.phtml') - ->label('scope', 'auth') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'oauth') - ->label('sdk.description', '/docs/references/auth/login-oauth.md') - ->label('sdk.location', true) - ->label('sdk.cookies', true) - ->label('abuse-limit', 50) - ->label('abuse-key', 'ip:{ip}') - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth Provider. Currently, supported providers are: ' . implode(', ', array_keys($providers))) - ->param('success', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt.') - ->param('failure', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt.') - ->param('scopes', [], function () { return new ArrayList(new Text(128)); }, 'An array of string where each can be max 128 chars', true) - ->action( - function ($provider, $success, $failure, $scopes) use ($response, $request, $project) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/auth/login/oauth/callback/'.$provider.'/'.$project->getUid(); - $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'); - - $appSecret = json_decode($appSecret, true); - - if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $request->getServer('_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('Provider is undefined, configure provider app ID and app secret key to continue', 412); - } - - $classname = 'Auth\\OAuth\\'.ucfirst($provider); - - if (!class_exists($classname)) { - throw new Exception('Provider is not supported', 501); - } - - $oauth = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes); - - $response->redirect($oauth->getLoginURL()); - } - ); - -$utopia->get('/v1/auth/login/oauth/callback/:provider/:projectId') - ->desc('OAuth Callback') - ->label('error', __DIR__.'/../../views/general/error.phtml') - ->label('scope', 'auth') - ->label('docs', false) - ->param('projectId', '', function () { return new Text(1024); }, 'Project unique ID') - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth provider') - ->param('code', '', function () { return new Text(1024); }, 'OAuth code') - ->param('state', '', function () { return new Text(2048); }, 'Login state params', true) - ->action( - function ($projectId, $provider, $code, $state) use ($response, $request, $domain) { - $response->redirect($request->getServer('REQUEST_SCHEME', 'https').'://'.$domain.'/v1/auth/login/oauth/'.$provider.'/redirect?' - .http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); - } - ); - -$utopia->get('/v1/auth/login/oauth/:provider/redirect') - ->desc('OAuth Redirect') - ->label('error', __DIR__.'/../../views/general/error.phtml') - ->label('webhook', 'auth.oauth') - ->label('scope', 'auth') - ->label('abuse-limit', 50) - ->label('abuse-key', 'ip:{ip}') - ->label('docs', false) - ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'OAuth provider') - ->param('code', '', function () { return new Text(1024); }, 'OAuth code') - ->param('state', '', function () { return new Text(2048); }, 'OAuth state params', true) - ->action( - function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit) { - $callback = $request->getServer('REQUEST_SCHEME', 'https').'://'.$request->getServer('HTTP_HOST').'/v1/auth/login/oauth/callback/'.$provider.'/'.$project->getUid(); - $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; - $validateURL = new URL(); - - $appId = $project->getAttribute('usersOauth'.ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'); - - $appSecret = json_decode($appSecret, true); - - if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']); - $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, hex2bin($appSecret['iv']), hex2bin($appSecret['tag'])); - } - - $classname = 'Auth\\OAuth\\'.ucfirst($provider); - - if (!class_exists($classname)) { - throw new Exception('Provider is not supported', 501); - } - - $oauth = new $classname($appId, $appSecret, $callback); - - if (!empty($state)) { - try { - $state = array_merge($defaultState, $oauth->parseState($state)); - } catch (\Exception $exception) { - throw new Exception('Failed to parse login state params as passed from OAuth provider'); - } - } else { - $state = $defaultState; - } - - if (!$validateURL->isValid($state['success'])) { - throw new Exception('Invalid redirect URL for success login', 400); - } - - if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { - throw new Exception('Invalid redirect URL for failure login', 400); - } - - $accessToken = $oauth->getAccessToken($code); - - if (empty($accessToken)) { - if (!empty($state['failure'])) { - $response->redirect($state['failure'], 301, 0); - } - - throw new Exception('Failed to obtain access token'); - } - - $oauthID = $oauth->getUserID($accessToken); - - if (empty($oauthID)) { - if (!empty($state['failure'])) { - $response->redirect($state['failure'], 301, 0); - } - - throw new Exception('Missing ID from OAuth provider', 400); - } - - $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - - if ($current) { - $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); - } - - $user = (empty($user->getUid())) ? $projectDB->getCollection([ // Get user by provider id - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'oauth'.ucfirst($provider).'='.$oauthID, - ], - ]) : $user; - - if (empty($user)) { // No user logged in or with oauth provider ID, create new one or connect with account with same email - $name = $oauth->getUserName($accessToken); - $email = $oauth->getUserEmail($accessToken); - - $user = $projectDB->getCollection([ // Get user by provider email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!$user || empty($user->getUid())) { // Last option -> create user alone, generate random password - Authorization::disable(); - - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']], - 'email' => $email, - 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth provider - 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => time(), - 'registration' => time(), - 'confirm' => true, - 'reset' => false, - 'name' => $name, - ]); - - Authorization::enable(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - } - } - - // Create login token, confirm user account and update OAuth ID and Access Token - - $secret = Auth::tokenGenerator(); - $expiry = time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - - $user - ->setAttribute('oauth'.ucfirst($provider), $oauthID) - ->setAttribute('oauth'.ucfirst($provider).'AccessToken', $accessToken) - ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) - ->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user['$uid']], 'write' => ['user:'.$user['$uid']]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND) - ; - - Authorization::setRole('user:'.$user->getUid()); - - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $audit - ->setParam('userId', $user->getUid()) - ->setParam('event', 'auth.oauth.login') - ->setParam('data', ['provider' => $provider]) - ; - - $response - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getUid(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ; - - $response->redirect($state['success']); - } - ); - -$utopia->delete('/v1/auth/logout') - ->desc('Logout Current Session') - ->label('webhook', 'auth.logout') - ->label('scope', 'account') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'logout') - ->label('sdk.description', '/docs/references/auth/logout.md') - ->label('abuse-limit', 100) - ->action( - function () use ($response, $request, $user, $projectDB, $audit, $webhook) { - $token = Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - - if (!$projectDB->deleteDocument($token)) { - throw new Exception('Failed to remove token from DB', 500); - } - - $webhook - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) - ; - - $audit->setParam('event', 'auth.logout'); - - $response - ->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null) - ->json(array('result' => 'success')) - ; - } - ); - -$utopia->delete('/v1/auth/logout/:id') - ->desc('Logout Specific Session') - ->label('scope', 'account') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'logoutBySession') - ->label('sdk.description', '/docs/references/auth/logout-by-session.md') - ->label('abuse-limit', 100) - ->param('id', null, function () { return new UID(); }, 'User specific session unique ID number. if 0 delete all sessions.') - ->action( - function ($id) use ($response, $request, $user, $projectDB, $audit) { - $tokens = $user->getAttribute('tokens', []); - - foreach ($tokens as $token) { /* @var $token Document */ - if (($id == $token->getUid() || ($id == 0)) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) { - if (!$projectDB->deleteDocument($token->getUid())) { - throw new Exception('Failed to remove token from DB', 500); - } - - $audit - ->setParam('event', 'auth.logout') - ->setParam('resource', '/auth/token/'.$token->getUid()) - ; - - if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete cookies - $response->addCookie(Auth::$cookieName, '', time() - 3600, '/', COOKIE_DOMAIN, ('https' == $request->getServer('REQUEST_SCHEME', 'https')), true, null); - } - } - } - - $response->json(array('result' => 'success')); - } - ); - -$utopia->post('/v1/auth/recovery') - ->desc('Password Recovery') - ->label('scope', 'auth') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'recovery') - ->label('sdk.description', '/docs/references/auth/recovery.md') - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},email:{param-email}') - ->param('email', '', function () { return new Email(); }, 'User account email address.') - ->param('reset', '', function () use ($clients) { return new Host($clients); }, 'Reset URL in your app to redirect the user after the reset token has been sent to the user email.') - ->action( - function ($email, $reset) use ($request, $response, $projectDB, $register, $audit, $project) { - $profile = $projectDB->getCollection([ // Get user by email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } - - $secret = Auth::tokenGenerator(); - - $profile->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$profile->getUid()], 'write' => ['user:'.$profile->getUid()]], - 'type' => Auth::TOKEN_TYPE_RECOVERY, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => time() + Auth::TOKEN_EXPIRATION_RECOVERY, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND); - - Authorization::setRole('user:'.$profile->getUid()); - - $profile = $projectDB->updateDocument($profile->getArrayCopy()); - - if (false === $profile) { - throw new Exception('Failed to save user to DB', 500); - } - - $reset = Template::parseURL($reset); - $reset['query'] = Template::mergeQuery(((isset($reset['query'])) ? $reset['query'] : ''), ['userId' => $profile->getUid(), 'token' => $secret]); - $reset = Template::unParseURL($reset); - - $body = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.recovery.body')); - $body - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $profile->getAttribute('name')) - ->setParam('{{redirect}}', $reset) - ; - - $mail = $register->get('smtp'); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */ - - $mail->addAddress($profile->getAttribute('email', ''), $profile->getAttribute('name', '')); - - $mail->Subject = Locale::getText('account.emails.recovery.title'); - $mail->Body = $body->render(); - $mail->AltBody = strip_tags($body->render()); - - try { - $mail->send(); - } catch (\Exception $error) { - //throw new Exception('Problem sending mail: ' . $error->getMessage(), 500); - } - - $audit - ->setParam('userId', $profile->getUid()) - ->setParam('event', 'auth.recovery') - ; - - $response->json(array('result' => 'success')); - } - ); - -$utopia->put('/v1/auth/recovery/reset') - ->desc('Password Reset') - ->label('scope', 'auth') - ->label('sdk.namespace', 'auth') - ->label('sdk.method', 'recoveryReset') - ->label('sdk.description', '/docs/references/auth/recovery-reset.md') - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{param-userId}') - ->param('userId', '', function () { return new UID(); }, 'User account email address.') - ->param('token', '', function () { return new Text(256); }, 'Valid reset token.') - ->param('password-a', '', function () { return new Password(); }, 'New password.') - ->param('password-b', '', function () {return new Password(); }, 'New password again.') - ->action( - function ($userId, $token, $passwordA, $passwordB) use ($response, $projectDB, $audit) { - if ($passwordA !== $passwordB) { - throw new Exception('Passwords must match', 400); - } - - $profile = $projectDB->getCollection([ // Get user by email address - 'limit' => 1, - 'first' => true, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$uid='.$userId, - ], - ]); - - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } - - $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $token); - - if (!$token) { - throw new Exception('Recovery token is not valid', 401); - } - - Authorization::setRole('user:'.$profile->getUid()); - - $profile = $projectDB->updateDocument(array_merge($profile->getArrayCopy(), [ - 'password' => Auth::passwordHash($passwordA), - 'password-update' => time(), - 'confirm' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - if (!$projectDB->deleteDocument($token)) { - throw new Exception('Failed to remove token from DB', 500); - } - - $audit - ->setParam('userId', $profile->getUid()) - ->setParam('event', 'auth.recovery.reset') - ; - - $response->json(array('result' => 'success')); - } - ); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 36cd6081c..626ebb05d 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -103,11 +103,11 @@ $utopia->get('/v1/projects') foreach ($results as $project) { foreach ($providers as $provider => $node) { - $secret = json_decode($project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'), true); + $secret = json_decode($project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'), true); if (!empty($secret) && isset($secret['version'])) { $key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']); - $project->setAttribute('usersOauth'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag']))); + $project->setAttribute('usersOauth2'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag']))); } } } @@ -131,11 +131,11 @@ $utopia->get('/v1/projects/:projectId') } foreach ($providers as $provider => $node) { - $secret = json_decode($project->getAttribute('usersOauth'.ucfirst($provider).'Secret', '{}'), true); + $secret = json_decode($project->getAttribute('usersOauth2'.ucfirst($provider).'Secret', '{}'), true); if (!empty($secret) && isset($secret['version'])) { $key = $request->getServer('_APP_OPENSSL_KEY_V'.$secret['version']); - $project->setAttribute('usersOauth'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag']))); + $project->setAttribute('usersOauth2'.ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, hex2bin($secret['iv']), hex2bin($secret['tag']))); } } @@ -322,11 +322,11 @@ $utopia->patch('/v1/projects/:projectId') } ); -$utopia->patch('/v1/projects/:projectId/oauth') - ->desc('Update Project OAuth') +$utopia->patch('/v1/projects/:projectId/oauth2') + ->desc('Update Project OAuth2') ->label('scope', 'projects.write') ->label('sdk.namespace', 'projects') - ->label('sdk.method', 'updateOAuth') + ->label('sdk.method', 'updateOAuth2') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->param('provider', '', function () use ($providers) { return new WhiteList(array_keys($providers)); }, 'Provider Name', false) ->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true) @@ -351,8 +351,8 @@ $utopia->patch('/v1/projects/:projectId/oauth') ]); $project = $consoleDB->updateDocument(array_merge($project->getArrayCopy(), [ - 'usersOauth'.ucfirst($provider).'Appid' => $appId, - 'usersOauth'.ucfirst($provider).'Secret' => $secret, + 'usersOauth2'.ucfirst($provider).'Appid' => $appId, + 'usersOauth2'.ucfirst($provider).'Secret' => $secret, ])); if (false === $project) { diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 64f694fac..a4091019f 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -62,15 +62,15 @@ $utopia->post('/v1/users') 'name' => $name, ]); - $oauthKeys = []; + $oauth2Keys = []; foreach ($providers as $key => $provider) { if (!$provider['enabled']) { continue; } - $oauthKeys[] = 'oauth'.ucfirst($key); - $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken'; + $oauth2Keys[] = 'oauth2'.ucfirst($key); + $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response @@ -82,7 +82,7 @@ $utopia->post('/v1/users') 'registration', 'emailVerification', 'name', - ], $oauthKeys)), ['roles' => []])); + ], $oauth2Keys)), ['roles' => []])); } ); @@ -111,18 +111,18 @@ $utopia->get('/v1/users') ], ]); - $oauthKeys = []; + $oauth2Keys = []; foreach ($providers as $key => $provider) { if (!$provider['enabled']) { continue; } - $oauthKeys[] = 'oauth'.ucfirst($key); - $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken'; + $oauth2Keys[] = 'oauth2'.ucfirst($key); + $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } - $results = array_map(function ($value) use ($oauthKeys) { /* @var $value \Database\Document */ + $results = array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */ return $value->getArrayCopy(array_merge( [ '$uid', @@ -132,7 +132,7 @@ $utopia->get('/v1/users') 'emailVerification', 'name', ], - $oauthKeys + $oauth2Keys )); }, $results); @@ -156,15 +156,15 @@ $utopia->get('/v1/users/:userId') throw new Exception('User not found', 404); } - $oauthKeys = []; + $oauth2Keys = []; foreach ($providers as $key => $provider) { if (!$provider['enabled']) { continue; } - $oauthKeys[] = 'oauth'.ucfirst($key); - $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken'; + $oauth2Keys[] = 'oauth2'.ucfirst($key); + $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response->json(array_merge($user->getArrayCopy(array_merge( @@ -176,7 +176,7 @@ $utopia->get('/v1/users/:userId') 'emailVerification', 'name', ], - $oauthKeys + $oauth2Keys )), ['roles' => []])); } ); @@ -362,15 +362,15 @@ $utopia->patch('/v1/users/:userId/status') throw new Exception('Failed saving user to DB', 500); } - $oauthKeys = []; + $oauth2Keys = []; foreach ($providers as $key => $provider) { if (!$provider['enabled']) { continue; } - $oauthKeys[] = 'oauth'.ucfirst($key); - $oauthKeys[] = 'oauth'.ucfirst($key).'AccessToken'; + $oauth2Keys[] = 'oauth2'.ucfirst($key); + $oauth2Keys[] = 'oauth2'.ucfirst($key).'AccessToken'; } $response @@ -381,7 +381,7 @@ $utopia->patch('/v1/users/:userId/status') 'registration', 'emailVerification', 'name', - ], $oauthKeys)), ['roles' => []])); + ], $oauth2Keys)), ['roles' => []])); } ); diff --git a/app/controllers/mock.php b/app/controllers/mock.php index ec82fe0be..2f1535b66 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -235,28 +235,28 @@ $utopia->get('/v1/mock/tests/general/empty') } ); -$utopia->get('/v1/mock/tests/general/oauth') - ->desc('Mock an OAuth login route') +$utopia->get('/v1/mock/tests/general/oauth2') + ->desc('Mock an OAuth2 login route') ->label('scope', 'public') ->label('docs', false) - ->param('client_id', '', function () { return new Text(100); }, 'OAuth Client ID.') - ->param('redirect_uri', '', function () { return new Host(['http://localhost']); }, 'OAuth Redirect URI.') // Important to deny an open redirect attack - ->param('scope', '', function () { return new Text(100); }, 'OAuth scope list.') - ->param('state', '', function () { return new Text(1024); }, 'OAuth state.') + ->param('client_id', '', function () { return new Text(100); }, 'OAuth2 Client ID.') + ->param('redirect_uri', '', function () { return new Host(['http://localhost']); }, 'OAuth2 Redirect URI.') // Important to deny an open redirect attack + ->param('scope', '', function () { return new Text(100); }, 'OAuth2 scope list.') + ->param('state', '', function () { return new Text(1024); }, 'OAuth2 state.') ->action( function ($clientId, $redirectURI, $scope, $state) use ($response) { $response->redirect($redirectURI.'?'.http_build_query(['code' => 'abcdef', 'state' => $state])); } ); -$utopia->get('/v1/mock/tests/general/oauth/token') - ->desc('Mock an OAuth login route') +$utopia->get('/v1/mock/tests/general/oauth2/token') + ->desc('Mock an OAuth2 login route') ->label('scope', 'public') ->label('docs', false) - ->param('client_id', '', function () { return new Text(100); }, 'OAuth Client ID.') - ->param('redirect_uri', '', function () { return new Host(['http://localhost']); }, 'OAuth Redirect URI.') - ->param('client_secret', '', function () { return new Text(100); }, 'OAuth scope list.') - ->param('code', '', function () { return new Text(100); }, 'OAuth state.') + ->param('client_id', '', function () { return new Text(100); }, 'OAuth2 Client ID.') + ->param('redirect_uri', '', function () { return new Host(['http://localhost']); }, 'OAuth2 Redirect URI.') + ->param('client_secret', '', function () { return new Text(100); }, 'OAuth2 scope list.') + ->param('code', '', function () { return new Text(100); }, 'OAuth2 state.') ->action( function ($clientId, $redirectURI, $clientSecret, $code) use ($response) { if($clientId != '1') { @@ -275,11 +275,11 @@ $utopia->get('/v1/mock/tests/general/oauth/token') } ); -$utopia->get('/v1/mock/tests/general/oauth/user') - ->desc('Mock an OAuth user route') +$utopia->get('/v1/mock/tests/general/oauth2/user') + ->desc('Mock an OAuth2 user route') ->label('scope', 'public') ->label('docs', false) - ->param('token', '', function () { return new Text(100); }, 'OAuth Access Token.') + ->param('token', '', function () { return new Text(100); }, 'OAuth2 Access Token.') ->action( function ($token) use ($response) { if($token != '123456') { @@ -294,7 +294,7 @@ $utopia->get('/v1/mock/tests/general/oauth/user') } ); -$utopia->get('/v1/mock/tests/general/oauth/success') +$utopia->get('/v1/mock/tests/general/oauth2/success') ->label('scope', 'public') ->label('docs', false) ->action( @@ -305,7 +305,7 @@ $utopia->get('/v1/mock/tests/general/oauth/success') } ); -$utopia->get('/v1/mock/tests/general/oauth/failure') +$utopia->get('/v1/mock/tests/general/oauth2/failure') ->label('scope', 'public') ->label('docs', false) ->action( diff --git a/app/init.php b/app/init.php index cb0295dc7..42e0d0372 100644 --- a/app/init.php +++ b/app/init.php @@ -37,7 +37,7 @@ $response = new Response(); $env = $request->getServer('_APP_ENV', App::ENV_TYPE_PRODUCTION); $domain = $request->getServer('HTTP_HOST', ''); $version = $request->getServer('_APP_VERSION', 'UNKNOWN'); -$providers = include __DIR__.'/../app/config/providers.php'; // OAuth providers list +$providers = include __DIR__.'/../app/config/providers.php'; // OAuth2 providers list $platforms = include __DIR__.'/../app/config/platforms.php'; $locales = include __DIR__.'/../app/config/locales.php'; // Locales list $collections = include __DIR__.'/../app/config/collections.php'; // Collections list diff --git a/app/sdks/dart/lib/services/account.dart b/app/sdks/dart/lib/services/account.dart index 237aa7668..52f8c75c5 100644 --- a/app/sdks/dart/lib/services/account.dart +++ b/app/sdks/dart/lib/services/account.dart @@ -184,8 +184,8 @@ class Account extends Service { /// choice. Each OAuth 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. - Future createOAuthSession({provider, success, failure}) async { - String path = '/account/sessions/oauth/{provider}'.replaceAll(RegExp('{provider}'), provider); + Future createOAuth2Session({provider, success, failure}) async { + String path = '/account/sessions/oauth2/{provider}'.replaceAll(RegExp('{provider}'), provider); Map params = { 'success': success, diff --git a/app/sdks/javascript/docs/examples/account/create-o-auth2session.md b/app/sdks/javascript/docs/examples/account/create-o-auth2session.md new file mode 100644 index 000000000..ee4b20402 --- /dev/null +++ b/app/sdks/javascript/docs/examples/account/create-o-auth2session.md @@ -0,0 +1,9 @@ +let sdk = new Appwrite(); + +sdk + .setProject('5df5acd0d48c2') // Your project ID +; + +let result = sdk.account.createOAuth2Session('bitbucket', 'https://example.com', 'https://example.com'); + +console.log(result); // Resource URL diff --git a/app/sdks/javascript/src/sdk.js b/app/sdks/javascript/src/sdk.js index 2eb394629..0d7d5b073 100644 --- a/app/sdks/javascript/src/sdk.js +++ b/app/sdks/javascript/src/sdk.js @@ -746,7 +746,7 @@ }, /** - * Create Account Session with OAuth + * Create Account Session with OAuth2 * * Allow the user to login to his account using the OAuth provider of his * choice. Each OAuth provider should be enabled from the Appwrite console @@ -759,7 +759,7 @@ * @throws {Error} * @return {string} */ - createOAuthSession: function(provider, success, failure) { + createOAuth2Session: function(provider, success, failure) { if(provider === undefined) { throw new Error('Missing required parameter: "provider"'); } @@ -772,7 +772,7 @@ throw new Error('Missing required parameter: "failure"'); } - let path = '/account/sessions/oauth/{provider}'.replace(new RegExp('{provider}', 'g'), provider); + let path = '/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}', 'g'), provider); let payload = {}; diff --git a/app/sdks/javascript/src/sdk.min.js b/app/sdks/javascript/src/sdk.min.js index a1bdd0d4d..60e5d1a66 100644 --- a/app/sdks/javascript/src/sdk.min.js +++ b/app/sdks/javascript/src/sdk.min.js @@ -44,10 +44,10 @@ return http.put(path,{'content-type':'application/json',},payload)},getSessions: if(password===undefined){throw new Error('Missing required parameter: "password"')} let path='/account/sessions';let payload={};if(email){payload.email=email} if(password){payload.password=password} -return http.post(path,{'content-type':'application/json',},payload)},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload)},createOAuthSession:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"')} +return http.post(path,{'content-type':'application/json',},payload)},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload)},createOAuth2Session:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"')} if(success===undefined){throw new Error('Missing required parameter: "success"')} if(failure===undefined){throw new Error('Missing required parameter: "failure"')} -let path='/account/sessions/oauth/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload.success=success} +let path='/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload.success=success} if(failure){payload.failure=failure} payload.project=config.project;let query=Object.keys(payload).map(key=>key+'='+encodeURIComponent(payload[key])).join('&');return config.endpoint+path+((query)?'?'+query:'')},deleteSession:function(sessionId){if(sessionId===undefined){throw new Error('Missing required parameter: "sessionId"')} let path='/account/sessions/{sessionId}'.replace(new RegExp('{sessionId}','g'),sessionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload)},createVerification:function(url){if(url===undefined){throw new Error('Missing required parameter: "url"')} diff --git a/app/views/console/index.phtml b/app/views/console/index.phtml index 0846fade0..d54e8e3ae 100644 --- a/app/views/console/index.phtml +++ b/app/views/console/index.phtml @@ -6,7 +6,7 @@ $home = $this->getParam('home', ''); diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index 3fccee6a7..07cce5d12 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -318,7 +318,7 @@ $providers = $this->getParam('providers', []);
  • -

    OAuth Providers

    +

    OAuth2 Providers

    getParam('providers', []); - <?php echo ucfirst($provider); ?> Logo + <?php echo ucfirst($provider); ?> Logo + !{{console-project.usersOauth2Appid}} || + !{{console-project.usersOauth2Secret}}">  Disabled

    - OAuth Developer Docs + OAuth2 Developer Docs

  • diff --git a/docs/references/account/create-session-oauth.md b/docs/references/account/create-session-oauth.md deleted file mode 100644 index 613e7ada2..000000000 --- a/docs/references/account/create-session-oauth.md +++ /dev/null @@ -1 +0,0 @@ -Allow the user to login to his account using the OAuth provider of his choice. Each OAuth 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. \ No newline at end of file diff --git a/docs/references/account/create-session-oauth2.md b/docs/references/account/create-session-oauth2.md new file mode 100644 index 000000000..459b62dc4 --- /dev/null +++ b/docs/references/account/create-session-oauth2.md @@ -0,0 +1 @@ +Allow the user to login to his account using the OAuth2 provider of his 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. \ No newline at end of file diff --git a/docs/tutorials/add-oauth-provider.md b/docs/tutorials/add-oauth2-provider.md similarity index 62% rename from docs/tutorials/add-oauth-provider.md rename to docs/tutorials/add-oauth2-provider.md index b97222b09..515b86fa2 100644 --- a/docs/tutorials/add-oauth-provider.md +++ b/docs/tutorials/add-oauth2-provider.md @@ -1,4 +1,4 @@ -# Adding a New OAuth Provider +# Adding a New OAuth2 Provider This document is part of the Appwrite contributors' guide. Before you continue reading this document make sure you have read the [Code of Conduct](../CODE_OF_CONDUCT.md) and the [Contributing Guide](../CONTRIBUTING.md). @@ -6,13 +6,13 @@ This document is part of the Appwrite contributors' guide. Before you continue r ### Agenda -OAuth providers help users to log in easily to apps and websites without the need to provide passwords or any other type of credentials. Appwrite's goal is to have support from as many **major** OAuth providers as possible. +OAuth2 providers help users to log in easily to apps and websites without the need to provide passwords or any other type of credentials. Appwrite's goal is to have support from as many **major** OAuth2 providers as possible. -As of the writing of these lines, we do not accept any minor OAuth providers. For us to accept some smaller and potentially unlimited number of OAuth providers, some product design and software architecture changes must be applied first. +As of the writing of these lines, we do not accept any minor OAuth2 providers. For us to accept some smaller and potentially unlimited number of OAuth2 providers, some product design and software architecture changes must be applied first. ### List Your new Provider -The first step in adding a new OAuth provider is to add it to the list in providers config file array, located at: +The first step in adding a new OAuth2 provider is to add it to the list in providers config file array, located at: ``` ./app/config/providers.php @@ -25,13 +25,13 @@ Make sure to fill all data needed and that your provider array key name: ### Add Provider Logo -Add a logo image to your new provider in this path: `./public/images/oauth`. Your logo should be a png 100×100px file with the name of your provider (all lowercase). Please make sure to leave about 30px padding around the logo to be consistent with other logos. +Add a logo image to your new provider in this path: `./public/images/oauth2`. Your logo should be a png 100×100px file with the name of your provider (all lowercase). Please make sure to leave about 30px padding around the logo to be consistent with other logos. ### Add Provider Class Once you have finished setting up all the metadata for the new provider, you need to start coding. -Create a new class that extends the basic OAuth provider abstract class in this location: +Create a new class that extends the basic OAuth2 provider abstract class in this location: ```bash ./src/Auth/OAuth/ProviderName @@ -41,7 +41,7 @@ Note that the class name should start with a capital letter as PHP FIG standards Once a new class is created, you can start to implement your new provider's login flow. The best way to do this correctly is to have a look at another provider's implementation and try to follow the same standards. -Please mention in your documentation what resources or API docs you used to implement the provider's OAuth protocol. +Please mention in your documentation what resources or API docs you used to implement the provider's OAuth2 protocol. ### Test Your Provider @@ -49,7 +49,7 @@ After you finished adding your new provider to Appwrite you should be able to se Add credentials and check both a successful and a failed login (where the user rejects integration on provider page). -You can test your OAuth provider by trying to login using the [OAuth method](https://appwrite.io/docs/auth#oauth) when integrating the Appwrite JS SDK in a demo app. +You can test your OAuth2 provider by trying to login using the [OAuth2 method](https://appwrite.io/docs/account#createOAuth2Session) when integrating the Appwrite JS SDK in a demo app. Pass your new adapter name as the provider parameter. If login is successful, you will be redirected to your success URL parameter. Otherwise, you will be redirected to your failure URL. diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index 35c843a04..0ee9b679e 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -50,10 +50,10 @@ return http.put(path,{'content-type':'application/json',},payload);},getSessions if(password===undefined){throw new Error('Missing required parameter: "password"');} let path='/account/sessions';let payload={};if(email){payload['email']=email;} if(password){payload['password']=password;} -return http.post(path,{'content-type':'application/json',},payload);},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createOAuthSession:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"');} +return http.post(path,{'content-type':'application/json',},payload);},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createOAuth2Session:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"');} if(success===undefined){throw new Error('Missing required parameter: "success"');} if(failure===undefined){throw new Error('Missing required parameter: "failure"');} -let path='/account/sessions/oauth/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload['success']=success;} +let path='/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload['success']=success;} if(failure){payload['failure']=failure;} return http.get(path,{'content-type':'application/json',},payload);},deleteSession:function(sessionId){if(sessionId===undefined){throw new Error('Missing required parameter: "id"');} let path='/account/sessions/{sessionId}'.replace(new RegExp('{sessionId}','g'),sessionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createVerification:function(url){if(url===undefined){throw new Error('Missing required parameter: "url"');} @@ -186,9 +186,9 @@ let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}',' if(scopes){payload['scopes']=scopes;} return http.put(path,{'content-type':'application/json',},payload);},deleteKey:function(projectId,keyId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} if(keyId===undefined){throw new Error('Missing required parameter: "keyId"');} -let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{keyId}','g'),keyId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},updateOAuth:function(projectId,provider,appId='',secret=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} +let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{keyId}','g'),keyId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},updateOAuth2:function(projectId,provider,appId='',secret=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} if(provider===undefined){throw new Error('Missing required parameter: "provider"');} -let path='/projects/{projectId}/oauth'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(provider){payload['provider']=provider;} +let path='/projects/{projectId}/oauth2'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(provider){payload['provider']=provider;} if(appId){payload['appId']=appId;} if(secret){payload['secret']=secret;} return http.patch(path,{'content-type':'application/json',},payload);},listPlatforms:function(projectId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} diff --git a/public/dist/scripts/app-dep.js b/public/dist/scripts/app-dep.js index 08a89d279..a56882311 100644 --- a/public/dist/scripts/app-dep.js +++ b/public/dist/scripts/app-dep.js @@ -50,10 +50,10 @@ return http.put(path,{'content-type':'application/json',},payload);},getSessions if(password===undefined){throw new Error('Missing required parameter: "password"');} let path='/account/sessions';let payload={};if(email){payload['email']=email;} if(password){payload['password']=password;} -return http.post(path,{'content-type':'application/json',},payload);},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createOAuthSession:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"');} +return http.post(path,{'content-type':'application/json',},payload);},deleteSessions:function(){let path='/account/sessions';let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createOAuth2Session:function(provider,success,failure){if(provider===undefined){throw new Error('Missing required parameter: "provider"');} if(success===undefined){throw new Error('Missing required parameter: "success"');} if(failure===undefined){throw new Error('Missing required parameter: "failure"');} -let path='/account/sessions/oauth/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload['success']=success;} +let path='/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}','g'),provider);let payload={};if(success){payload['success']=success;} if(failure){payload['failure']=failure;} return http.get(path,{'content-type':'application/json',},payload);},deleteSession:function(sessionId){if(sessionId===undefined){throw new Error('Missing required parameter: "id"');} let path='/account/sessions/{sessionId}'.replace(new RegExp('{sessionId}','g'),sessionId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},createVerification:function(url){if(url===undefined){throw new Error('Missing required parameter: "url"');} @@ -186,9 +186,9 @@ let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}',' if(scopes){payload['scopes']=scopes;} return http.put(path,{'content-type':'application/json',},payload);},deleteKey:function(projectId,keyId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} if(keyId===undefined){throw new Error('Missing required parameter: "keyId"');} -let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{keyId}','g'),keyId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},updateOAuth:function(projectId,provider,appId='',secret=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} +let path='/projects/{projectId}/keys/{keyId}'.replace(new RegExp('{projectId}','g'),projectId).replace(new RegExp('{keyId}','g'),keyId);let payload={};return http.delete(path,{'content-type':'application/json',},payload);},updateOAuth2:function(projectId,provider,appId='',secret=''){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} if(provider===undefined){throw new Error('Missing required parameter: "provider"');} -let path='/projects/{projectId}/oauth'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(provider){payload['provider']=provider;} +let path='/projects/{projectId}/oauth2'.replace(new RegExp('{projectId}','g'),projectId);let payload={};if(provider){payload['provider']=provider;} if(appId){payload['appId']=appId;} if(secret){payload['secret']=secret;} return http.patch(path,{'content-type':'application/json',},payload);},listPlatforms:function(projectId){if(projectId===undefined){throw new Error('Missing required parameter: "projectId"');} diff --git a/public/images/oauth/amazon.png b/public/images/oauth2/amazon.png similarity index 100% rename from public/images/oauth/amazon.png rename to public/images/oauth2/amazon.png diff --git a/public/images/oauth/apple.png b/public/images/oauth2/apple.png similarity index 100% rename from public/images/oauth/apple.png rename to public/images/oauth2/apple.png diff --git a/public/images/oauth/bitbucket.png b/public/images/oauth2/bitbucket.png similarity index 100% rename from public/images/oauth/bitbucket.png rename to public/images/oauth2/bitbucket.png diff --git a/public/images/oauth/bitly.png b/public/images/oauth2/bitly.png similarity index 100% rename from public/images/oauth/bitly.png rename to public/images/oauth2/bitly.png diff --git a/public/images/oauth/discord.png b/public/images/oauth2/discord.png similarity index 100% rename from public/images/oauth/discord.png rename to public/images/oauth2/discord.png diff --git a/public/images/oauth/dropbox.png b/public/images/oauth2/dropbox.png similarity index 100% rename from public/images/oauth/dropbox.png rename to public/images/oauth2/dropbox.png diff --git a/public/images/oauth/facebook.png b/public/images/oauth2/facebook.png similarity index 100% rename from public/images/oauth/facebook.png rename to public/images/oauth2/facebook.png diff --git a/public/images/oauth/github.png b/public/images/oauth2/github.png similarity index 100% rename from public/images/oauth/github.png rename to public/images/oauth2/github.png diff --git a/public/images/oauth/gitlab.png b/public/images/oauth2/gitlab.png similarity index 100% rename from public/images/oauth/gitlab.png rename to public/images/oauth2/gitlab.png diff --git a/public/images/oauth/google.png b/public/images/oauth2/google.png similarity index 100% rename from public/images/oauth/google.png rename to public/images/oauth2/google.png diff --git a/public/images/oauth/linkedin.png b/public/images/oauth2/linkedin.png similarity index 100% rename from public/images/oauth/linkedin.png rename to public/images/oauth2/linkedin.png diff --git a/public/images/oauth/microsoft.png b/public/images/oauth2/microsoft.png similarity index 100% rename from public/images/oauth/microsoft.png rename to public/images/oauth2/microsoft.png diff --git a/public/images/oauth/paypal.png b/public/images/oauth2/paypal.png similarity index 100% rename from public/images/oauth/paypal.png rename to public/images/oauth2/paypal.png diff --git a/public/images/oauth/salesforce.png b/public/images/oauth2/salesforce.png similarity index 100% rename from public/images/oauth/salesforce.png rename to public/images/oauth2/salesforce.png diff --git a/public/images/oauth/slack.png b/public/images/oauth2/slack.png similarity index 100% rename from public/images/oauth/slack.png rename to public/images/oauth2/slack.png diff --git a/public/images/oauth/spotify.png b/public/images/oauth2/spotify.png similarity index 100% rename from public/images/oauth/spotify.png rename to public/images/oauth2/spotify.png diff --git a/public/images/oauth/twitch.png b/public/images/oauth2/twitch.png similarity index 100% rename from public/images/oauth/twitch.png rename to public/images/oauth2/twitch.png diff --git a/public/images/oauth/twitter.png b/public/images/oauth2/twitter.png similarity index 100% rename from public/images/oauth/twitter.png rename to public/images/oauth2/twitter.png diff --git a/public/images/oauth/vk.png b/public/images/oauth2/vk.png similarity index 100% rename from public/images/oauth/vk.png rename to public/images/oauth2/vk.png diff --git a/public/images/oauth/yahoo.png b/public/images/oauth2/yahoo.png similarity index 100% rename from public/images/oauth/yahoo.png rename to public/images/oauth2/yahoo.png diff --git a/public/images/oauth/yandex.png b/public/images/oauth2/yandex.png similarity index 100% rename from public/images/oauth/yandex.png rename to public/images/oauth2/yandex.png diff --git a/public/scripts/dependencies/appwrite.js b/public/scripts/dependencies/appwrite.js index 5b01b3156..4bcbdfbd1 100644 --- a/public/scripts/dependencies/appwrite.js +++ b/public/scripts/dependencies/appwrite.js @@ -771,10 +771,10 @@ }, /** - * Create Account Session with OAuth + * Create Account Session with OAuth2 * - * Allow the user to login to his account using the OAuth provider of his - * choice. Each OAuth provider should be enabled from the Appwrite console + * Allow the user to login to his account using the OAuth2 provider of his + * 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. * @@ -784,7 +784,7 @@ * @throws {Error} * @return {Promise} */ - createOAuthSession: function(provider, success, failure) { + createOAuth2Session: function(provider, success, failure) { if(provider === undefined) { throw new Error('Missing required parameter: "provider"'); } @@ -797,7 +797,7 @@ throw new Error('Missing required parameter: "failure"'); } - let path = '/account/sessions/oauth/{provider}'.replace(new RegExp('{provider}', 'g'), provider); + let path = '/account/sessions/oauth2/{provider}'.replace(new RegExp('{provider}', 'g'), provider); let payload = {}; @@ -2161,7 +2161,7 @@ }, /** - * Update Project OAuth + * Update Project OAuth2 * * * @param {string} projectId @@ -2171,7 +2171,7 @@ * @throws {Error} * @return {Promise} */ - updateOAuth: function(projectId, provider, appId = '', secret = '') { + updateOAuth2: function(projectId, provider, appId = '', secret = '') { if(projectId === undefined) { throw new Error('Missing required parameter: "projectId"'); } @@ -2180,7 +2180,7 @@ throw new Error('Missing required parameter: "provider"'); } - let path = '/projects/{projectId}/oauth'.replace(new RegExp('{projectId}', 'g'), projectId); + let path = '/projects/{projectId}/oauth2'.replace(new RegExp('{projectId}', 'g'), projectId); let payload = {}; diff --git a/src/Auth/OAuth.php b/src/Auth/OAuth2.php similarity index 94% rename from src/Auth/OAuth.php rename to src/Auth/OAuth2.php index e4bd23130..37a542b56 100644 --- a/src/Auth/OAuth.php +++ b/src/Auth/OAuth2.php @@ -2,7 +2,7 @@ namespace Auth; -abstract class OAuth +abstract class OAuth2 { /** * @var string @@ -30,7 +30,7 @@ abstract class OAuth protected $scopes; /** - * OAuth constructor. + * OAuth2 constructor. * * @param string $appId * @param string $appSecret @@ -92,7 +92,7 @@ abstract class OAuth * * @return $this */ - protected function addScope(string $scope):OAuth + protected function addScope(string $scope):OAuth2 { // Add a scope to the scopes array if it isn't already present if (!in_array($scope, $this->scopes)){ @@ -110,7 +110,7 @@ abstract class OAuth } - // The parseState function was designed specifically for Amazon OAuth Adapter to override. + // The parseState function was designed specifically for Amazon OAuth2 Adapter to override. // The response from Amazon is html encoded and hence it needs to be html_decoded before // json_decoding /** @@ -138,7 +138,7 @@ abstract class OAuth curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, 'Console_OAuth_Agent'); + curl_setopt($ch, CURLOPT_USERAGENT, APP_USERAGENT); if (!empty($payload)) { curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); diff --git a/src/Auth/OAuth/Amazon.php b/src/Auth/OAuth2/Amazon.php similarity index 97% rename from src/Auth/OAuth/Amazon.php rename to src/Auth/OAuth2/Amazon.php index 64c04fdc9..b2913fbd9 100644 --- a/src/Auth/OAuth/Amazon.php +++ b/src/Auth/OAuth2/Amazon.php @@ -1,14 +1,15 @@ version.'/mock/tests/general/oauth?'. http_build_query([ + return 'http://localhost/'.$this->version.'/mock/tests/general/oauth2?'. http_build_query([ 'client_id' => $this->appID, 'redirect_uri' => $this->callback, 'scope' => implode(' ', $this->getScopes()), @@ -53,7 +53,7 @@ class Mock extends OAuth { $accessToken = $this->request( 'GET', - 'http://localhost/'.$this->version.'/mock/tests/general/oauth/token?'. + 'http://localhost/'.$this->version.'/mock/tests/general/oauth2/token?'. http_build_query([ 'client_id' => $this->appID, 'redirect_uri' => $this->callback, @@ -127,7 +127,7 @@ class Mock extends OAuth protected function getUser(string $accessToken):array { if (empty($this->user)) { - $user = $this->request('GET', 'http://localhost/'.$this->version.'/mock/tests/general/oauth/user?token='.urlencode($accessToken)); + $user = $this->request('GET', 'http://localhost/'.$this->version.'/mock/tests/general/oauth2/user?token='.urlencode($accessToken)); $this->user = json_decode($user, true); } diff --git a/src/Auth/OAuth/Paypal.php b/src/Auth/OAuth2/Paypal.php similarity index 98% rename from src/Auth/OAuth/Paypal.php rename to src/Auth/OAuth2/Paypal.php index bff273b24..b1175cf3f 100644 --- a/src/Auth/OAuth/Paypal.php +++ b/src/Auth/OAuth2/Paypal.php @@ -1,13 +1,13 @@ client->call(Client::METHOD_PATCH, '/projects/'.$this->getProject()['$uid'].'/oauth', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/projects/'.$this->getProject()['$uid'].'/oauth2', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => 'console', @@ -35,13 +35,13 @@ class AccountCustomClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 200); - $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth/'.$provider, array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/'.$provider, array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$uid'], ]), [ - 'success' => 'http://localhost/v1/mock/tests/general/oauth/success', - 'failure' => 'http://localhost/v1/mock/tests/general/oauth/failure', + 'success' => 'http://localhost/v1/mock/tests/general/oauth2/success', + 'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure', ]); $this->assertEquals(200, $response['headers']['status-code']);