1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00

feat: account update status

This commit is contained in:
Torsten Dittmann 2022-05-16 11:58:17 +02:00
parent e68c48a713
commit bb0db796f7
6 changed files with 79 additions and 128 deletions

View file

@ -1090,18 +1090,7 @@ $collections = [
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'deleted',
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
]
],
'indexes' => [
[
@ -1117,14 +1106,7 @@ $collections = [
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
[
'$id' => '_key_deleted_email',
'type' => Database::INDEX_KEY,
'attributes' => ['deleted', 'email'],
'lengths' => [0, 320],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
]
],
],

View file

@ -81,9 +81,7 @@ App::post('/v1/account')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$total = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_USERS);
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
@ -108,8 +106,7 @@ App::post('/v1/account')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
@ -170,7 +167,6 @@ App::post('/v1/account/sessions')
$protocol = $request->getProtocol();
$profile = $dbForProject->findOne('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
new Query('email', Query::TYPE_EQUAL, [$email])]
);
@ -480,7 +476,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
if ($isVerified === true) {
// Get user by email address
$user = $dbForProject->findOne('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
new Query('email', Query::TYPE_EQUAL, [$email])]
);
}
@ -489,7 +484,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$total = $dbForProject->count('users', [new Query('deleted', Query::TYPE_EQUAL, [false])], APP_LIMIT_USERS);
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
@ -514,8 +509,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
@ -663,9 +657,7 @@ App::post('/v1/account/sessions/magic-url')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$total = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_USERS);
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
@ -689,8 +681,7 @@ App::post('/v1/account/sessions/magic-url')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email]),
'deleted' => false
'search' => implode(' ', [$userId, $email])
])));
}
@ -790,7 +781,7 @@ App::put('/v1/account/sessions/magic-url')
$user = Authorization::skip(fn() => $dbForProject->getDocument('users', $userId));
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -925,9 +916,7 @@ App::post('/v1/account/sessions/anonymous')
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
if ($limit !== 0) {
$total = $dbForProject->count('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
], APP_LIMIT_USERS);
$total = $dbForProject->count('users', max: APP_LIMIT_USERS);
if ($total >= $limit) {
throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501, Exception::USER_COUNT_EXCEEDED);
@ -951,8 +940,7 @@ App::post('/v1/account/sessions/anonymous')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => $userId,
'deleted' => false
'search' => $userId
])));
// Create session token
@ -1469,17 +1457,18 @@ App::patch('/v1/account/prefs')
$response->dynamic($user, Response::MODEL_USER);
});
App::delete('/v1/account')
->desc('Delete Account')
App::patch('/v1/account/status')
->desc('Update Account Status')
->groups(['api', 'account'])
->label('event', 'users.[userId].delete')
->label('event', 'users.[userId].update.status')
->label('scope', 'account')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/account/delete.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk.method', 'updateStatus')
->label('sdk.description', '/docs/references/account/update-status.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->inject('request')
->inject('response')
->inject('user')
@ -1496,28 +1485,15 @@ App::delete('/v1/account')
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
$protocol = $request->getProtocol();
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
// TODO Seems to be related to users.php/App::delete('/v1/users/:userId'). Can we share code between these two? Do todos below apply to users.php?
// TODO delete all tokens or only current session?
// TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later
/**
* Data to delete
* * Tokens
* * Memberships
*/
$audits
->setResource('user/' . $user->getId())
->setPayload($response->output($user, Response::MODEL_USER))
;
->setPayload($response->output($user, Response::MODEL_USER));
$events
->setParam('userId', $user->getId())
->setPayload($response->output($user, Response::MODEL_USER))
;
->setPayload($response->output($user, Response::MODEL_USER));
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
@ -1525,11 +1501,7 @@ App::delete('/v1/account')
$usage->setParam('users.delete', 1);
$response
->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->noContent()
;
$response->dynamic($user, Response::MODEL_USER);
});
App::delete('/v1/account/sessions/:sessionId')
@ -1839,7 +1811,6 @@ App::post('/v1/account/recovery')
$email = \strtolower($email);
$profile = $dbForProject->findOne('users', [
new Query('deleted', Query::TYPE_EQUAL, [false]),
new Query('email', Query::TYPE_EQUAL, [$email])
]);
@ -1942,7 +1913,7 @@ App::put('/v1/account/recovery')
$profile = $dbForProject->getDocument('users', $userId);
if ($profile->isEmpty() || $profile->getAttribute('deleted')) {
if ($profile->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}

View file

@ -348,8 +348,7 @@ App::post('/v1/teams/:teamId/memberships')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
'search' => implode(' ', [$userId, $email, $name])
])));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);

View file

@ -68,8 +68,7 @@ App::post('/v1/users')
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'deleted' => false
'search' => implode(' ', [$userId, $email, $name])
]));
} catch (Duplicate $th) {
throw new Exception('Account already exists', 409, Exception::USER_ALREADY_EXISTS);
@ -120,9 +119,7 @@ App::get('/v1/users')
}
}
$queries = [
new Query('deleted', Query::TYPE_EQUAL, [false])
];
$queries = [];
if (!empty($search)) {
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
@ -160,7 +157,7 @@ App::get('/v1/users/:userId')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -192,7 +189,7 @@ App::get('/v1/users/:userId/prefs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -228,7 +225,7 @@ App::get('/v1/users/:userId/sessions')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -274,7 +271,7 @@ App::get('/v1/users/:userId/memberships')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -324,7 +321,7 @@ App::get('/v1/users/:userId/logs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -409,7 +406,7 @@ App::patch('/v1/users/:userId/status')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -452,7 +449,7 @@ App::patch('/v1/users/:userId/verification')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -495,7 +492,7 @@ App::patch('/v1/users/:userId/name')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -543,7 +540,7 @@ App::patch('/v1/users/:userId/password')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -590,7 +587,7 @@ App::patch('/v1/users/:userId/email')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -650,7 +647,7 @@ App::patch('/v1/users/:userId/prefs')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -684,15 +681,17 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->inject('dbForProject')
->inject('events')
->inject('usage')
->action(function ($userId, $sessionId, $response, $dbForProject, $events, $usage) {
->inject('deletes')
->action(function ($userId, $sessionId, $response, $dbForProject, $events, $usage, $deletes) {
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
/** @var Appwrite\Event\Event $events */
/** @var Appwrite\Stats\Stats $usage */
/** @var Appwrite\Event\Delete $deletes */
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -716,6 +715,11 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
->setParam('sessionId', $sessionId)
;
$deletes
->setType(DELETE_TYPE_USERS)
->setDocument($user)
;
$response->noContent();
});
@ -743,7 +747,7 @@ App::delete('/v1/users/:userId/sessions')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
@ -795,28 +799,14 @@ App::delete('/v1/users/:userId')
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty() || $user->getAttribute('deleted')) {
if ($user->isEmpty()) {
throw new Exception('User not found', 404, Exception::USER_NOT_FOUND);
}
/**
* DO NOT DELETE THE USER RECORD ITSELF.
* WE RETAIN THE USER RECORD TO RESERVE THE USER ID AND ENSURE THAT THE USER ID IS NOT REUSED.
*/
// clone user object to send to workers
$clone = clone $user;
$user
->setAttribute("name", null)
->setAttribute("email", null)
->setAttribute("password", null)
->setAttribute("deleted", true)
->setAttribute("tokens", null)
->setAttribute("search", null)
;
$dbForProject->updateDocument('users', $userId, $user);
$dbForProject->deleteDocument('users', $userId);
$deletes
->setType(DELETE_TYPE_DOCUMENT)

View file

@ -31,7 +31,8 @@ class DeletesV1 extends Worker
*/
protected $consoleDB = null;
public function getName(): string {
public function getName(): string
{
return "deletes";
}
@ -202,11 +203,6 @@ class DeletesV1 extends Worker
*/
protected function deleteUser(Document $document, string $projectId): void
{
/**
* DO NOT DELETE THE USER RECORD ITSELF.
* WE RETAIN THE USER RECORD TO RESERVE THE USER ID AND ENSURE THAT THE USER ID IS NOT REUSED.
*/
$userId = $document->getId();
// Delete all sessions of this user from the sessions table and update the sessions field of the user record
@ -225,9 +221,14 @@ class DeletesV1 extends Worker
$teamId = $document->getAttribute('teamId');
$team = $this->getProjectDB($projectId)->getDocument('teams', $teamId);
if (!$team->isEmpty()) {
$team = $this->getProjectDB($projectId)->updateDocument('teams', $teamId, new Document(\array_merge($team->getArrayCopy(), [
'total' => \max($team->getAttribute('total', 0) - 1, 0), // Ensure that total >= 0
])));
$team = $this
->getProjectDB($projectId)
->updateDocument(
'teams',
$teamId,
// Ensure that total >= 0
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0))
);
}
}
});
@ -348,7 +349,7 @@ class DeletesV1 extends Worker
*/
Console::info("Deleting builds for function " . $functionId);
$storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId);
foreach ($deploymentIds as $deploymentId) {
foreach ($deploymentIds as $deploymentId) {
$this->deleteByGroup('builds', [
new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId])
], $dbForProject, function (Document $document) use ($storageBuilds, $deploymentId) {
@ -362,7 +363,7 @@ class DeletesV1 extends Worker
/**
* Delete Executions
*/
*/
Console::info("Deleting executions for function " . $functionId);
$this->deleteByGroup('executions', [
new Query('functionId', Query::TYPE_EQUAL, [$functionId])
@ -380,7 +381,6 @@ class DeletesV1 extends Worker
Console::error($th->getMessage());
}
}
}
/**
@ -474,7 +474,7 @@ class DeletesV1 extends Worker
$chunk++;
/** @var string[] $projectIds */
$projectIds = array_map(fn(Document $project) => $project->getId(), $projects);
$projectIds = array_map(fn (Document $project) => $project->getId(), $projects);
$sum = count($projects);
@ -533,21 +533,21 @@ class DeletesV1 extends Worker
$consoleDB = $this->getConsoleDB();
// If domain has certificate generated
if(isset($document['certificateId'])) {
if (isset($document['certificateId'])) {
$domainUsingCertificate = $consoleDB->findOne('domains', [
new Query('certificateId', Query::TYPE_EQUAL, [$document['certificateId']])
]);
if(!$domainUsingCertificate) {
if (!$domainUsingCertificate) {
$mainDomain = App::getEnv('_APP_DOMAIN_TARGET', '');
if($mainDomain === $document->getAttribute('domain')) {
if ($mainDomain === $document->getAttribute('domain')) {
$domainUsingCertificate = $mainDomain;
}
}
// If certificate is still used by some domain, mark we can't delete.
// Current domain should not be found, because we only have copy. Original domain is already deleted from database.
if($domainUsingCertificate) {
if ($domainUsingCertificate) {
Console::warning("Skipping certificate deletion, because a domain is still using it.");
return;
}
@ -559,7 +559,7 @@ class DeletesV1 extends Worker
if ($domain && $checkTraversal && is_dir($directory)) {
// Delete certificate document, so Appwrite is aware of change
if(isset($document['certificateId'])) {
if (isset($document['certificateId'])) {
$consoleDB->deleteDocument('certificates', $document['certificateId']);
}
@ -577,8 +577,8 @@ class DeletesV1 extends Worker
$dbForProject = $this->getProjectDB($projectId);
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
$device = new Local(APP_STORAGE_UPLOADS.'/app-'.$projectId);
$device = new Local(APP_STORAGE_UPLOADS . '/app-' . $projectId);
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
case Storage::DEVICE_S3:
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
@ -597,7 +597,7 @@ class DeletesV1 extends Worker
$device = new DOSpaces(APP_STORAGE_UPLOADS . '/app-' . $projectId, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
break;
}
$device->deletePath($document->getId());
}
}

View file

@ -256,6 +256,15 @@ class V13 extends Migration
}
break;
case 'users':
/**
* Remove deleted users.
*/
if ($document->getAttribute('deleted', false) === true) {
$this->projectDB->deleteDocument('users', $document->getId());
}
break;
}
return $document;