Make legacy login alerts configurable

This adds security alerts section to the communication settings, with one new item: a toggle for enabling/disabling legacy login alerts. It's enabled by default. Disabling it means that you no longer get emails when logging in through a non-2FA enabled login flow, like createSession.

The reason I tackled this is that I have a bot account that automatically refreshes its session, using username and app password, and I'm close to having 100 of these emails in my inbox. I also tried to just disable TOTP but I wasn't allowed (I fixed that in a different PR).

English and Swedish translations were me, the rest were MyMemory. I imagine someone can improve on them after this is merged!
This commit is contained in:
Johanna Larsson
2026-09-11 19:54:37 +00:00
committed by Tangled
parent 09ba5e4521
commit 3474ed588d
13 changed files with 124 additions and 16 deletions
@@ -19,6 +19,7 @@ pub struct NotificationPrefsOutput {
pub telegram_verified: bool,
pub signal_username: Option<String>,
pub signal_verified: bool,
pub legacy_login_alerts: bool,
}
pub async fn get_notification_prefs(
@@ -32,6 +33,26 @@ pub async fn get_notification_prefs(
.await
.log_db_err("get notification prefs")?
.ok_or(ApiError::AccountNotFound)?;
let user_id = state
.repos
.user
.get_id_by_did(&auth.did)
.await
.log_db_err("get user by did")?
.ok_or(ApiError::AccountNotFound)?;
let legacy_login_alerts = state
.repos
.infra
.get_account_preferences(user_id)
.await
.log_db_err("get legacy login alert prefs")?
.iter()
.find(|(name, _)| name == "legacy_login_alerts")
.and_then(|(_, value)| value.as_bool())
.unwrap_or(true);
Ok(Json(NotificationPrefsOutput {
preferred_channel: prefs.preferred_channel,
email: prefs.email,
@@ -41,6 +62,7 @@ pub async fn get_notification_prefs(
telegram_verified: prefs.telegram_verified,
signal_username: prefs.signal_username,
signal_verified: prefs.signal_verified,
legacy_login_alerts,
}))
}
@@ -121,6 +143,7 @@ pub struct UpdateNotificationPrefsInput {
pub discord_username: Option<String>,
pub telegram_username: Option<String>,
pub signal_username: Option<String>,
pub legacy_login_alerts: Option<bool>,
}
#[derive(Serialize)]
@@ -435,6 +458,15 @@ pub async fn update_notification_prefs(
.await?;
}
if let Some(alerts) = input.legacy_login_alerts {
state
.repos
.infra
.upsert_account_preference(user_id, "legacy_login_alerts", json!(alerts))
.await
.log_db_err("update legacy login alert prefs")?;
}
Ok(Json(UpdateNotificationPrefsOutput {
success: true,
verification_required,
+25 -9
View File
@@ -323,15 +323,31 @@ pub async fn create_session(
"Legacy login on TOTP-enabled account - sending notification"
);
let hostname = &tranquil_config::get().server.hostname;
if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_legacy_login(
state.repos.user.as_ref(),
state.repos.infra.as_ref(),
row.id,
hostname,
client_ip,
row.preferred_comms_channel,
)
.await
let alerts_enabled = state
.repos
.infra
.get_account_preferences(row.id)
.await
.map(|prefs| {
prefs
.iter()
.find(|(name, _)| name == "legacy_login_alerts")
.and_then(|(_, value)| value.as_bool())
.unwrap_or(true)
})
.unwrap_or(true);
if alerts_enabled
&& let Err(e) = tranquil_pds::comms::comms_repo::enqueue_legacy_login(
state.repos.user.as_ref(),
state.repos.infra.as_ref(),
row.id,
hostname,
client_ip,
row.preferred_comms_channel,
)
.await
{
error!("Failed to queue legacy login notification: {:?}", e);
}
@@ -30,6 +30,7 @@
let savedDiscordUsername = $state('')
let savedTelegramUsername = $state('')
let savedSignalUsername = $state('')
let legacyLoginAlerts = $state(true)
let verifyingChannel = $state<string | null>(null)
let verificationCode = $state('')
let historyLoading = $state(true)
@@ -62,6 +63,7 @@
telegramVerified = prefs.telegramVerified
signalUsername = prefs.signalUsername ?? ''
signalVerified = prefs.signalVerified
legacyLoginAlerts = prefs.legacyLoginAlerts ?? true
savedDiscordUsername = discordUsername
savedTelegramUsername = telegramUsername
savedSignalUsername = signalUsername
@@ -85,6 +87,7 @@
discordUsername: discordUsername !== savedDiscordUsername ? discordUsername : undefined,
telegramUsername: telegramUsername !== savedTelegramUsername ? telegramUsername : undefined,
signalUsername: signalUsername !== savedSignalUsername ? signalUsername : undefined,
legacyLoginAlerts,
})
await refreshSession()
toast.success($_('comms.preferencesSaved'))
@@ -316,6 +319,25 @@
</div>
</section>
<section>
<h3>{$_('comms.securityAlerts')}</h3>
<div class="toggle-row">
<div class="toggle-info">
<span class="toggle-label">{$_('comms.legacyLoginAlerts')}</span>
<span class="toggle-description">{$_('comms.legacyLoginAlertsDescription')}</span>
</div>
<button
type="button"
class="toggle-button {legacyLoginAlerts ? 'on' : 'off'}"
onclick={() => legacyLoginAlerts = !legacyLoginAlerts}
disabled={saving}
aria-label={legacyLoginAlerts ? $_('comms.disableLegacyLoginAlerts') : $_('comms.enableLegacyLoginAlerts')}
>
<span class="toggle-slider"></span>
</button>
</div>
</section>
<div class="actions">
<button type="submit" disabled={saving}>
{saving ? $_('common.saving') : $_('comms.savePreferences')}
+1
View File
@@ -680,6 +680,7 @@ export const api = {
discordUsername?: string;
telegramUsername?: string;
signalUsername?: string;
legacyLoginAlerts?: boolean;
}): Promise<UpdateNotificationPrefsResponse> {
return xrpc("_account.updateNotificationPrefs", {
method: "POST",
+1
View File
@@ -232,6 +232,7 @@ export interface NotificationPrefs {
telegramVerified: boolean;
signalUsername: string | null;
signalVerified: boolean;
legacyLoginAlerts: boolean;
}
export interface NotificationHistoryItem {
+6 -1
View File
@@ -458,7 +458,12 @@
"telegramStartBot": "Or send /start {handle} to @{botUsername} manually",
"telegramOpenLink": "Open Telegram to verify",
"discordStartBot": "DM @{botUsername} on Discord and send /start {handle}",
"discordOpenLink": "Open Discord to verify"
"discordOpenLink": "Open Discord to verify",
"securityAlerts": "Security alerts",
"legacyLoginAlerts": "Legacy login alerts",
"legacyLoginAlertsDescription": "Get notified when someone signs in to your TOTP enabled account without using legacy login methods.",
"enableLegacyLoginAlerts": "Enable legacy login alerts",
"disableLegacyLoginAlerts": "Disable legacy login alerts"
},
"repoExplorer": {
"collections": "Collections",
+6 -1
View File
@@ -458,7 +458,12 @@
"failedToLoad": "Asetusten lataus epäonnistui",
"failedToSave": "Asetusten tallennus epäonnistui",
"failedToVerify": "Vahvistus epäonnistui",
"failedToLoadHistory": "Viestihistorian lataus epäonnistui"
"failedToLoadHistory": "Viestihistorian lataus epäonnistui",
"securityAlerts": "Turvallisuushälytykset",
"legacyLoginAlerts": "Vanhat kirjautumisilmoitukset",
"legacyLoginAlertsDescription": "Saat ilmoituksen, kun joku kirjautuu TOTP-yhteensopivalle tilillesi käyttämällä vanhoja kirjautumistapoja.",
"enableLegacyLoginAlerts": "Ota käyttöön vanhat kirjautumisilmoitukset",
"disableLegacyLoginAlerts": "Poista käytöstä vanhat kirjautumisilmoitukset"
},
"repoExplorer": {
"collections": "Kokoelmat",
+6 -1
View File
@@ -458,7 +458,12 @@
"telegramStartBot": "Ou envoyez /start {handle} à @{botUsername} manuellement",
"telegramOpenLink": "Ouvrir Telegram pour vérifier",
"discordStartBot": "Envoyez un DM à @{botUsername} sur Discord avec /start {handle}",
"discordOpenLink": "Ouvrir Discord pour vérifier"
"discordOpenLink": "Ouvrir Discord pour vérifier",
"securityAlerts": "Alertes de sécurité",
"legacyLoginAlerts": "Anciennes alertes de connexion",
"legacyLoginAlertsDescription": "Recevez une notification lorsque quelqu'un se connecte à votre compte TOTP à l'aide de méthodes de connexion héritées.",
"enableLegacyLoginAlerts": "Activer les alertes de connexion héritées",
"disableLegacyLoginAlerts": "Désactiver les alertes de connexion héritées"
},
"repoExplorer": {
"collections": "Collections",
+6 -1
View File
@@ -458,7 +458,12 @@
"failedToLoad": "設定の読み込みに失敗しました",
"failedToSave": "設定の保存に失敗しました",
"failedToVerify": "確認に失敗しました",
"failedToLoadHistory": "メッセージ履歴の読み込みに失敗しました"
"failedToLoadHistory": "メッセージ履歴の読み込みに失敗しました",
"securityAlerts": "セキュリティ警告",
"legacyLoginAlerts": "レガシーログインアラート",
"legacyLoginAlertsDescription": "誰かが従来のログイン方法を使用してTOTP対応アカウントにサインインしたときに通知を受け取ります。",
"enableLegacyLoginAlerts": "レガシーログインアラートを有効にする",
"disableLegacyLoginAlerts": "レガシーログインアラートを無効にする"
},
"repoExplorer": {
"collections": "コレクション",
+6 -1
View File
@@ -458,7 +458,12 @@
"failedToLoad": "설정 로딩 실패",
"failedToSave": "설정 저장 실패",
"failedToVerify": "인증 실패",
"failedToLoadHistory": "메시지 기록 로딩 실패"
"failedToLoadHistory": "메시지 기록 로딩 실패",
"securityAlerts": "보안 경고",
"legacyLoginAlerts": "레거시 로그인 알림",
"legacyLoginAlertsDescription": "레거시 로그인 방법을 사용하여 누군가가 TOTP 지원 계정에 로그인하면 알림을 받습니다.",
"enableLegacyLoginAlerts": "레거시 로그인 알림 활성화",
"disableLegacyLoginAlerts": "레거시 로그인 알림 비활성화"
},
"repoExplorer": {
"collections": "컬렉션",
+6 -1
View File
@@ -458,7 +458,12 @@
"failedToLoad": "Kunde inte ladda inställningar",
"failedToSave": "Kunde inte spara inställningar",
"failedToVerify": "Verifiering misslyckades",
"failedToLoadHistory": "Kunde inte ladda meddelandehistorik"
"failedToLoadHistory": "Kunde inte ladda meddelandehistorik",
"securityAlerts": "Säkerhetsvarningar",
"legacyLoginAlerts": "Varningar för föråldrade inloggningsmetoder",
"legacyLoginAlertsDescription": "Bli notifierad när en inloggning sker med föråldrade inloggningsmetoder, när du har TOTP aktiverat.",
"enableLegacyLoginAlerts": "Aktivera föråldrad inloggningsmetodsvarning",
"disableLegacyLoginAlerts": "Avaktivera föråldrad inloggningsmetodsvarning"
},
"repoExplorer": {
"collections": "Samlingar",
+6 -1
View File
@@ -458,7 +458,12 @@
"failedToLoad": "加载偏好设置失败",
"failedToSave": "保存偏好设置失败",
"failedToVerify": "验证失败",
"failedToLoadHistory": "加载消息历史失败"
"failedToLoadHistory": "加载消息历史失败",
"securityAlerts": "安全警报",
"legacyLoginAlerts": "旧版登录提醒",
"legacyLoginAlertsDescription": "当有人使用传统登录方式登录您的启用了TOTP的帐户时,会收到通知。",
"enableLegacyLoginAlerts": "启用旧版登录警报",
"disableLegacyLoginAlerts": "禁用旧版登录警报"
},
"repoExplorer": {
"collections": "集合",
+1
View File
@@ -260,6 +260,7 @@ export const mockData = {
telegramVerified: false,
signalUsername: null,
signalVerified: false,
legacyLoginAlerts: true,
...overrides,
}),
describeServer: (overrides?: Record<string, unknown>) => ({