feat: team deletion is now synchronous
This commit is contained in:
parent
b2c57e2d8b
commit
e19d2b5a28
|
@ -59,7 +59,7 @@ App::post('/v1/teams')
|
|||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->param('teamId', '', new CustomId(), 'Team ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', null, new Text(128), 'Team name. Max length: 128 chars.')
|
||||
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', true)
|
||||
->param('roles', ['owner'], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of '.APP_LIMIT_ARRAY_PARAMS_SIZE.' roles are allowed, each 32 characters long.', true)
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
|
@ -88,8 +88,8 @@ App::post('/v1/teams')
|
|||
throw new Exception(Exception::TEAM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
|
||||
if (!\in_array('owner', $roles)) {
|
||||
if (! $isPrivilegedUser && ! $isAppUser) { // Don't add user on server mode
|
||||
if (! \in_array('owner', $roles)) {
|
||||
$roles[] = 'owner';
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ App::post('/v1/teams')
|
|||
'joined' => DateTime::now(),
|
||||
'confirm' => true,
|
||||
'secret' => '',
|
||||
'search' => implode(' ', [$membershipId, $user->getId()])
|
||||
'search' => implode(' ', [$membershipId, $user->getId()]),
|
||||
]);
|
||||
|
||||
$membership = $dbForProject->createDocument('memberships', $membership);
|
||||
|
@ -122,7 +122,7 @@ App::post('/v1/teams')
|
|||
|
||||
$queueForEvents->setParam('teamId', $team->getId());
|
||||
|
||||
if (!empty($user->getId())) {
|
||||
if (! empty($user->getId())) {
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ App::get('/v1/teams')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of '.APP_LIMIT_ARRAY_PARAMS_SIZE.' queries are allowed, each '.APP_LIMIT_ARRAY_ELEMENT_SIZE.' characters long. You may filter on the following attributes: '.implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
@ -155,7 +155,7 @@ App::get('/v1/teams')
|
|||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
if (! empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
|
@ -338,10 +338,12 @@ App::delete('/v1/teams/:teamId')
|
|||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->action(function (string $teamId, Response $response, Document $project, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
|
@ -349,18 +351,39 @@ App::delete('/v1/teams/:teamId')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('teams', $teamId)) {
|
||||
if (! $dbForProject->deleteDocument('teams', $teamId)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
$teamInternalId = $team->getInternalId();
|
||||
|
||||
$this->deleteByGroup(
|
||||
'memberships',
|
||||
[
|
||||
Query::equal('teamInternalId', [$teamInternalId]),
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $membership) use ($dbForProject) {
|
||||
$userId = $membership->getAttribute('userId');
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
}
|
||||
);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::equal('teamInternalId', [$teamInternalId]),
|
||||
]);
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($project);
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM))
|
||||
;
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
@ -386,7 +409,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of '.APP_LIMIT_ARRAY_PARAMS_SIZE.' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
|
@ -402,7 +425,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
if (empty($url)) {
|
||||
if (!$isAPIKey && !$isPrivilegedUser) {
|
||||
if (! $isAPIKey && ! $isPrivilegedUser) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'URL is required');
|
||||
}
|
||||
}
|
||||
|
@ -413,7 +436,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
if (! $isPrivilegedUser && ! $isAppUser && empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED);
|
||||
}
|
||||
|
||||
|
@ -424,28 +447,28 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
if (!empty($userId)) {
|
||||
if (! empty($userId)) {
|
||||
$invitee = $dbForProject->getDocument('users', $userId);
|
||||
if ($invitee->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND, 'User with given userId doesn\'t exist.', 404);
|
||||
}
|
||||
if (!empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
if (! empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and email doesn\'t match', 409);
|
||||
}
|
||||
if (!empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
if (! empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and phone doesn\'t match', 409);
|
||||
}
|
||||
$email = $invitee->getAttribute('email', '');
|
||||
$phone = $invitee->getAttribute('phone', '');
|
||||
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
|
||||
} elseif (!empty($email)) {
|
||||
} elseif (! empty($email)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
if (! empty($invitee) && ! empty($phone) && $invitee->getAttribute('phone', '') !== $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409);
|
||||
}
|
||||
} elseif (!empty($phone)) {
|
||||
} elseif (! empty($phone)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
if (! empty($invitee) && ! empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409);
|
||||
}
|
||||
}
|
||||
|
@ -465,7 +488,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) {
|
||||
if ($identityWithMatchingEmail !== false && ! $identityWithMatchingEmail->isEmpty()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
|
@ -507,9 +530,9 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
}
|
||||
}
|
||||
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
if (! $isOwner && ! $isPrivilegedUser && ! $isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
}
|
||||
|
||||
|
@ -534,7 +557,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
|
||||
'confirm' => ($isPrivilegedUser || $isAppUser),
|
||||
'secret' => Auth::hash($secret),
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()])
|
||||
'search' => implode(' ', [$membershipId, $invitee->getId()]),
|
||||
]);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
|
||||
|
@ -557,37 +580,37 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url = Template::unParseURL($url);
|
||||
if (!empty($email)) {
|
||||
if (! empty($email)) {
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
|
||||
$body = $locale->getText("emails.invitation.body");
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.invitation-' . $locale->default] ?? [];
|
||||
$body = $locale->getText('emails.invitation.body');
|
||||
$subject = \sprintf($locale->getText('emails.invitation.subject'), $team->getAttribute('name'), $projectName);
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.invitation-'.$locale->default] ?? [];
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message = Template::fromFile(__DIR__.'/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $body, escapeHtml: false)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"));
|
||||
->setParam('{{hello}}', $locale->getText('emails.invitation.hello'))
|
||||
->setParam('{{footer}}', $locale->getText('emails.invitation.footer'))
|
||||
->setParam('{{thanks}}', $locale->getText('emails.invitation.thanks'))
|
||||
->setParam('{{signature}}', $locale->getText('emails.invitation.signature'));
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
$smtpEnabled = $smtp['enabled'] ?? false;
|
||||
|
||||
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyTo = "";
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server');
|
||||
$replyTo = '';
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
if (! empty($smtp['senderEmail'])) {
|
||||
$senderEmail = $smtp['senderEmail'];
|
||||
}
|
||||
if (!empty($smtp['senderName'])) {
|
||||
if (! empty($smtp['senderName'])) {
|
||||
$senderName = $smtp['senderName'];
|
||||
}
|
||||
if (!empty($smtp['replyTo'])) {
|
||||
if (! empty($smtp['replyTo'])) {
|
||||
$replyTo = $smtp['replyTo'];
|
||||
}
|
||||
|
||||
|
@ -598,14 +621,14 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
if (! empty($customTemplate)) {
|
||||
if (! empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
}
|
||||
if (!empty($customTemplate['senderName'])) {
|
||||
if (! empty($customTemplate['senderName'])) {
|
||||
$senderName = $customTemplate['senderName'];
|
||||
}
|
||||
if (!empty($customTemplate['replyTo'])) {
|
||||
if (! empty($customTemplate['replyTo'])) {
|
||||
$replyTo = $customTemplate['replyTo'];
|
||||
}
|
||||
|
||||
|
@ -626,7 +649,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
'user' => $user->getAttribute('name'),
|
||||
'team' => $team->getAttribute('name'),
|
||||
'redirect' => $url,
|
||||
'project' => $projectName
|
||||
'project' => $projectName,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
|
@ -635,17 +658,16 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->setVariables($emailVariables)
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
->trigger();
|
||||
} elseif (! empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl');
|
||||
$message = Template::fromFile(__DIR__.'/../../config/locale/templates/sms-base.tpl');
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-' . $locale->default] ?? [];
|
||||
if (!empty($customTemplate)) {
|
||||
$customTemplate = $project->getAttribute('templates', [])['sms.invitation-'.$locale->default] ?? [];
|
||||
if (! empty($customTemplate)) {
|
||||
$message = $customTemplate['message'];
|
||||
}
|
||||
|
||||
|
@ -669,8 +691,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
;
|
||||
->setParam('membershipId', $membership->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
@ -696,7 +717,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/memberships')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of '.APP_LIMIT_ARRAY_PARAMS_SIZE.' queries are allowed, each '.APP_LIMIT_ARRAY_ELEMENT_SIZE.' characters long. You may filter on the following attributes: '.implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
@ -714,7 +735,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
if (! empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
|
@ -753,7 +774,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
max: APP_LIMIT_COUNT
|
||||
);
|
||||
|
||||
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
|
||||
$memberships = array_filter($memberships, fn (Document $membership) => ! empty($membership->getAttribute('userId')));
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $team) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
@ -765,7 +786,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
if (! $totpEnabled && ! $emailEnabled && ! $phoneEnabled) {
|
||||
$mfa = false;
|
||||
}
|
||||
}
|
||||
|
@ -774,8 +795,7 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
->setAttribute('mfa', $mfa)
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
|
||||
return $membership;
|
||||
}, $memberships);
|
||||
|
@ -827,7 +847,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
|||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
if (! $totpEnabled && ! $emailEnabled && ! $phoneEnabled) {
|
||||
$mfa = false;
|
||||
}
|
||||
}
|
||||
|
@ -836,8 +856,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->setAttribute('mfa', $mfa)
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
});
|
||||
|
@ -858,7 +877,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings. Use this param to set the user\'s roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of '.APP_LIMIT_ARRAY_PARAMS_SIZE.' roles are allowed, each 32 characters long.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
|
@ -883,9 +902,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
|||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
if (! $isOwner && ! $isPrivilegedUser && ! $isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to modify roles');
|
||||
}
|
||||
|
||||
|
@ -963,7 +982,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
if ($userId !== $membership->getAttribute('userId')) {
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user ('.$user->getAttribute('email').')');
|
||||
}
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
|
@ -971,7 +990,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
}
|
||||
|
||||
if ($membership->getAttribute('userInternalId') !== $user->getInternalId()) {
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user ('.$user->getAttribute('email').')');
|
||||
}
|
||||
|
||||
if ($membership->getAttribute('confirm') === true) {
|
||||
|
@ -980,8 +999,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
|
||||
$membership // Attach user to team
|
||||
->setAttribute('joined', DateTime::now())
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
->setAttribute('confirm', true);
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
|
@ -1005,7 +1023,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration),
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
|
@ -1027,25 +1045,22 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
|||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
;
|
||||
->setParam('membershipId', $membership->getId());
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (! Config::getParam('domainVerification')) {
|
||||
$response
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
|
||||
;
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ($protocol == 'https'), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ($protocol == 'https'), true, Config::getParam('cookieSamesite'));
|
||||
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
@ -1109,8 +1124,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
|||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setParam('membershipId', $membership->getId())
|
||||
->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP))
|
||||
;
|
||||
->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
@ -1151,13 +1165,13 @@ App::get('/v1/teams/:teamId/logs')
|
|||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
$resource = 'team/' . $team->getId();
|
||||
$resource = 'team/'.$team->getId();
|
||||
$logs = $audit->getLogsByResource($resource, $limit, $offset);
|
||||
|
||||
$output = [];
|
||||
|
||||
foreach ($logs as $i => &$log) {
|
||||
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
$log['userAgent'] = (! empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
||||
|
||||
$detector = new Detector($log['userAgent']);
|
||||
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
||||
|
@ -1185,14 +1199,14 @@ App::get('/v1/teams/:teamId/logs')
|
|||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel']
|
||||
'deviceModel' => $device['deviceModel'],
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
if ($record) {
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.'.strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
|
||||
} else {
|
||||
$output[$i]['countryCode'] = '--';
|
||||
$output[$i]['countryName'] = $locale->getText('locale.country.unknown');
|
||||
|
|
|
@ -167,7 +167,6 @@ const DELETE_TYPE_PROJECTS = 'projects';
|
|||
const DELETE_TYPE_FUNCTIONS = 'functions';
|
||||
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
|
||||
const DELETE_TYPE_USERS = 'users';
|
||||
const DELETE_TYPE_TEAMS = 'teams';
|
||||
const DELETE_TYPE_EXECUTIONS = 'executions';
|
||||
const DELETE_TYPE_AUDIT = 'audit';
|
||||
const DELETE_TYPE_ABUSE = 'abuse';
|
||||
|
|
|
@ -93,12 +93,6 @@ class Deletes extends Action
|
|||
case DELETE_TYPE_USERS:
|
||||
$this->deleteUser($getProjectDB, $document, $project);
|
||||
break;
|
||||
case DELETE_TYPE_TEAMS:
|
||||
$this->deleteMemberships($getProjectDB, $document, $project);
|
||||
if ($project->getId() === 'console') {
|
||||
$this->deleteProjectsByTeam($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $document);
|
||||
}
|
||||
break;
|
||||
case DELETE_TYPE_BUCKETS:
|
||||
$this->deleteBucket($getProjectDB, $deviceForFiles, $document, $project);
|
||||
break;
|
||||
|
@ -410,53 +404,6 @@ class Deletes extends Action
|
|||
], $dbForProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $document teams document
|
||||
* @param Document $project
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteMemberships(callable $getProjectDB, Document $document, Document $project): void
|
||||
{
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$teamInternalId = $document->getInternalId();
|
||||
|
||||
// Delete Memberships
|
||||
$this->deleteByGroup(
|
||||
'memberships',
|
||||
[
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
],
|
||||
$dbForProject,
|
||||
function (Document $membership) use ($dbForProject) {
|
||||
$userId = $membership->getAttribute('userId');
|
||||
$dbForProject->purgeCachedDocument('users', $userId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForConsole
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Conflict
|
||||
* @throws Restricted
|
||||
* @throws Structure
|
||||
*/
|
||||
private function deleteProjectsByTeam(Database $dbForConsole, callable $getProjectDB, Device $deviceForFiles, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, Document $document): void
|
||||
{
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()])
|
||||
]);
|
||||
foreach ($projects as $project) {
|
||||
$this->deleteProject($dbForConsole, $getProjectDB, $deviceForFiles, $deviceForFunctions, $deviceForBuilds, $deviceForCache, $project);
|
||||
$dbForConsole->deleteDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForConsole
|
||||
|
|
Loading…
Reference in a new issue