diff --git a/app/config/collections.php b/app/config/collections.php index 35aa37d352..487f893423 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1054,9 +1054,9 @@ $collections = [ 'size' => 16384, 'signed' => true, 'required' => false, - 'default' => [], - 'array' => true, - 'filters' => ['json'], + 'default' => null, + 'array' => false, + 'filters' => ['subQuerySessions'], ], [ '$id' => 'tokens', @@ -1478,6 +1478,13 @@ $collections = [ 'lengths' => [100, 100], 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], ], + [ + '$id' => '_key_user', + 'type' => Database::INDEX_KEY, + 'attributes' => ['userId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], ], ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 680b98274f..8751b97372 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -104,7 +104,7 @@ App::post('/v1/account') 'reset' => false, 'name' => $name, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), @@ -208,8 +208,7 @@ App::post('/v1/account/sessions') ->setAttribute('$write', ['user:' . $profile->getId()]) ); - $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); - $profile = $dbForProject->updateDocument('users', $profile->getId(), $profile); + $dbForProject->deleteCachedDocument('users', $profile->getId()); $audits ->setParam('userId', $profile->getId()) @@ -458,13 +457,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $current = Auth::sessionVerify($sessions, Auth::$secret); if ($current) { // Delete current session of new one. - foreach ($sessions as $key => $session) {/** @var Document $session */ - if ($current === $session['$id']) { - unset($sessions[$key]); - - $dbForProject->deleteDocument('sessions', $session->getId()); - $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); - } + $currentDocument = $dbForProject->getDocument('sessions', $current); + if(!$currentDocument->isEmpty()) { + $dbForProject->deleteDocument('sessions', $currentDocument->getId()); + $dbForProject->deleteCachedDocument('users', $user->getId()); } } @@ -505,7 +501,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'reset' => false, 'name' => $name, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), @@ -553,17 +549,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $user ->setAttribute('status', true) - ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND) ; Authorization::setRole('user:' . $user->getId()); + $dbForProject->updateDocument('users', $user->getId(), $user); + $session = $dbForProject->createDocument('sessions', $session ->setAttribute('$read', ['user:' . $user->getId()]) ->setAttribute('$write', ['user:' . $user->getId()]) ); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); + $dbForProject->deleteCachedDocument('users', $user->getId()); $audits ->setParam('userId', $user->getId()) @@ -679,7 +676,7 @@ App::post('/v1/account/sessions/magic-url') 'registration' => \time(), 'reset' => false, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => implode(' ', [$userId, $email]), @@ -824,6 +821,10 @@ App::put('/v1/account/sessions/magic-url') ->setAttribute('$write', ['user:' . $user->getId()]) ); + $dbForProject->deleteCachedDocument('users', $user->getId()); + + $tokens = $user->getAttribute('tokens', []); + /** * We act like we're updating and validating * the recovery token but actually we don't need it anymore. @@ -832,8 +833,7 @@ App::put('/v1/account/sessions/magic-url') $dbForProject->deleteCachedDocument('users', $user->getId()); $user - ->setAttribute('emailVerification', true) - ->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + ->setAttribute('emailVerification', true); $user = $dbForProject->updateDocument('users', $user->getId(), $user); @@ -942,7 +942,7 @@ App::post('/v1/account/sessions/anonymous') 'reset' => false, 'name' => null, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => $userId, @@ -978,8 +978,7 @@ App::post('/v1/account/sessions/anonymous') ->setAttribute('$write', ['user:' . $user->getId()]) ); - $user = $dbForProject->updateDocument('users', $user->getId(), - $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND)); + $dbForProject->deleteCachedDocument('users', $user->getId()); $audits ->setParam('userId', $user->getId()) @@ -1030,16 +1029,17 @@ App::post('/v1/account/jwt') ->label('abuse-key', 'url:{url},userId:{userId}') ->inject('response') ->inject('user') - ->action(function ($response, $user) { + ->inject('dbForProject') + ->action(function ($response, $user, $dbForProject) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Document $user */ + /** @var Utopia\Database\Database $dbForProject */ + $sessions = $user->getAttribute('sessions', []); $current = new Document(); - foreach ($sessions as $session) { - /** @var Utopia\Database\Document $session */ - + foreach ($sessions as $session) { /** @var Utopia\Database\Document $session */ if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $current = $session; } @@ -1623,8 +1623,8 @@ App::delete('/v1/account/sessions/:sessionId') ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; } - - $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', $sessions)); + + $dbForProject->deleteCachedDocument('users', $user->getId()); $events ->setParam('eventData', $response->output($session, Response::MODEL_SESSION)) @@ -1718,8 +1718,7 @@ App::patch('/v1/account/sessions/:sessionId') $dbForProject->updateDocument('sessions', $sessionId, $session); - $user->setAttribute("sessions", $sessions); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); + $dbForProject->deleteCachedDocument('users', $user->getId()); $audits ->setParam('userId', $user->getId()) @@ -1805,7 +1804,7 @@ App::delete('/v1/account/sessions') } } - $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', [])); + $dbForProject->deleteCachedDocument('users', $user->getId()); $numOfSessions = count($sessions); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 501096e349..a6bb58ba3d 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -341,7 +341,7 @@ App::post('/v1/teams/:teamId/memberships') 'reset' => false, 'name' => $name, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), @@ -708,11 +708,10 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') ->setAttribute('$write', ['user:'.$user->getId()]) ); - $user->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + $dbForProject->deleteCachedDocument('users', $user->getId()); Authorization::setRole('user:'.$userId); - $user = $dbForProject->updateDocument('users', $user->getId(), $user); $membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership); $dbForProject->deleteCachedDocument('users', $user->getId()); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d4b2109798..2f33098bcb 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -63,7 +63,7 @@ App::post('/v1/users') 'reset' => false, 'name' => $name, 'prefs' => new \stdClass(), - 'sessions' => [], + 'sessions' => null, 'tokens' => null, 'memberships' => null, 'search' => implode(' ', [$userId, $email, $name]), @@ -632,25 +632,20 @@ App::delete('/v1/users/:userId/sessions/:sessionId') throw new Exception('User not found', 404, Exception::USER_NOT_FOUND); } - $sessions = $user->getAttribute('sessions', []); + $session = $dbForProject->getDocument('sessions', $sessionId); - foreach ($sessions as $key => $session) { /** @var Document $session */ - - if ($sessionId == $session->getId()) { - unset($sessions[$key]); - - $dbForProject->deleteDocument('sessions', $session->getId()); - - $user->setAttribute('sessions', $sessions); - - $events - ->setParam('eventData', $response->output($user, Response::MODEL_USER)) - ; - - $dbForProject->updateDocument('users', $user->getId(), $user); - } + if($session->isEmpty()) { + throw new Exception('User not found', 404, Exception::USER_SESSION_NOT_FOUND); } + $dbForProject->deleteDocument('sessions', $session->getId()); + + $dbForProject->deleteCachedDocument('users', $user->getId()); + + $events + ->setParam('eventData', $response->output($user, Response::MODEL_USER)) + ; + $usage ->setParam('users.update', 1) ->setParam('users.sessions.delete', 1) @@ -693,7 +688,7 @@ App::delete('/v1/users/:userId/sessions') $dbForProject->deleteDocument('sessions', $session->getId()); } - $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('sessions', [])); + $dbForProject->deleteCachedDocument('users', $user->getId()); $events ->setParam('eventData', $response->output($user, Response::MODEL_USER)) diff --git a/app/init.php b/app/init.php index e693906167..0c6c366fd7 100644 --- a/app/init.php +++ b/app/init.php @@ -301,6 +301,19 @@ Database::addFilter('subQueryWebhooks', } ); +Database::addFilter('subQuerySessions', + function($value) { + return null; + }, + function($value, Document $document, Database $database) { + $sessions = Authorization::skip(fn () => $database->find('sessions', [ + new Query('userId', Query::TYPE_EQUAL, [$document->getId()]) + ], $database->getIndexLimit(), 0, [])); + + return $sessions; + } +); + Database::addFilter('subQueryTokens', function($value) { return null; diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 892d417d9c..0beb7d01a8 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -208,13 +208,14 @@ class DeletesV1 extends Worker */ $userId = $document->getId(); - $user = $this->getProjectDB($projectId)->getDocument('users', $userId); // Delete all sessions of this user from the sessions table and update the sessions field of the user record $this->deleteByGroup('sessions', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) ], $this->getProjectDB($projectId)); - + + $this->getProjectDB($projectId)->deleteCachedDocument('users', $userId); + // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ new Query('userId', Query::TYPE_EQUAL, [$userId]) diff --git a/tests/e2e/Services/Teams/TeamsBaseServer.php b/tests/e2e/Services/Teams/TeamsBaseServer.php index 41dcc6c84c..5db4b628f6 100644 --- a/tests/e2e/Services/Teams/TeamsBaseServer.php +++ b/tests/e2e/Services/Teams/TeamsBaseServer.php @@ -204,7 +204,7 @@ trait TeamsBaseServer $this->assertEquals(1, $response['body']['total']); $this->assertIsInt($response['body']['total']); $this->assertIsInt($response['body']['dateCreated']); - + /** Delete User */ $user = $this->client->call(Client::METHOD_DELETE, '/users/' . $userUid, array_merge([