mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Alert on new session creation
This commit is contained in:
@@ -15,6 +15,12 @@
|
||||
"emails.magicSession.footer": "If you didn’t ask to login using this email, you can ignore this message.",
|
||||
"emails.magicSession.thanks": "Thanks",
|
||||
"emails.magicSession.signature": "{{project}} team",
|
||||
"emails.authNotify.subject": "New session alert for {{project}}",
|
||||
"emails.authNotify.hello": "Hey {{user}},",
|
||||
"emails.authNotify.body": "We're writing to inform you that a new session has been initiated on your {{project}} account on {{date}} at {{time}}. <br><br>Here are the details: <br>Device: {{device}}<br>Location: {{country}}<br>IP Address: {{ip}} <br><br>If you did not initiate this session, we strongly recommend that you review your account settings.",
|
||||
"emails.authNotify.footer": "If you recognize this activity, no further action is required.",
|
||||
"emails.authNotify.thanks": "Thanks",
|
||||
"emails.authNotify.signature": "{{project}} team",
|
||||
"emails.recovery.subject": "Password Reset",
|
||||
"emails.recovery.hello": "Hello {{user}}",
|
||||
"emails.recovery.body": "Follow this link to reset your {{project}} password.",
|
||||
|
||||
@@ -234,6 +234,7 @@ App::post('/v1/account/sessions/email')
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->param('notify', false, new Boolean(), 'Send email notification about new session creation to user email.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
@@ -242,7 +243,8 @@ App::post('/v1/account/sessions/email')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
|
||||
->inject('queueForMails')
|
||||
->action(function (string $email, string $password, bool $notify, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
@@ -310,6 +312,77 @@ App::post('/v1/account/sessions/email')
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['notify'] ?? false) {
|
||||
$body = $locale->getText("emails.authNotify.body");
|
||||
$subject = $locale->getText("emails.authNotify.subject");
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $body)
|
||||
->setParam('{{hello}}', $locale->getText("emails.authNotify.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.authNotify.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.authNotify.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.authNotify.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'] ?? '');
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyTo($replyTo)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
}
|
||||
|
||||
$device = $detector->getDevice();
|
||||
|
||||
$sessionCreatedAt = $session->getCreatedAt();
|
||||
$dateTime = new DateTimeImmutable($sessionCreatedAt);
|
||||
$date = $dateTime->format('Y-m-d');
|
||||
$time = $dateTime->format('H:i:s');
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'user' => $user->getAttribute('name'),
|
||||
'project' => $project->getAttribute('name'),
|
||||
'date' => $date,
|
||||
'time' => $time,
|
||||
'device' => $device['deviceBrand'] . " " . $device['deviceModel'] . " " . $device['deviceName'],
|
||||
'country' => $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')),
|
||||
'ip' => $request->getIP(),
|
||||
'redirect' => ''
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient($email)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$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'))
|
||||
|
||||
@@ -85,7 +85,7 @@ App::post('/v1/projects')
|
||||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordAi' => false, 'sessionRefresh' => false, 'passwordDictionary' => false, 'passwordDictionaryLength' => '10k', 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordAi' => false, 'sessionRefresh' => false, 'passwordDictionary' => false, 'passwordDictionaryLength' => '10k', 'notify' => true, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, 'personalDataCheck' => false];
|
||||
foreach ($auth as $index => $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
@@ -853,6 +853,39 @@ App::patch('/v1/projects/:projectId/auth/password-ai')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/notify')
|
||||
->desc('Update authentication notify status. Enable or disable notifications for new session creations.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'notifyAuthCreate')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.', true)
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable notifications for new session creations. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
if (!empty($projectId)) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['notify'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/personal-data')
|
||||
->desc('Enable or disable checking user passwords for similarity with their personal data.')
|
||||
->groups(['api', 'projects'])
|
||||
|
||||
@@ -1224,6 +1224,7 @@ App::setResource('console', function () {
|
||||
'invites' => App::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
|
||||
'limit' => (App::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
|
||||
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
|
||||
'notify' => true,
|
||||
],
|
||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
||||
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||
|
||||
@@ -138,18 +138,24 @@ class Project extends Model
|
||||
'default' => '10k',
|
||||
'example' => '1m',
|
||||
])
|
||||
->addRule('authPasswordAi', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against against AI opinion',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authSessionRefresh', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authPasswordAi', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against against AI opinion',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authNotify', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to send notification on new session creation.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authSessionRefresh', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not sessions are automatically extended to session duration on every request',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authPersonalDataCheck', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check the user password for similarity with their personal data.',
|
||||
@@ -238,8 +244,7 @@ class Project extends Model
|
||||
'description' => 'SMTP server secure protocol',
|
||||
'default' => '',
|
||||
'example' => 'tls',
|
||||
])
|
||||
;
|
||||
]);
|
||||
|
||||
$services = Config::getParam('services', []);
|
||||
$auth = Config::getParam('auth', []);
|
||||
@@ -254,8 +259,7 @@ class Project extends Model
|
||||
'description' => $name . ' auth method status',
|
||||
'example' => true,
|
||||
'default' => true,
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($services as $service) {
|
||||
@@ -272,8 +276,7 @@ class Project extends Model
|
||||
'description' => $name . ' service status',
|
||||
'example' => true,
|
||||
'default' => true,
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,8 +343,8 @@ class Project extends Model
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
$document->setAttribute('authPasswordDictionaryLength', $authValues['passwordDictionaryLength'] ?? false);
|
||||
$document->setAttribute('authPasswordAi', $authValues['passwordAi'] ?? false);
|
||||
$document->setAttribute('authNotify', $authValues['notify'] ?? false);
|
||||
$document->setAttribute('authSessionRefresh', $authValues['sessionRefresh'] ?? false);
|
||||
// TODO: Khushboo add here
|
||||
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
|
||||
Reference in New Issue
Block a user