diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c9b678302..2cc4c700f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -141,7 +141,7 @@ jobs: run: | docker load --input /tmp/${{ env.IMAGE }}.tar docker compose up -d - sleep 10 + sleep 25 - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug diff --git a/.gitmodules b/.gitmodules index bed812bea0..e259782156 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = chore-update-sdk + branch = 1.5.x diff --git a/Dockerfile b/Dockerfile index ccb8f8bac1..d7343dd71d 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.4.4 as final +FROM appwrite/base:0.9.0 as final LABEL maintainer="team@appwrite.io" diff --git a/app/config/locale/templates.php b/app/config/locale/templates.php index f2672c04a0..ac5a2acf1d 100644 --- a/app/config/locale/templates.php +++ b/app/config/locale/templates.php @@ -6,10 +6,12 @@ return [ 'magicSession', 'recovery', 'invitation', + 'mfaChallenge' ], 'sms' => [ 'verification', 'login', - 'invitation' + 'invitation', + 'mfaChallenge' ] ]; diff --git a/app/config/locale/templates/email-magic-url.tpl b/app/config/locale/templates/email-magic-url.tpl index 21988c5bc1..def1ea2395 100644 --- a/app/config/locale/templates/email-magic-url.tpl +++ b/app/config/locale/templates/email-magic-url.tpl @@ -1,4 +1,4 @@ -
{{hello}}
+{{hello}},
{{optionButton}}
diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl new file mode 100644 index 0000000000..cf09448ca5 --- /dev/null +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -0,0 +1,16 @@ +{{hello}},
+ +{{description}}
+ +|
+ {{otp}} + |
+
{{clientInfo}}
+ +{{thanks}}
+{{signature}}
diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index 84802c1603..9552185f84 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -1,4 +1,4 @@ -{{hello}}
+{{hello}},
{{description}}
diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index dfa5ebe32a..22a132964e 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -4,36 +4,42 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", - "emails.verification.hello": "Hey {{user}}", - "emails.verification.body": "Follow this link to verify your email address.", + "emails.verification.hello": "Hello {{user}}", + "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", "emails.verification.thanks": "Thanks", "emails.verification.signature": "{{project}} team", "emails.magicSession.subject": "{{project}} Login", - "emails.magicSession.hello": "Hello,", - "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{project}} account. This link will expire in 1 hour.", + "emails.magicSession.hello": "Hello {{user}}", + "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{b}}{{project}}{{/b}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", - "emails.magicSession.clientInfo": "This sign in was requested using {{agentClient}} on {{agentDevice}} {{agentOs}}. If you didn't request the sign in, you can safely ignore this email.", - "emails.magicSession.securityPhrase": "Security phrase for this email is {{phrase}}. You can trust this email if this phrase matches the phrase shown during sign in.", + "emails.magicSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", + "emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", - "emails.otpSession.hello": "Hello,", - "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{project}} account. This code will expire in 15 minutes.", - "emails.otpSession.clientInfo": "This sign in was requested using {{agentClient}} on {{agentDevice}} {{agentOs}}. If you didn't request the sign in, you can safely ignore this email.", - "emails.otpSession.securityPhrase": "Security phrase for this email is {{phrase}}. You can trust this email if this phrase matches the phrase shown during sign in.", + "emails.otpSession.hello": "Hello {{user}}", + "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", + "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", + "emails.otpSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", + "emails.mfaChallenge.subject": "Verification Code for {{project}}", + "emails.mfaChallenge.hello": "Hello {{user}}", + "emails.mfaChallenge.description": "Enter the following verification code to verify your email and activate two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", + "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", + "emails.mfaChallenge.thanks": "Thanks,", + "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", "emails.recovery.hello": "Hello {{user}}", - "emails.recovery.body": "Follow this link to reset your {{project}} password.", + "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", "emails.recovery.footer": "If you didn’t ask to reset your password, you can ignore this message.", "emails.recovery.thanks": "Thanks", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", - "emails.invitation.hello": "Hello", - "emails.invitation.body": "This mail was sent to you because {{owner}} wanted to invite you to become a member of the {{team}} team at {{project}}.", + "emails.invitation.hello": "Hello {{user}}", + "emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", "emails.invitation.thanks": "Thanks", "emails.invitation.signature": "{{project}} team", diff --git a/app/console b/app/console index 44edd461c6..c72ba12e47 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 44edd461c6036cb462047c1424b80f0903cdc15e +Subproject commit c72ba12e479b0d3d9b3f4e48e01625c12a38fd7f diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 22d2776daf..eec259f321 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1259,15 +1259,16 @@ App::post('/v1/account/tokens/magic-url') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ - 'user' => '', - 'team' => '', + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates + 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'redirect' => $url, - 'agentDevice' => '' . ( $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN') . '', - 'agentClient' => '' . ($agentClient['clientName'] ?? 'UNKNOWN') . '', - 'agentOs' => '' . ($agentOs['osName'] ?? 'UNKNOWN') . '', - 'phrase' => '' . (!empty($phrase) ? $phrase : '') . '' + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + 'phrase' => !empty($phrase) ? $phrase : '', + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -1487,15 +1488,16 @@ App::post('/v1/account/tokens/email') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}} ,{{team}}, {{project}} and {{otp}} are required in the templates */ - 'user' => '', - 'team' => '', + // {{user}}, {{project}} and {{otp}} are required in the templates + 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'otp' => $tokenSecret, - 'agentDevice' => '' . ( $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN') . '', - 'agentClient' => '' . ($agentClient['clientName'] ?? 'UNKNOWN') . '', - 'agentOs' => '' . ($agentOs['osName'] ?? 'UNKNOWN') . '', - 'phrase' => '' . (!empty($phrase) ? $phrase : '') . '' + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + 'phrase' => !empty($phrase) ? $phrase : '', + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -2953,11 +2955,12 @@ App::post('/v1/account/recovery') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $profile->getAttribute('name'), - 'team' => '', 'redirect' => $url, - 'project' => $projectName + 'project' => $projectName, + // TODO: remove unnecessary team variable from this email + 'team' => '' ]; $queueForMails @@ -3200,11 +3203,12 @@ App::post('/v1/account/verification') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $user->getAttribute('name'), - 'team' => '', 'redirect' => $url, - 'project' => $projectName + 'project' => $projectName, + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -3696,7 +3700,7 @@ App::post('/v1/account/mfa/challenge') ->label('audits.userId', '{response.userId}') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'create2FAChallenge') + ->label('sdk.method', 'createChallenge') ->label('sdk.description', '/docs/references/account/create-2fa-challenge.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -3707,11 +3711,13 @@ App::post('/v1/account/mfa/challenge') ->inject('response') ->inject('dbForProject') ->inject('user') + ->inject('locale') + ->inject('project') + ->inject('request') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('queueForMails') - ->inject('locale') - ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, Locale $locale) { + ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails) { $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); $code = Auth::codeGenerator(); @@ -3743,6 +3749,22 @@ App::post('/v1/account/mfa/challenge') throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); } + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); + + $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; + if (!empty($customTemplate)) { + $message = $customTemplate['message'] ?? $message; + } + + $messageContent = Template::fromString($locale->getText("sms.verification.body")); + $messageContent + ->setParam('{{project}}', $project->getAttribute('name')) + ->setParam('{{secret}}', $code); + $messageContent = \strip_tags($messageContent->render()); + $message = $message->setParam('{{token}}', $messageContent); + + $message = $message->render(); + $queueForMessaging ->setType(MESSAGE_SEND_TYPE_INTERNAL) ->setMessage(new Document([ @@ -3751,7 +3773,8 @@ App::post('/v1/account/mfa/challenge') 'content' => $code, ], ])) - ->setRecipients([$user->getAttribute('phone')]); + ->setRecipients([$user->getAttribute('phone')]) + ->setProviderType(MESSAGE_TYPE_SMS); break; case 'email': if (empty(App::getEnv('_APP_SMTP_HOST'))) { @@ -3764,9 +3787,85 @@ App::post('/v1/account/mfa/challenge') throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); } + $subject = $locale->getText("emails.mfaChallenge.subject"); + $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; + + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $agentOs = $detector->getOS(); + $agentClient = $detector->getClient(); + $agentDevice = $detector->getDevice(); + + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl'); + $message + ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) + ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) + ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) + ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); + + $body = $message->render(); + + $smtp = $project->getAttribute('smtp', []); + $smtpEnabled = $smtp['enabled'] ?? false; + + $senderEmail = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $senderName = App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); + $replyTo = ""; + + if ($smtpEnabled) { + if (!empty($smtp['senderEmail'])) { + $senderEmail = $smtp['senderEmail']; + } + if (!empty($smtp['senderName'])) { + $senderName = $smtp['senderName']; + } + if (!empty($smtp['replyTo'])) { + $replyTo = $smtp['replyTo']; + } + + $queueForMails + ->setSmtpHost($smtp['host'] ?? '') + ->setSmtpPort($smtp['port'] ?? '') + ->setSmtpUsername($smtp['username'] ?? '') + ->setSmtpPassword($smtp['password'] ?? '') + ->setSmtpSecure($smtp['secure'] ?? ''); + + if (!empty($customTemplate)) { + if (!empty($customTemplate['senderEmail'])) { + $senderEmail = $customTemplate['senderEmail']; + } + if (!empty($customTemplate['senderName'])) { + $senderName = $customTemplate['senderName']; + } + if (!empty($customTemplate['replyTo'])) { + $replyTo = $customTemplate['replyTo']; + } + + $body = $customTemplate['message'] ?? ''; + $subject = $customTemplate['subject'] ?? $subject; + } + + $queueForMails + ->setSmtpReplyTo($replyTo) + ->setSmtpSenderEmail($senderEmail) + ->setSmtpSenderName($senderName); + } + + $emailVariables = [ + 'direction' => $locale->getText('settings.direction'), + // {{user}}, {{project}} and {{otp}} are required in the templates + 'user' => $user->getAttribute('name'), + 'project' => $project->getAttribute('name'), + 'otp' => $code, + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN' + ]; + $queueForMails - ->setSubject("{$code} is your 6-digit code") - ->setBody($code) + ->setSubject($subject) + ->setBody($body) + ->setVariables($emailVariables) ->setRecipient($user->getAttribute('email')) ->trigger(); break; @@ -3841,6 +3940,9 @@ App::put('/v1/account/mfa/challenge') $dbForProject->updateDocument('sessions', $sessionId, $session->setAttribute('factors', $provider, Document::SET_TYPE_APPEND)); + $queueForEvents + ->setParam('userId', $user->getId()); + $response->dynamic($session, Response::MODEL_SESSION); }); diff --git a/app/controllers/api/messaging.php b/app/controllers/api/messaging.php index 1c30e07ba5..7e051ad828 100644 --- a/app/controllers/api/messaging.php +++ b/app/controllers/api/messaging.php @@ -766,11 +766,12 @@ App::post('/v1/messaging/providers/apns') ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) ->param('teamId', '', new Text(0), 'APNS team ID.', true) ->param('bundleId', '', new Text(0), 'APNS bundle ID.', true) + ->param('sandbox', false, new Boolean(), 'Use APNS sandbox environment.', true) ->param('enabled', null, new Boolean(), 'Set as enabled.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, string $authKey, string $authKeyId, string $teamId, string $bundleId, bool $sandbox, ?bool $enabled, Event $queueForEvents, Database $dbForProject, Response $response) { $providerId = $providerId == 'unique()' ? ID::unique() : $providerId; $credentials = []; @@ -803,6 +804,10 @@ App::post('/v1/messaging/providers/apns') $enabled = false; } + $options = [ + 'sandbox' => $sandbox + ]; + $provider = new Document([ '$id' => $providerId, 'name' => $name, @@ -810,6 +815,7 @@ App::post('/v1/messaging/providers/apns') 'type' => MESSAGE_TYPE_PUSH, 'enabled' => $enabled, 'credentials' => $credentials, + 'options' => $options ]); try { @@ -1808,10 +1814,11 @@ App::patch('/v1/messaging/providers/apns/:providerId') ->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true) ->param('teamId', '', new Text(0), 'APNS team ID.', true) ->param('bundleId', '', new Text(0), 'APNS bundle ID.', true) + ->param('sandbox', null, new Boolean(), 'Use APNS sandbox environment.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $providerId, string $name, ?bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $providerId, string $name, ?bool $enabled, string $authKey, string $authKeyId, string $teamId, string $bundleId, ?bool $sandbox, Event $queueForEvents, Database $dbForProject, Response $response) { $provider = $dbForProject->getDocument('providers', $providerId); if ($provider->isEmpty()) { @@ -1847,6 +1854,14 @@ App::patch('/v1/messaging/providers/apns/:providerId') $provider->setAttribute('credentials', $credentials); + $options = $provider->getAttribute('options'); + + if (!\is_null($sandbox)) { + $options['sandbox'] = $sandbox; + } + + $provider->setAttribute('options', $options); + if (!\is_null($enabled)) { if ($enabled) { if ( @@ -2133,21 +2148,26 @@ App::patch('/v1/messaging/topics/:topicId') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOPIC) ->param('topicId', '', new UID(), 'Topic ID.') - ->param('name', '', new Text(128), 'Topic Name.', true) + ->param('name', null, new Text(128), 'Topic Name.', true) + ->param('subscribe', null, new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) ->inject('queueForEvents') ->inject('dbForProject') ->inject('response') - ->action(function (string $topicId, string $name, Event $queueForEvents, Database $dbForProject, Response $response) { + ->action(function (string $topicId, ?string $name, ?array $subscribe, Event $queueForEvents, Database $dbForProject, Response $response) { $topic = $dbForProject->getDocument('topics', $topicId); if ($topic->isEmpty()) { throw new Exception(Exception::TOPIC_NOT_FOUND); } - if (!empty($name)) { + if (!\is_null($name)) { $topic->setAttribute('name', $name); } + if (!\is_null($subscribe)) { + $topic->setAttribute('subscribe', $subscribe); + } + $topic = $dbForProject->updateDocument('topics', $topicId, $topic); $queueForEvents diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index e2c41d9ed1..987c146e0d 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -513,7 +513,7 @@ App::post('/v1/users/:userId/targets') Permission::delete(Role::user($user->getId())), ], 'providerId' => $providerId ?? null, - 'providerInternalId' => $provider->getInternalId() ?? null, + 'providerInternalId' => $provider->isEmpty() ? null : $provider->getInternalId(), 'providerType' => $providerType, 'userId' => $userId, 'userInternalId' => $user->getInternalId(), @@ -1662,7 +1662,7 @@ App::post('/v1/users/:userId/sessions') ->inject('queueForEvents') ->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) { $user = $dbForProject->getDocument('users', $userId); - if ($user === false || $user->isEmpty()) { + if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } @@ -1731,7 +1731,7 @@ App::post('/v1/users/:userId/tokens') ->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents) { $user = $dbForProject->getDocument('users', $userId); - if ($user === false || $user->isEmpty()) { + if ($user->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } diff --git a/composer.json b/composer.json index c0afd636f7..90cf585739 100644 --- a/composer.json +++ b/composer.json @@ -50,7 +50,7 @@ "utopia-php/cache": "0.9.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.48.2", + "utopia-php/database": "0.48.*", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.33.*", diff --git a/composer.lock b/composer.lock index 447a047d30..b7bb031c3c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9e0e07159d27e4b86511aaab851532de", + "content-hash": "f19c09e7e233fe0f767bf5255fb46b86", "packages": [ { "name": "adhocore/jwt", @@ -65,16 +65,16 @@ }, { "name": "appwrite/appwrite", - "version": "10.1.0", + "version": "10.0.0", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-for-php.git", - "reference": "da579af70723cfc117b5af84375bdef117e27312" + "reference": "461eedf4efd502dc905c3055f36f0e3583f67390" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/da579af70723cfc117b5af84375bdef117e27312", - "reference": "da579af70723cfc117b5af84375bdef117e27312", + "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/461eedf4efd502dc905c3055f36f0e3583f67390", + "reference": "461eedf4efd502dc905c3055f36f0e3583f67390", "shasum": "" }, "require": { @@ -99,10 +99,10 @@ "support": { "email": "team@appwrite.io", "issues": "https://github.com/appwrite/sdk-for-php/issues", - "source": "https://github.com/appwrite/sdk-for-php/tree/10.1.0", + "source": "https://github.com/appwrite/sdk-for-php/tree/10.0.0", "url": "https://appwrite.io/support" }, - "time": "2023-11-20T09:56:12+00:00" + "time": "2023-09-07T23:28:31+00:00" }, { "name": "appwrite/php-clamav", @@ -1552,16 +1552,16 @@ }, { "name": "utopia-php/database", - "version": "0.48.2", + "version": "0.48.4", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4" + "reference": "02f20bd901b8fab26d7dc2c58f7da1d6a08d21c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4", - "reference": "0a231a2874fdbc0cf2ae2170b3f132fdee0ddfd4", + "url": "https://api.github.com/repos/utopia-php/database/zipball/02f20bd901b8fab26d7dc2c58f7da1d6a08d21c0", + "reference": "02f20bd901b8fab26d7dc2c58f7da1d6a08d21c0", "shasum": "" }, "require": { @@ -1569,7 +1569,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.9.*", - "utopia-php/framework": "0.*.*", + "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -1602,9 +1602,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.48.2" + "source": "https://github.com/utopia-php/database/tree/0.48.4" }, - "time": "2024-02-02T14:10:14+00:00" + "time": "2024-02-23T03:22:55+00:00" }, { "name": "utopia-php/domains", @@ -1962,20 +1962,20 @@ }, { "name": "utopia-php/migration", - "version": "dev-dev", + "version": "0.3.6", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "0847fad35006c16f2aa572c4fa890cc8a0e7f8f2" + "reference": "f78273b38bade23db5866e5c7cb5f55427ba82af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/0847fad35006c16f2aa572c4fa890cc8a0e7f8f2", - "reference": "0847fad35006c16f2aa572c4fa890cc8a0e7f8f2", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/f78273b38bade23db5866e5c7cb5f55427ba82af", + "reference": "f78273b38bade23db5866e5c7cb5f55427ba82af", "shasum": "" }, "require": { - "appwrite/appwrite": "10.1.0", + "appwrite/appwrite": "10.0.*", "php": "8.*", "utopia-php/cli": "0.*" }, @@ -1990,19 +1990,7 @@ "Utopia\\Migration\\": "src/Migration" } }, - "autoload-dev": { - "psr-4": { - "Utopia\\Tests\\": "tests/Migration" - } - }, - "scripts": { - "lint": [ - "./vendor/bin/pint --test" - ], - "format": [ - "./vendor/bin/pint" - ] - }, + "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], @@ -2015,10 +2003,10 @@ "utopia" ], "support": { - "source": "https://github.com/utopia-php/migration/tree/dev", - "issues": "https://github.com/utopia-php/migration/issues" + "issues": "https://github.com/utopia-php/migration/issues", + "source": "https://github.com/utopia-php/migration/tree/0.3.6" }, - "time": "2024-02-23T12:02:32+00:00" + "time": "2023-11-02T15:13:03+00:00" }, { "name": "utopia-php/mongo", @@ -5461,9 +5449,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": { - "utopia-php/migration": 20 - }, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Migration/Version/V20.php b/src/Appwrite/Migration/Version/V20.php index 0e7434c56c..0ef899588e 100644 --- a/src/Appwrite/Migration/Version/V20.php +++ b/src/Appwrite/Migration/Version/V20.php @@ -30,31 +30,34 @@ class V20 extends Migration foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subQueryVariables', 'subQueryChallenges', 'subQueryProjectVariables', 'subQueryTargets', 'subQueryTopicTargets'] as $name) { Database::addFilter( $name, - fn() => null, - fn() => [] + fn () => null, + fn () => [] ); } - $this->migrateUsageMetrics('project.$all.network.requests', 'network.requests'); - $this->migrateUsageMetrics('project.$all.network.outbound', 'network.outbound'); - $this->migrateUsageMetrics('project.$all.network.inbound', 'network.inbound'); - $this->migrateUsageMetrics('users.$all.count.total', 'users'); - $this->migrateSessionsMetric(); - Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')'); $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); Console::info('Migrating Collections'); $this->migrateCollections(); - Console::info('Migrating Functions'); - $this->migrateFunctions(); + // No need to migrate stats for console + if ($this->project->getInternalId() !== 'console') { + $this->migrateUsageMetrics('project.$all.network.requests', 'network.requests'); + $this->migrateUsageMetrics('project.$all.network.outbound', 'network.outbound'); + $this->migrateUsageMetrics('project.$all.network.inbound', 'network.inbound'); + $this->migrateUsageMetrics('users.$all.count.total', 'users'); + $this->migrateSessionsMetric(); - Console::info('Migrating Databases'); - $this->migrateDatabases(); + Console::info('Migrating Functions'); + $this->migrateFunctions(); - Console::info('Migrating Buckets'); - $this->migrateBuckets(); + Console::info('Migrating Databases'); + $this->migrateDatabases(); + + Console::info('Migrating Buckets'); + $this->migrateBuckets(); + } Console::info('Migrating Documents'); $this->forEachDocument([$this, 'fixDocument']); @@ -75,25 +78,27 @@ class V20 extends Migration }; // Support database array type migration (user collections) - foreach ( - $this->documentsIterator('attributes', [ - Query::equal('array', [true]), - ]) as $attribute - ) { - $foundIndex = false; + if ($collectionType === 'projects') { foreach ( - $this->documentsIterator('indexes', [ - Query::equal('databaseInternalId', [$attribute['databaseInternalId']]), - Query::equal('collectionInternalId', [$attribute['collectionInternalId']]), - ]) as $index + $this->documentsIterator('attributes', [ + Query::equal('array', [true]), + ]) as $attribute ) { - if (in_array($attribute['key'], $index['attributes'])) { - $this->projectDB->deleteIndex($index['collectionId'], $index['$id']); - $foundIndex = true; + $foundIndex = false; + foreach ( + $this->documentsIterator('indexes', [ + Query::equal('databaseInternalId', [$attribute['databaseInternalId']]), + Query::equal('collectionInternalId', [$attribute['collectionInternalId']]), + ]) as $index + ) { + if (in_array($attribute['key'], $index['attributes'])) { + $this->projectDB->deleteIndex($index['collectionId'], $index['$id']); + $foundIndex = true; + } + } + if ($foundIndex === true) { + $this->projectDB->updateAttribute($attribute['collectionInternalId'], $attribute['key'], $attribute['type']); } - } - if ($foundIndex === true) { - $this->projectDB->updateAttribute($attribute['collectionInternalId'], $attribute['key'], $attribute['type']); } } @@ -323,7 +328,6 @@ class V20 extends Migration */ protected function createInfMetric(string $metric, int $value): void { - try { /** * Creating inf metric @@ -351,7 +355,6 @@ class V20 extends Migration */ protected function migrateUsageMetrics(string $from, string $to): void { - /** * inf metric */ @@ -411,7 +414,6 @@ class V20 extends Migration */ private function migrateFunctions(): void { - $this->migrateUsageMetrics('deployment.$all.storage.size', 'deployments.storage'); $this->migrateUsageMetrics('builds.$all.compute.total', 'builds'); $this->migrateUsageMetrics('builds.$all.compute.time', 'builds.compute'); @@ -539,6 +541,14 @@ class V20 extends Migration $duration = $this->project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG; $expire = DateTime::addSeconds(new \DateTime(), $duration); $document->setAttribute('expire', $expire); + + $factors = match ($document->getAttribute('provider')) { + Auth::SESSION_PROVIDER_ANONYMOUS => ['anonymous'], + Auth::SESSION_PROVIDER_PHONE => ['phone'], + default => ['password'], + }; + + $document->setAttribute('factors', $factors); break; } return $document; diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index ac9c44c3b9..57d1baa978 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -32,6 +32,14 @@ class Mails extends Action ->callback(fn (Message $message, Registry $register, Log $log) => $this->action($message, $register, $log)); } + /** + * @var array