1
0
Fork 0
mirror of synced 2024-06-02 19:04:49 +12:00

feat: team deletion is now synchronous

This commit is contained in:
Binyamin Yawitz 2024-05-06 10:22:57 -04:00
parent b2c57e2d8b
commit e19d2b5a28
3 changed files with 104 additions and 144 deletions

View file

@ -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');

View file

@ -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';

View file

@ -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