diff --git a/app/config/locale/templates.php b/app/config/locale/templates.php index ac5a2acf1d..e013c3ccc9 100644 --- a/app/config/locale/templates.php +++ b/app/config/locale/templates.php @@ -6,7 +6,8 @@ return [ 'magicSession', 'recovery', 'invitation', - 'mfaChallenge' + 'mfaChallenge', + 'sessionAlert' ], 'sms' => [ 'verification', diff --git a/app/config/locale/templates/email-session-alert.tpl b/app/config/locale/templates/email-session-alert.tpl index 9855175b6f..20cecf212d 100644 --- a/app/config/locale/templates/email-session-alert.tpl +++ b/app/config/locale/templates/email-session-alert.tpl @@ -11,4 +11,4 @@
{{footer}}
{{thanks}}
-{{signature}}
+{{signature}}
\ No newline at end of file diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 3a4a199e38..b16b50196f 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -18,13 +18,13 @@ "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.sessionAlert.subject": "New session alert for {{project}}", + "emails.sessionAlert.subject": "Security alert: new session on your {{project}} account", "emails.sessionAlert.hello":"Hello {{user}}", - "emails.sessionAlert.body": "We're writing to inform you that a new session has been initiated on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}. \nHere are the details of the new session: ", + "emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}.\nHere are the details of the new session: ", "emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}", "emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}", "emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}", - "emails.sessionAlert.footer": "If you didn't request the sign in, you can safely ignore this email. If you suspect unauthorized activity, please secure your account immediately.", + "emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.", "emails.sessionAlert.thanks": "Thanks,", "emails.sessionAlert.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 37627d79fc..af4e60364e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -124,7 +124,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - 'dateTime' => DateTime::format(new \DateTime(), 'Y-m-d H:i:s'), + 'dateTime' => DateTime::format(new \DateTime(), 'h:ia MMMM dS'), 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'device' => $session->getAttribute('clientName'), @@ -224,7 +224,11 @@ $createSession = function (string $userId, string $secret, Request $request, Res } if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) { - sendSessionAlert($locale, $user, $project, $session, $queueForMails); + if ($dbForProject->count('sessions', [ + Query::equal('userId', [$user->getId()]), + ]) !== 1) { + sendSessionAlert($locale, $user, $project, $session, $queueForMails); + } } $queueForEvents @@ -904,7 +908,11 @@ App::post('/v1/account/sessions/email') ; if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) { - sendSessionAlert($locale, $user, $project, $session, $queueForMails); + if ($dbForProject->count('sessions', [ + Query::equal('userId', [$user->getId()]), + ]) !== 1) { + sendSessionAlert($locale, $user, $project, $session, $queueForMails); + } } $response->dynamic($session, Response::MODEL_SESSION); diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 321b1110fd..7f465a8260 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -1225,7 +1225,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); - // Create a session for the new account + // Create first session for the new account $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', @@ -1238,11 +1238,23 @@ class AccountCustomClientTest extends Scope $this->assertEquals(201, $response['headers']['status-code']); + // Create second session for the new account + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + ]), [ + 'email' => $email, + 'password' => $password, + ]); + + // Check the alert email $lastEmail = $this->getLastEmail(); $this->assertEquals($email, $lastEmail['to'][0]['address']); - $this->assertStringContainsString('New session alert', $lastEmail['subject']); + $this->assertStringContainsString('Security alert: new session', $lastEmail['subject']); $this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address $this->assertStringContainsString('Unknown', $lastEmail['text']); // Country $this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name