diff --git a/app/config/events.php b/app/config/events.php index 2d88d2a4e..0861d5bbc 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -82,6 +82,11 @@ return [ 'model' => Response::MODEL_SESSION, 'note' => '', ], + 'account.sessions.udpdate' => [ + 'description' => 'This event triggers when the account session is updated.', + 'model' => Response::MODEL_SESSION, + 'note' => '', + ], 'database.collections.create' => [ 'description' => 'This event triggers when a database collection is created.', 'model' => Response::MODEL_COLLECTION, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 028e8ccab..8a1e10e7d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -23,6 +23,7 @@ use Utopia\Database\Validator\UID; use Utopia\Exception; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; +use Utopia\Validator\Boolean; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -533,7 +534,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'providerUid' => $oauth2ID, 'providerAccessToken' => $accessToken, 'providerRefreshToken' => $refreshToken, - 'providerAccessTokenExpiry' => $accessTokenExpiry, + 'providerAccessTokenExpiry' => \time() + $accessTokenExpiry - 5, // 5 seconds time-sync and networking gap, to be safe 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), @@ -1203,6 +1204,7 @@ App::get('/v1/account/logs') 'account.update.password', 'account.update.prefs', 'account.sessions.create', + 'account.sessions.update', 'account.sessions.delete', 'account.recovery.create', 'account.recovery.update', @@ -1598,8 +1600,8 @@ App::delete('/v1/account/sessions/:sessionId') $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') - ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) - : $sessionId; + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + : $sessionId; $sessions = $user->getAttribute('sessions', []); @@ -1652,6 +1654,94 @@ App::delete('/v1/account/sessions/:sessionId') throw new Exception('Session not found', 404); }); +App::patch('/v1/account/sessions/:sessionId/oauth2-tokens') + ->desc('Update OAUth2 Tokens') + ->groups(['api', 'account']) + ->label('scope', 'account') + ->label('event', 'account.sessions.update') + ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT]) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'updateOAuth2Tokens') + ->label('sdk.description', '/docs/references/account/update-oauth2-tokens.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_SESSION) + ->label('abuse-limit', 10) + ->param('sessionId', null, new UID(), 'Session ID. Use the string \'current\' to update the current device session.') + ->param('force', true, new Boolean(), 'Should generate new token even if current one is still valid?', true) + ->inject('request') + ->inject('response') + ->inject('user') + ->inject('dbForProject') + ->inject('project') + ->inject('locale') + ->inject('audits') + ->inject('events') + ->inject('usage') + ->action(function ($sessionId, $force, $request, $response, $user, $dbForProject, $project, $locale, $audits, $events, $usage) { + /** @var Appwrite\Utopia\Request $request */ + /** @var boolean $force */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForProject */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Locale\Locale $locale */ + /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ + /** @var Appwrite\Stats\Stats $usage */ + + $sessionId = ($sessionId === 'current') + ? Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret) + : $sessionId; + + $sessions = $user->getAttribute('sessions', []); + + foreach ($sessions as $key => $session) {/** @var Document $session */ + if ($sessionId == $session->getId()) { + $provider = $session->getAttribute('provider'); + $refreshToken = $session->getAttribute('providerRefreshToken'); + + $appId = $project->getAttribute('providers', [])[$provider.'Appid'] ?? ''; + $appSecret = $project->getAttribute('providers', [])[$provider.'Secret'] ?? '{}'; + + $className = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); + $oauth2 = new $className($appId, $appSecret, '', [], []); + + $oauth2->refreshTokens($refreshToken); + + \var_dump($oauth2->getAccessToken('')); + \var_dump($oauth2->getRefreshToken('')); + + $session + ->setAttribute('providerAccessToken', $oauth2->getAccessToken('')) + ->setAttribute('providerRefreshToken', $oauth2->getRefreshToken('')) + ->setAttribute('providerAccessTokenExpiry', \time() + $oauth2->getAccessTokenExpiry('') - 5) // 5 seconds time-sync and networking gap, to be safe + ; + + $session = $dbForProject->updateDocument('sessions', $sessionId, $session); + + $audits + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.sessions.update') + ->setParam('resource', 'user/' . $user->getId()) + ; + + $events + ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) + ; + + $usage + ->setParam('users.sessions.update', 1) + ->setParam('users.update', 1) + ; + + $response->dynamic($session, Response::MODEL_SESSION); + } + } + + throw new Exception('Session not found', 404); + }); + App::delete('/v1/account/sessions') ->desc('Delete All Account Sessions') ->groups(['api', 'account']) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index ac030f7e7..e21b2d01f 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -289,6 +289,7 @@ App::get('/v1/users/:userId/logs') 'account.update.password', 'account.update.prefs', 'account.sessions.create', + 'account.sessions.update', 'account.sessions.delete', 'account.recovery.create', 'account.recovery.update', diff --git a/src/Appwrite/Auth/OAuth2.php b/src/Appwrite/Auth/OAuth2.php index 356451dcf..0aa87c219 100644 --- a/src/Appwrite/Auth/OAuth2.php +++ b/src/Appwrite/Auth/OAuth2.php @@ -66,6 +66,13 @@ abstract class OAuth2 */ abstract public function getTokens(string $code):array; + /** + * @param string $refreshToken + * + * @return array + */ + abstract public function refreshTokens(string $refreshToken):array; + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Amazon.php b/src/Appwrite/Auth/OAuth2/Amazon.php index e73582508..4a970e1aa 100644 --- a/src/Appwrite/Auth/OAuth2/Amazon.php +++ b/src/Appwrite/Auth/OAuth2/Amazon.php @@ -87,6 +87,18 @@ class Amazon extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Apple.php b/src/Appwrite/Auth/OAuth2/Apple.php index 0a764b966..51b3e8f0b 100644 --- a/src/Appwrite/Auth/OAuth2/Apple.php +++ b/src/Appwrite/Auth/OAuth2/Apple.php @@ -85,6 +85,18 @@ class Apple extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Bitbucket.php b/src/Appwrite/Auth/OAuth2/Bitbucket.php index bc35bd5ca..dbda90dd2 100644 --- a/src/Appwrite/Auth/OAuth2/Bitbucket.php +++ b/src/Appwrite/Auth/OAuth2/Bitbucket.php @@ -71,6 +71,18 @@ class Bitbucket extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Bitly.php b/src/Appwrite/Auth/OAuth2/Bitly.php index fcea846e0..123a8f8a4 100644 --- a/src/Appwrite/Auth/OAuth2/Bitly.php +++ b/src/Appwrite/Auth/OAuth2/Bitly.php @@ -81,6 +81,18 @@ class Bitly extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Box.php b/src/Appwrite/Auth/OAuth2/Box.php index f53a8d80f..e0e22bc49 100644 --- a/src/Appwrite/Auth/OAuth2/Box.php +++ b/src/Appwrite/Auth/OAuth2/Box.php @@ -88,6 +88,18 @@ class Box extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Discord.php b/src/Appwrite/Auth/OAuth2/Discord.php index 9e55e5301..bda797616 100644 --- a/src/Appwrite/Auth/OAuth2/Discord.php +++ b/src/Appwrite/Auth/OAuth2/Discord.php @@ -83,6 +83,18 @@ class Discord extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Dropbox.php b/src/Appwrite/Auth/OAuth2/Dropbox.php index cc1614f39..facd54b3d 100644 --- a/src/Appwrite/Auth/OAuth2/Dropbox.php +++ b/src/Appwrite/Auth/OAuth2/Dropbox.php @@ -72,6 +72,18 @@ class Dropbox extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Facebook.php b/src/Appwrite/Auth/OAuth2/Facebook.php index ab4f173a4..37a0019c1 100644 --- a/src/Appwrite/Auth/OAuth2/Facebook.php +++ b/src/Appwrite/Auth/OAuth2/Facebook.php @@ -71,6 +71,18 @@ class Facebook extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Github.php b/src/Appwrite/Auth/OAuth2/Github.php index e6329d696..913e1e90f 100644 --- a/src/Appwrite/Auth/OAuth2/Github.php +++ b/src/Appwrite/Auth/OAuth2/Github.php @@ -68,6 +68,18 @@ class Github extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Gitlab.php b/src/Appwrite/Auth/OAuth2/Gitlab.php index 2278affed..68d52fd46 100644 --- a/src/Appwrite/Auth/OAuth2/Gitlab.php +++ b/src/Appwrite/Auth/OAuth2/Gitlab.php @@ -71,6 +71,18 @@ class Gitlab extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Google.php b/src/Appwrite/Auth/OAuth2/Google.php index 437965d68..66b4fa9c5 100644 --- a/src/Appwrite/Auth/OAuth2/Google.php +++ b/src/Appwrite/Auth/OAuth2/Google.php @@ -81,6 +81,18 @@ class Google extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Linkedin.php b/src/Appwrite/Auth/OAuth2/Linkedin.php index 2331cbf34..2c7907fb8 100644 --- a/src/Appwrite/Auth/OAuth2/Linkedin.php +++ b/src/Appwrite/Auth/OAuth2/Linkedin.php @@ -84,6 +84,18 @@ class Linkedin extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Microsoft.php b/src/Appwrite/Auth/OAuth2/Microsoft.php index 59e758727..2b09888dc 100644 --- a/src/Appwrite/Auth/OAuth2/Microsoft.php +++ b/src/Appwrite/Auth/OAuth2/Microsoft.php @@ -78,6 +78,18 @@ class Microsoft extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Mock.php b/src/Appwrite/Auth/OAuth2/Mock.php index 8cfc68e7b..b051e51f0 100644 --- a/src/Appwrite/Auth/OAuth2/Mock.php +++ b/src/Appwrite/Auth/OAuth2/Mock.php @@ -72,6 +72,18 @@ class Mock extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Notion.php b/src/Appwrite/Auth/OAuth2/Notion.php index dbe8f700a..9fd0d4784 100644 --- a/src/Appwrite/Auth/OAuth2/Notion.php +++ b/src/Appwrite/Auth/OAuth2/Notion.php @@ -77,6 +77,18 @@ class Notion extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Paypal.php b/src/Appwrite/Auth/OAuth2/Paypal.php index 038190baf..8131536ca 100644 --- a/src/Appwrite/Auth/OAuth2/Paypal.php +++ b/src/Appwrite/Auth/OAuth2/Paypal.php @@ -98,6 +98,18 @@ class Paypal extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Salesforce.php b/src/Appwrite/Auth/OAuth2/Salesforce.php index ffcbf3e4d..07c883b96 100644 --- a/src/Appwrite/Auth/OAuth2/Salesforce.php +++ b/src/Appwrite/Auth/OAuth2/Salesforce.php @@ -88,6 +88,18 @@ class Salesforce extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Slack.php b/src/Appwrite/Auth/OAuth2/Slack.php index 6c2786eea..dfc4e5830 100644 --- a/src/Appwrite/Auth/OAuth2/Slack.php +++ b/src/Appwrite/Auth/OAuth2/Slack.php @@ -71,6 +71,18 @@ class Slack extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Spotify.php b/src/Appwrite/Auth/OAuth2/Spotify.php index 6cb31d5be..db800b3fe 100644 --- a/src/Appwrite/Auth/OAuth2/Spotify.php +++ b/src/Appwrite/Auth/OAuth2/Spotify.php @@ -84,6 +84,18 @@ class Spotify extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Stripe.php b/src/Appwrite/Auth/OAuth2/Stripe.php index 76512a197..1b2984a86 100644 --- a/src/Appwrite/Auth/OAuth2/Stripe.php +++ b/src/Appwrite/Auth/OAuth2/Stripe.php @@ -83,6 +83,18 @@ class Stripe extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Tradeshift.php b/src/Appwrite/Auth/OAuth2/Tradeshift.php index b50d5032f..b8d00cde3 100644 --- a/src/Appwrite/Auth/OAuth2/Tradeshift.php +++ b/src/Appwrite/Auth/OAuth2/Tradeshift.php @@ -93,6 +93,18 @@ class Tradeshift extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Twitch.php b/src/Appwrite/Auth/OAuth2/Twitch.php index 8d9fef201..d355820a7 100644 --- a/src/Appwrite/Auth/OAuth2/Twitch.php +++ b/src/Appwrite/Auth/OAuth2/Twitch.php @@ -84,6 +84,26 @@ class Twitch extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + $this->tokens = \json_decode($this->request( + 'POST', + $this->endpoint . 'token?' . \http_build_query([ + "client_id" => $this->appID, + "client_secret" => $this->appSecret, + "refresh_token" => $refreshToken, + "grant_type" => "refresh_token", + ]) + ), true); + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Vk.php b/src/Appwrite/Auth/OAuth2/Vk.php index 2dc537411..046a50848 100644 --- a/src/Appwrite/Auth/OAuth2/Vk.php +++ b/src/Appwrite/Auth/OAuth2/Vk.php @@ -87,6 +87,18 @@ class Vk extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/WordPress.php b/src/Appwrite/Auth/OAuth2/WordPress.php index ba4400a35..ac4b35059 100644 --- a/src/Appwrite/Auth/OAuth2/WordPress.php +++ b/src/Appwrite/Auth/OAuth2/WordPress.php @@ -73,6 +73,18 @@ class WordPress extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Yahoo.php b/src/Appwrite/Auth/OAuth2/Yahoo.php index 2e6a54cd0..6fca884be 100644 --- a/src/Appwrite/Auth/OAuth2/Yahoo.php +++ b/src/Appwrite/Auth/OAuth2/Yahoo.php @@ -100,6 +100,18 @@ class Yahoo extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Yammer.php b/src/Appwrite/Auth/OAuth2/Yammer.php index f7b08a7e0..55f384474 100644 --- a/src/Appwrite/Auth/OAuth2/Yammer.php +++ b/src/Appwrite/Auth/OAuth2/Yammer.php @@ -71,6 +71,18 @@ class Yammer extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/src/Appwrite/Auth/OAuth2/Yandex.php b/src/Appwrite/Auth/OAuth2/Yandex.php index 03fc9669e..5db2bf5b2 100644 --- a/src/Appwrite/Auth/OAuth2/Yandex.php +++ b/src/Appwrite/Auth/OAuth2/Yandex.php @@ -84,6 +84,18 @@ class Yandex extends OAuth2 return $this->tokens; } + /** + * @param string $refreshToken + * + * @return array + */ + public function refreshTokens(string $refreshToken):array + { + // TODO: Implement (Twitch as example) + + return $this->tokens; + } + /** * @param string $accessToken * diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 6cfcaef62..9129fb3cf 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -107,6 +107,7 @@ trait ProjectCustom 'account.verification.update', 'account.delete', 'account.sessions.create', + 'account.sessions.update', 'account.sessions.delete', 'database.collections.create', 'database.collections.update',