mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
delete account api
This commit is contained in:
@@ -103,7 +103,6 @@ RUN chmod +x /usr/local/bin/hamster && \
|
||||
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
||||
chmod +x /usr/local/bin/patch-recreate-repositories-documents && \
|
||||
chmod +x /usr/local/bin/patch-delete-project-collections && \
|
||||
chmod +x /usr/local/bin/patch-delete-console-users && \
|
||||
chmod +x /usr/local/bin/delete-orphaned-projects && \
|
||||
chmod +x /usr/local/bin/clear-card-cache && \
|
||||
chmod +x /usr/local/bin/calc-users-stats && \
|
||||
|
||||
@@ -165,6 +165,55 @@ App::post('/v1/account')
|
||||
->dynamic($user, Response::MODEL_ACCOUNT);
|
||||
});
|
||||
|
||||
App::delete('/v1/account')
|
||||
->desc('Delete account')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].delete')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.delete')
|
||||
->label('audits.resource', 'user/{request.userId}')
|
||||
->label('usage.metric', 'users.{scope}.requests.delete')
|
||||
->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)
|
||||
->inject('requestTimestamp')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
|
||||
$clone = clone $user;
|
||||
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->deleteDocument('users', $user->getId()));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $clone->getId())
|
||||
->setPayload($response->output($clone, Response::MODEL_ACCOUNT));
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
}
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$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'))
|
||||
;
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
|
||||
App::post('/v1/account/sessions/email')
|
||||
->alias('/v1/account/sessions')
|
||||
->desc('Create email session')
|
||||
@@ -2133,20 +2182,15 @@ App::patch('/v1/account/status')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->action(function (?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$clone = clone $user;
|
||||
$user->setAttribute('status', false);
|
||||
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->deleteDocument('users', $user->getId()));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone);
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $clone->getId())
|
||||
->setPayload($response->output($clone, Response::MODEL_ACCOUNT));
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($user, Response::MODEL_ACCOUNT));
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
@@ -2158,7 +2202,7 @@ App::patch('/v1/account/status')
|
||||
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic($clone, Response::MODEL_ACCOUNT);
|
||||
$response->dynamic($user, Response::MODEL_ACCOUNT);
|
||||
});
|
||||
|
||||
App::delete('/v1/account/sessions/:sessionId')
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-console-users $@
|
||||
@@ -20,7 +20,6 @@ use Appwrite\Platform\Tasks\CalcTierStats;
|
||||
use Appwrite\Platform\Tasks\Upgrade;
|
||||
use Appwrite\Platform\Tasks\DeleteOrphanedProjects;
|
||||
use Appwrite\Platform\Tasks\GetMigrationStats;
|
||||
use Appwrite\Platform\Tasks\PatchDeleteConsoleUsers;
|
||||
|
||||
class Tasks extends Service
|
||||
{
|
||||
@@ -44,9 +43,7 @@ class Tasks extends Service
|
||||
->addAction(Specs::getName(), new Specs())
|
||||
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||
->addAction(DeleteOrphanedProjects::getName(), new DeleteOrphanedProjects())
|
||||
->addAction(PatchDeleteConsoleUsers::getName(), new PatchDeleteConsoleUsers())
|
||||
->addAction(GetMigrationStats::getName(), new GetMigrationStats())
|
||||
->addAction(PatchDeleteConsoleUsers::getName(), new PatchDeleteConsoleUsers())
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Delete;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class PatchDeleteConsoleUsers extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'patch-delete-console-users';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Delete console users patch')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForDeletes')
|
||||
->callback(fn ($dbForConsole, $queueForDeletes) => $this->action($dbForConsole, $queueForDeletes));
|
||||
}
|
||||
|
||||
public function action(Database $dbForConsole, Delete $queueForDeletes): void
|
||||
{
|
||||
Console::info("Starting the patch");
|
||||
|
||||
$startTime = microtime(true);
|
||||
$query = [Query::equal('status', [false])];
|
||||
$this->foreachDocument($dbForConsole, 'users', $query, function ($user) use ($dbForConsole, $queueForDeletes) {
|
||||
$clone = clone $user;
|
||||
try {
|
||||
$dbForConsole->deleteDocument('users', $user->getId());
|
||||
$queueForDeletes
|
||||
->setProject(new Document([
|
||||
'$id' => ID::custom('console'),
|
||||
'$internalId' => ID::custom('console')
|
||||
]))
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($clone)
|
||||
->trigger();
|
||||
} catch (\Throwable $th) {
|
||||
Console::error("Unexpected error occurred with User ID {$clone->getId()}");
|
||||
Console::error('[Error] Type: ' . get_class($th));
|
||||
Console::error('[Error] Message: ' . $th->getMessage());
|
||||
Console::error('[Error] File: ' . $th->getFile());
|
||||
Console::error('[Error] Line: ' . $th->getLine());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$endTime = microtime(true);
|
||||
$timeTaken = $endTime - $startTime;
|
||||
|
||||
$hours = (int)($timeTaken / 3600);
|
||||
$timeTaken -= $hours * 3600;
|
||||
$minutes = (int)($timeTaken / 60);
|
||||
$timeTaken -= $minutes * 60;
|
||||
$seconds = (int)$timeTaken;
|
||||
$milliseconds = ($timeTaken - $seconds) * 1000;
|
||||
Console::info("Delete console users patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds)");
|
||||
}
|
||||
|
||||
protected function foreachDocument(Database $database, string $collection, array $queries = [], callable $callback = null): void
|
||||
{
|
||||
$limit = 1000;
|
||||
$results = [];
|
||||
$sum = $limit;
|
||||
$latestDocument = null;
|
||||
|
||||
while ($sum === $limit) {
|
||||
$newQueries = $queries;
|
||||
|
||||
if ($latestDocument != null) {
|
||||
array_unshift($newQueries, Query::cursorAfter($latestDocument));
|
||||
}
|
||||
$newQueries[] = Query::limit($limit);
|
||||
$results = $database->find($collection, $newQueries);
|
||||
|
||||
if (empty($results)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
foreach ($results as $document) {
|
||||
if (is_callable($callback)) {
|
||||
$callback($document);
|
||||
}
|
||||
}
|
||||
$latestDocument = $results[array_key_last($results)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,82 @@ class AccountCustomClientTest extends Scope
|
||||
return [];
|
||||
}
|
||||
|
||||
public function testDeleteAccount()
|
||||
{
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name (blocked)';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$response['body']['$id'];
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
|
||||
$response['body']['$id'];
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 204);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
}
|
||||
|
||||
|
||||
public function testBlockedAccount(): array
|
||||
{
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
@@ -221,8 +297,6 @@ class AccountCustomClientTest extends Scope
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
], [
|
||||
'status' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 200);
|
||||
|
||||
Reference in New Issue
Block a user