mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge pull request #11787 from appwrite/CLO-4175-allow-delete-with-memberships
Allow deleting user account with active memberships
This commit is contained in:
@@ -474,19 +474,26 @@ Http::delete('/v1/account')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->inject('authorization')
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes, Authorization $authorization) {
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// get all memberships
|
||||
$memberships = $user->getAttribute('memberships', []);
|
||||
foreach ($memberships as $membership) {
|
||||
// prevent deletion if at least one active membership
|
||||
if ($membership->getAttribute('confirm', false)) {
|
||||
throw new Exception(Exception::USER_DELETION_PROHIBITED);
|
||||
if (!$membership->getAttribute('confirm', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
|
||||
if ($team->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Team is left as-is — we don't promote non-owner members to owner.
|
||||
// Orphan teams are cleaned up later by Cloud's inactive project cleanup.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ class Delete extends Action
|
||||
if ($team->getAttribute('userInternalId') === $membership->getAttribute('userInternalId')) {
|
||||
$membership = $dbForProject->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$team->getSequence()]),
|
||||
Query::equal('confirm', [true]),
|
||||
]);
|
||||
|
||||
if (!$membership->isEmpty()) {
|
||||
|
||||
@@ -14,7 +14,12 @@ class AccountConsoleClientTest extends Scope
|
||||
use ProjectConsole;
|
||||
use SideClient;
|
||||
|
||||
public function testDeleteAccount(): void
|
||||
/**
|
||||
* Test that account deletion succeeds even with active team memberships.
|
||||
* When the user is the sole owner and only member of a team, the team
|
||||
* should be cleaned up automatically.
|
||||
*/
|
||||
public function testDeleteAccountWithMembership(): void
|
||||
{
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
@@ -46,7 +51,7 @@ class AccountConsoleClientTest extends Scope
|
||||
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
// create team
|
||||
// Create team — user becomes sole owner and only member
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
@@ -58,7 +63,51 @@ class AccountConsoleClientTest extends Scope
|
||||
]);
|
||||
$this->assertEquals($team['headers']['status-code'], 201);
|
||||
|
||||
$teamId = $team['body']['$id'];
|
||||
// Account deletion should succeed even with active membership
|
||||
$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(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that account deletion works when the user has no team memberships.
|
||||
*/
|
||||
public function testDeleteAccountWithoutMembership(): void
|
||||
{
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
$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,
|
||||
]);
|
||||
|
||||
$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);
|
||||
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
@@ -67,27 +116,7 @@ class AccountConsoleClientTest extends Scope
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
// DELETE TEAM
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/teams/' . $teamId, 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);
|
||||
|
||||
$this->assertEventually(function () use ($session) {
|
||||
$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(204, $response['headers']['status-code']);
|
||||
}, 10_000, 500);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testSessionAlert(): void
|
||||
|
||||
Reference in New Issue
Block a user