+3
-2
frontend/src/lib/api.ts
+3
-2
frontend/src/lib/api.ts
···
57
message: res.statusText,
58
}));
59
if (
60
+
res.status === 401 &&
61
+
(err.error === "AuthenticationFailed" || err.error === "ExpiredToken") &&
62
+
token && tokenRefreshCallback && !skipRetry
63
) {
64
const newToken = await tokenRefreshCallback();
65
if (newToken && newToken !== token) {
+2
frontend/src/locales/en.json
+2
frontend/src/locales/en.json
···
172
"navCommsDesc": "Discord, Telegram, Signal channels",
173
"navRepo": "Repository Explorer",
174
"navRepoDesc": "Browse and manage raw AT Protocol records",
175
+
"navDelegation": "Delegation",
176
+
"navDelegationDesc": "Manage account controllers and delegated accounts",
177
"navAdmin": "Admin Panel",
178
"navAdminDesc": "Server stats and admin operations"
179
},
+2
frontend/src/locales/fi.json
+2
frontend/src/locales/fi.json
···
172
"navCommsDesc": "Discord-, Telegram-, Signal-kanavat",
173
"navRepo": "Tietovarastoselaaja",
174
"navRepoDesc": "Selaa ja hallitse raakoja AT Protocol -tietueita",
175
+
"navDelegation": "Delegointi",
176
+
"navDelegationDesc": "Hallitse tilin ohjaajia ja delegoituja tilejä",
177
"navAdmin": "Ylläpitopaneeli",
178
"navAdminDesc": "Palvelintilastot ja ylläpitotoiminnot"
179
},
+2
frontend/src/locales/ja.json
+2
frontend/src/locales/ja.json
+2
frontend/src/locales/ko.json
+2
frontend/src/locales/ko.json
+2
frontend/src/locales/sv.json
+2
frontend/src/locales/sv.json
···
172
"navCommsDesc": "Discord, Telegram, Signal-kanaler",
173
"navRepo": "Dataförvarsutforskare",
174
"navRepoDesc": "Bläddra och hantera råa AT Protocol-poster",
175
+
"navDelegation": "Delegering",
176
+
"navDelegationDesc": "Hantera kontokontrollanter och delegerade konton",
177
"navAdmin": "Adminpanel",
178
"navAdminDesc": "Serverstatistik och administratörsoperationer"
179
},
+3
frontend/src/locales/zh.json
+3
frontend/src/locales/zh.json
···
172
"navCommsDesc": "Discord、Telegram、Signal 渠道设置",
173
"navRepo": "数据浏览器",
174
"navRepoDesc": "浏览和管理原始 AT Protocol 记录",
175
"navAdmin": "管理后台",
176
"navAdminDesc": "服务器统计和管理操作"
177
},
···
912
"actor": "执行者",
913
"controller": "控制者",
914
"account": "账户",
915
"details": "详情",
916
"actionGrantCreated": "授权创建",
917
"actionGrantRevoked": "授权撤销",
···
172
"navCommsDesc": "Discord、Telegram、Signal 渠道设置",
173
"navRepo": "数据浏览器",
174
"navRepoDesc": "浏览和管理原始 AT Protocol 记录",
175
+
"navDelegation": "账户委托",
176
+
"navDelegationDesc": "管理控制者和委托账户",
177
"navAdmin": "管理后台",
178
"navAdminDesc": "服务器统计和管理操作"
179
},
···
914
"actor": "执行者",
915
"controller": "控制者",
916
"account": "账户",
917
+
"accountCreated": "已创建委托账户:{handle}",
918
"details": "详情",
919
"actionGrantCreated": "授权创建",
920
"actionGrantRevoked": "授权撤销",
+2
-2
frontend/src/routes/Dashboard.svelte
+2
-2
frontend/src/routes/Dashboard.svelte
+1
-1
src/api/server/verify_email.rs
+1
-1
src/api/server/verify_email.rs
+2
-46
src/api/server/verify_token.rs
+2
-46
src/api/server/verify_token.rs
···
1
use axum::{
2
Json,
3
extract::State,
4
-
http::{HeaderMap, StatusCode},
5
};
6
use serde::{Deserialize, Serialize};
7
use serde_json::json;
···
30
31
pub async fn verify_token(
32
State(state): State<AppState>,
33
-
headers: HeaderMap,
34
Json(input): Json<VerifyTokenInput>,
35
) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
36
-
verify_token_internal(&state, Some(&headers), input).await
37
}
38
39
pub async fn verify_token_internal(
40
state: &AppState,
41
-
headers: Option<&HeaderMap>,
42
input: VerifyTokenInput,
43
) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
44
let normalized_token = normalize_token_input(&input.token);
···
95
.await
96
}
97
VerificationPurpose::ChannelUpdate => {
98
-
let auth_did = extract_and_validate_auth(state, headers).await?;
99
-
if auth_did != token_data.did {
100
-
return Err((
101
-
StatusCode::BAD_REQUEST,
102
-
Json(
103
-
json!({ "error": "InvalidToken", "message": "Token does not match authenticated account" }),
104
-
),
105
-
));
106
-
}
107
handle_channel_update(state, &token_data.did, &token_data.channel, &identifier).await
108
}
109
VerificationPurpose::Signup => {
···
111
.await
112
}
113
}
114
-
}
115
-
116
-
async fn extract_and_validate_auth(
117
-
state: &AppState,
118
-
headers: Option<&HeaderMap>,
119
-
) -> Result<String, (StatusCode, Json<serde_json::Value>)> {
120
-
let headers = headers.ok_or_else(|| {
121
-
(
122
-
StatusCode::UNAUTHORIZED,
123
-
Json(json!({ "error": "AuthenticationRequired", "message": "Authentication required for this verification" })),
124
-
)
125
-
})?;
126
-
127
-
let token = crate::auth::extract_bearer_token_from_header(
128
-
headers.get("Authorization").and_then(|h| h.to_str().ok()),
129
-
)
130
-
.ok_or_else(|| {
131
-
(
132
-
StatusCode::UNAUTHORIZED,
133
-
Json(json!({ "error": "AuthenticationRequired", "message": "Authentication required for this verification" })),
134
-
)
135
-
})?;
136
-
137
-
let user = crate::auth::validate_bearer_token(&state.db, &token)
138
-
.await
139
-
.map_err(|_| {
140
-
(
141
-
StatusCode::UNAUTHORIZED,
142
-
Json(json!({ "error": "AuthenticationFailed", "message": "Invalid authentication token" })),
143
-
)
144
-
})?;
145
-
146
-
Ok(user.did)
147
}
148
149
async fn handle_migration_verification(
···
1
use axum::{
2
Json,
3
extract::State,
4
+
http::StatusCode,
5
};
6
use serde::{Deserialize, Serialize};
7
use serde_json::json;
···
30
31
pub async fn verify_token(
32
State(state): State<AppState>,
33
Json(input): Json<VerifyTokenInput>,
34
) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
35
+
verify_token_internal(&state, input).await
36
}
37
38
pub async fn verify_token_internal(
39
state: &AppState,
40
input: VerifyTokenInput,
41
) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
42
let normalized_token = normalize_token_input(&input.token);
···
93
.await
94
}
95
VerificationPurpose::ChannelUpdate => {
96
handle_channel_update(state, &token_data.did, &token_data.channel, &identifier).await
97
}
98
VerificationPurpose::Signup => {
···
100
.await
101
}
102
}
103
}
104
105
async fn handle_migration_verification(
+1
-3
src/api/verification.rs
+1
-3
src/api/verification.rs
···
2
use axum::{
3
Json,
4
extract::State,
5
-
http::HeaderMap,
6
response::{IntoResponse, Response},
7
};
8
use serde::Deserialize;
···
18
19
pub async fn confirm_channel_verification(
20
State(state): State<AppState>,
21
-
headers: HeaderMap,
22
Json(input): Json<ConfirmChannelVerificationInput>,
23
) -> Response {
24
let token_input = crate::api::server::VerifyTokenInput {
···
26
identifier: input.identifier,
27
};
28
29
-
match crate::api::server::verify_token_internal(&state, Some(&headers), token_input).await {
30
Ok(output) => Json(json!({"success": output.success})).into_response(),
31
Err((status, err_json)) => (status, err_json).into_response(),
32
}
···
2
use axum::{
3
Json,
4
extract::State,
5
response::{IntoResponse, Response},
6
};
7
use serde::Deserialize;
···
17
18
pub async fn confirm_channel_verification(
19
State(state): State<AppState>,
20
Json(input): Json<ConfirmChannelVerificationInput>,
21
) -> Response {
22
let token_input = crate::api::server::VerifyTokenInput {
···
24
identifier: input.identifier,
25
};
26
27
+
match crate::api::server::verify_token_internal(&state, token_input).await {
28
Ok(output) => Json(json!({"success": output.success})).into_response(),
29
Err((status, err_json)) => (status, err_json).into_response(),
30
}
+18
-18
src/comms/locale.rs
+18
-18
src/comms/locale.rs
···
49
password_reset_subject: "Password Reset - {hostname}",
50
password_reset_body: "Hello @{handle},\n\nYour password reset code is: {code}\n\nThis code will expire in 10 minutes.\n\nIf you did not request this, please ignore this message.",
51
email_update_subject: "Confirm your new email - {hostname}",
52
-
email_update_body: "Hello @{handle},\n\nYour verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 10 minutes.\n\nIf you did not request this, please ignore this email.\n\n(Or if you like to live dangerously: {verify_link})",
53
account_deletion_subject: "Account Deletion Request - {hostname}",
54
account_deletion_body: "Hello @{handle},\n\nYour account deletion confirmation code is: {code}\n\nThis code will expire in 10 minutes.\n\nIf you did not request this, please secure your account immediately.",
55
plc_operation_subject: "{hostname} - PLC Operation Token",
···
59
passkey_recovery_subject: "Account Recovery - {hostname}",
60
passkey_recovery_body: "Hello @{handle},\n\nYou requested to recover your passkey-only account.\n\nClick the link below to set a temporary password and regain access:\n{url}\n\nThis link will expire in 1 hour.\n\nIf you did not request this, please ignore this message. Your account remains secure.",
61
signup_verification_subject: "Verify your account - {hostname}",
62
-
signup_verification_body: "Welcome! Your verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 30 minutes.\n\nIf you did not create an account on {hostname}, please ignore this message.\n\n(Or if you like to live dangerously: {verify_link})",
63
legacy_login_subject: "Security Alert: Legacy Login Detected - {hostname}",
64
legacy_login_body: "Hello @{handle},\n\nA login to your account was detected using a legacy app (like Bluesky) that doesn't support TOTP verification.\n\nDetails:\n- Time: {timestamp}\n- IP Address: {ip}\n\nYour TOTP protection was bypassed for this login. The session has limited permissions for sensitive operations.\n\nIf this wasn't you, please:\n1. Change your password immediately\n2. Review your active sessions\n3. Consider disabling legacy app logins in your security settings\n\nStay safe,\n{hostname}",
65
migration_verification_subject: "Verify your email - {hostname}",
66
-
migration_verification_body: "Welcome to {hostname}!\n\nYour account has been migrated successfully. To complete the setup, please verify your email address.\n\nYour verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 48 hours.\n\nIf you did not migrate your account, please ignore this email.\n\n(Or if you like to live dangerously: {verify_link})",
67
};
68
69
static STRINGS_ZH: NotificationStrings = NotificationStrings {
···
72
password_reset_subject: "密码重置 - {hostname}",
73
password_reset_body: "您好 @{handle},\n\n您的密码重置验证码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请忽略此消息。",
74
email_update_subject: "确认您的新邮箱 - {hostname}",
75
-
email_update_body: "您好 @{handle},\n\n您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请忽略此邮件。\n\n(或者直接点击链接:{verify_link})",
76
account_deletion_subject: "账户删除请求 - {hostname}",
77
account_deletion_body: "您好 @{handle},\n\n您的账户删除确认码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请立即保护您的账户。",
78
plc_operation_subject: "{hostname} - PLC 操作令牌",
···
82
passkey_recovery_subject: "账户恢复 - {hostname}",
83
passkey_recovery_body: "您好 @{handle},\n\n您请求恢复仅通行密钥账户的访问权限。\n\n点击以下链接设置临时密码并恢复访问:\n{url}\n\n此链接将在1小时后过期。\n\n如果这不是您的操作,请忽略此消息。您的账户仍然安全。",
84
signup_verification_subject: "验证您的账户 - {hostname}",
85
-
signup_verification_body: "欢迎!您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在30分钟后过期。\n\n如果您没有在 {hostname} 上创建账户,请忽略此消息。\n\n(或者直接点击链接:{verify_link})",
86
legacy_login_subject: "安全提醒:检测到传统应用登录 - {hostname}",
87
legacy_login_body: "您好 @{handle},\n\n检测到使用不支持 TOTP 验证的传统应用(如 Bluesky)登录您的账户。\n\n详细信息:\n- 时间:{timestamp}\n- IP 地址:{ip}\n\n此次登录绕过了 TOTP 保护。该会话对敏感操作的权限有限。\n\n如果这不是您的操作,请:\n1. 立即更改密码\n2. 检查您的活跃会话\n3. 考虑在安全设置中禁用传统应用登录\n\n请注意安全,\n{hostname}",
88
migration_verification_subject: "验证您的邮箱 - {hostname}",
89
-
migration_verification_body: "欢迎来到 {hostname}!\n\n您的账户已成功迁移。要完成设置,请验证您的邮箱地址。\n\n您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在 48 小时后过期。\n\n如果您没有迁移账户,请忽略此邮件。\n\n(或者直接点击链接:{verify_link})",
90
};
91
92
static STRINGS_JA: NotificationStrings = NotificationStrings {
···
95
password_reset_subject: "パスワードリセット - {hostname}",
96
password_reset_body: "@{handle} 様\n\nパスワードリセットコードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。",
97
email_update_subject: "新しいメールアドレスの確認 - {hostname}",
98
-
email_update_body: "@{handle} 様\n\n確認コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメールを無視してください。\n\n(自己責任でワンクリック認証:{verify_link})",
99
account_deletion_subject: "アカウント削除リクエスト - {hostname}",
100
account_deletion_body: "@{handle} 様\n\nアカウント削除の確認コードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、直ちにアカウントを保護してください。",
101
plc_operation_subject: "{hostname} - PLC 操作トークン",
···
105
passkey_recovery_subject: "アカウント復旧 - {hostname}",
106
passkey_recovery_body: "@{handle} 様\n\nパスキー専用アカウントの復旧をリクエストされました。\n\n以下のリンクをクリックして一時パスワードを設定し、アクセスを回復してください:\n{url}\n\nこのリンクは1時間後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。アカウントは安全なままです。",
107
signup_verification_subject: "アカウント認証 - {hostname}",
108
-
signup_verification_body: "ようこそ!認証コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは30分後に期限切れとなります。\n\n{hostname} でアカウントを作成していない場合は、このメールを無視してください。\n\n(自己責任でワンクリック認証:{verify_link})",
109
legacy_login_subject: "セキュリティ警告:レガシーログインを検出 - {hostname}",
110
legacy_login_body: "@{handle} 様\n\nTOTP 認証に対応していないレガシーアプリ(Bluesky など)からのログインが検出されました。\n\n詳細:\n- 時刻:{timestamp}\n- IP アドレス:{ip}\n\nこのログインでは TOTP 保護がバイパスされました。このセッションは機密操作に対する権限が制限されています。\n\n心当たりがない場合は:\n1. 直ちにパスワードを変更してください\n2. アクティブなセッションを確認してください\n3. セキュリティ設定でレガシーアプリのログインを無効にすることを検討してください\n\nご注意ください。\n{hostname}",
111
migration_verification_subject: "メールアドレスの認証 - {hostname}",
112
-
migration_verification_body: "{hostname} へようこそ!\n\nアカウントの移行が完了しました。設定を完了するには、メールアドレスを認証してください。\n\n認証コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは48時間後に期限切れとなります。\n\nアカウントを移行していない場合は、このメールを無視してください。\n\n(自己責任でワンクリック認証:{verify_link})",
113
};
114
115
static STRINGS_KO: NotificationStrings = NotificationStrings {
···
118
password_reset_subject: "비밀번호 재설정 - {hostname}",
119
password_reset_body: "안녕하세요 @{handle}님,\n\n비밀번호 재설정 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요.",
120
email_update_subject: "새 이메일 주소 확인 - {hostname}",
121
-
email_update_body: "안녕하세요 @{handle}님,\n\n인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 이 이메일을 무시하세요.\n\n(위험을 감수하고 원클릭 인증: {verify_link})",
122
account_deletion_subject: "계정 삭제 요청 - {hostname}",
123
account_deletion_body: "안녕하세요 @{handle}님,\n\n계정 삭제 확인 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 즉시 계정을 보호하세요.",
124
plc_operation_subject: "{hostname} - PLC 작업 토큰",
···
128
passkey_recovery_subject: "계정 복구 - {hostname}",
129
passkey_recovery_body: "안녕하세요 @{handle}님,\n\n패스키 전용 계정 복구를 요청하셨습니다.\n\n아래 링크를 클릭하여 임시 비밀번호를 설정하고 액세스를 복구하세요:\n{url}\n\n이 링크는 1시간 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요. 계정은 안전하게 유지됩니다.",
130
signup_verification_subject: "계정 인증 - {hostname}",
131
-
signup_verification_body: "환영합니다! 인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 30분 후에 만료됩니다.\n\n{hostname}에서 계정을 만들지 않았다면 이 이메일을 무시하세요.\n\n(위험을 감수하고 원클릭 인증: {verify_link})",
132
legacy_login_subject: "보안 알림: 레거시 로그인 감지 - {hostname}",
133
legacy_login_body: "안녕하세요 @{handle}님,\n\nTOTP 인증을 지원하지 않는 레거시 앱(예: Bluesky)을 사용한 로그인이 감지되었습니다.\n\n세부 정보:\n- 시간: {timestamp}\n- IP 주소: {ip}\n\n이 로그인에서 TOTP 보호가 우회되었습니다. 이 세션은 민감한 작업에 대한 권한이 제한됩니다.\n\n본인이 아닌 경우:\n1. 즉시 비밀번호를 변경하세요\n2. 활성 세션을 검토하세요\n3. 보안 설정에서 레거시 앱 로그인 비활성화를 고려하세요\n\n{hostname} 드림",
134
migration_verification_subject: "이메일 인증 - {hostname}",
135
-
migration_verification_body: "{hostname}에 오신 것을 환영합니다!\n\n계정 마이그레이션이 완료되었습니다. 설정을 완료하려면 이메일 주소를 인증하세요.\n\n인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 48시간 후에 만료됩니다.\n\n계정을 마이그레이션하지 않았다면 이 이메일을 무시하세요.\n\n(위험을 감수하고 원클릭 인증: {verify_link})",
136
};
137
138
static STRINGS_SV: NotificationStrings = NotificationStrings {
···
141
password_reset_subject: "Lösenordsåterställning - {hostname}",
142
password_reset_body: "Hej @{handle},\n\nDin kod för lösenordsåterställning är: {code}\n\nDenna kod upphör om 10 minuter.\n\nOm du inte begärde detta kan du ignorera detta meddelande.",
143
email_update_subject: "Bekräfta din nya e-post - {hostname}",
144
-
email_update_body: "Hej @{handle},\n\nDin verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 10 minuter.\n\nOm du inte begärde detta kan du ignorera detta meddelande.\n\n(Eller om du gillar att leva farligt: {verify_link})",
145
account_deletion_subject: "Begäran om kontoradering - {hostname}",
146
account_deletion_body: "Hej @{handle},\n\nDin bekräftelsekod för kontoradering är: {code}\n\nDenna kod upphör om 10 minuter.\n\nOm du inte begärde detta, skydda ditt konto omedelbart.",
147
plc_operation_subject: "{hostname} - PLC-operationstoken",
···
151
passkey_recovery_subject: "Kontoåterställning - {hostname}",
152
passkey_recovery_body: "Hej @{handle},\n\nDu begärde att återställa ditt endast nyckelkonto.\n\nKlicka på länken nedan för att ställa in ett tillfälligt lösenord och återfå åtkomst:\n{url}\n\nDenna länk upphör om 1 timme.\n\nOm du inte begärde detta kan du ignorera detta meddelande. Ditt konto förblir säkert.",
153
signup_verification_subject: "Verifiera ditt konto - {hostname}",
154
-
signup_verification_body: "Välkommen! Din verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 30 minuter.\n\nOm du inte skapade ett konto på {hostname}, ignorera detta meddelande.\n\n(Eller om du gillar att leva farligt: {verify_link})",
155
legacy_login_subject: "Säkerhetsvarning: Äldre inloggning upptäckt - {hostname}",
156
legacy_login_body: "Hej @{handle},\n\nEn inloggning till ditt konto upptäcktes med en äldre app (som Bluesky) som inte stöder TOTP-verifiering.\n\nDetaljer:\n- Tid: {timestamp}\n- IP-adress: {ip}\n\nDitt TOTP-skydd kringgicks för denna inloggning. Sessionen har begränsade behörigheter för känsliga operationer.\n\nOm detta inte var du:\n1. Ändra ditt lösenord omedelbart\n2. Granska dina aktiva sessioner\n3. Överväg att inaktivera äldre appinloggningar i dina säkerhetsinställningar\n\nVar försiktig,\n{hostname}",
157
migration_verification_subject: "Verifiera din e-post - {hostname}",
158
-
migration_verification_body: "Välkommen till {hostname}!\n\nDitt konto har migrerats framgångsrikt. För att slutföra installationen, verifiera din e-postadress.\n\nDin verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 48 timmar.\n\nOm du inte migrerade ditt konto kan du ignorera detta meddelande.\n\n(Eller om du gillar att leva farligt: {verify_link})",
159
};
160
161
static STRINGS_FI: NotificationStrings = NotificationStrings {
···
164
password_reset_subject: "Salasanan palautus - {hostname}",
165
password_reset_body: "Hei @{handle},\n\nSalasanan palautuskoodisi on: {code}\n\nTämä koodi vanhenee 10 minuutissa.\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta.",
166
email_update_subject: "Vahvista uusi sähköpostisi - {hostname}",
167
-
email_update_body: "Hei @{handle},\n\nVahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 10 minuutissa.\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta.\n\n(Tai jos pidät vaarallisesta elämästä: {verify_link})",
168
account_deletion_subject: "Tilin poistopyyntö - {hostname}",
169
account_deletion_body: "Hei @{handle},\n\nTilin poiston vahvistuskoodisi on: {code}\n\nTämä koodi vanhenee 10 minuutissa.\n\nJos et pyytänyt tätä, suojaa tilisi välittömästi.",
170
plc_operation_subject: "{hostname} - PLC-toimintotunniste",
···
174
passkey_recovery_subject: "Tilin palautus - {hostname}",
175
passkey_recovery_body: "Hei @{handle},\n\nPyysit palauttamaan vain pääsyavaintilisi.\n\nKlikkaa alla olevaa linkkiä asettaaksesi väliaikaisen salasanan ja saadaksesi pääsyn takaisin:\n{url}\n\nTämä linkki vanhenee tunnissa.\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta. Tilisi pysyy turvassa.",
176
signup_verification_subject: "Vahvista tilisi - {hostname}",
177
-
signup_verification_body: "Tervetuloa! Vahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 30 minuutissa.\n\nJos et luonut tiliä palveluun {hostname}, jätä tämä viesti huomiotta.\n\n(Tai jos pidät vaarallisesta elämästä: {verify_link})",
178
legacy_login_subject: "Turvallisuushälytys: Vanha kirjautuminen havaittu - {hostname}",
179
legacy_login_body: "Hei @{handle},\n\nTilillesi havaittiin kirjautuminen vanhalla sovelluksella (kuten Bluesky), joka ei tue TOTP-vahvistusta.\n\nTiedot:\n- Aika: {timestamp}\n- IP-osoite: {ip}\n\nTOTP-suojauksesi ohitettiin tässä kirjautumisessa. Istunnolla on rajoitetut oikeudet arkaluontoisiin toimintoihin.\n\nJos tämä et ollut sinä:\n1. Vaihda salasanasi välittömästi\n2. Tarkista aktiiviset istuntosi\n3. Harkitse vanhojen sovellusten kirjautumisen poistamista käytöstä turvallisuusasetuksissa\n\nOle varovainen,\n{hostname}",
180
migration_verification_subject: "Vahvista sähköpostisi - {hostname}",
181
-
migration_verification_body: "Tervetuloa palveluun {hostname}!\n\nTilisi on siirretty onnistuneesti. Viimeistele asennus vahvistamalla sähköpostiosoitteesi.\n\nVahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 48 tunnissa.\n\nJos et siirtänyt tiliäsi, voit jättää tämän viestin huomiotta.\n\n(Tai jos pidät vaarallisesta elämästä: {verify_link})",
182
};
183
184
pub fn format_message(template: &str, vars: &[(&str, &str)]) -> String {
···
49
password_reset_subject: "Password Reset - {hostname}",
50
password_reset_body: "Hello @{handle},\n\nYour password reset code is: {code}\n\nThis code will expire in 10 minutes.\n\nIf you did not request this, please ignore this message.",
51
email_update_subject: "Confirm your new email - {hostname}",
52
+
email_update_body: "Hello @{handle},\n\nYour verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 10 minutes.\n\nOr if you like to live dangerously:\n{verify_link}\n\nIf you did not request this, please ignore this email.",
53
account_deletion_subject: "Account Deletion Request - {hostname}",
54
account_deletion_body: "Hello @{handle},\n\nYour account deletion confirmation code is: {code}\n\nThis code will expire in 10 minutes.\n\nIf you did not request this, please secure your account immediately.",
55
plc_operation_subject: "{hostname} - PLC Operation Token",
···
59
passkey_recovery_subject: "Account Recovery - {hostname}",
60
passkey_recovery_body: "Hello @{handle},\n\nYou requested to recover your passkey-only account.\n\nClick the link below to set a temporary password and regain access:\n{url}\n\nThis link will expire in 1 hour.\n\nIf you did not request this, please ignore this message. Your account remains secure.",
61
signup_verification_subject: "Verify your account - {hostname}",
62
+
signup_verification_body: "Welcome! Your verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 30 minutes.\n\nOr if you like to live dangerously:\n{verify_link}\n\nIf you did not create an account on {hostname}, please ignore this message.",
63
legacy_login_subject: "Security Alert: Legacy Login Detected - {hostname}",
64
legacy_login_body: "Hello @{handle},\n\nA login to your account was detected using a legacy app (like Bluesky) that doesn't support TOTP verification.\n\nDetails:\n- Time: {timestamp}\n- IP Address: {ip}\n\nYour TOTP protection was bypassed for this login. The session has limited permissions for sensitive operations.\n\nIf this wasn't you, please:\n1. Change your password immediately\n2. Review your active sessions\n3. Consider disabling legacy app logins in your security settings\n\nStay safe,\n{hostname}",
65
migration_verification_subject: "Verify your email - {hostname}",
66
+
migration_verification_body: "Welcome to {hostname}!\n\nYour account has been migrated successfully. To complete the setup, please verify your email address.\n\nYour verification code is:\n{code}\n\nCopy the code above and enter it at:\n{verify_page}\n\nThis code will expire in 48 hours.\n\nOr if you like to live dangerously:\n{verify_link}\n\nIf you did not migrate your account, please ignore this email.",
67
};
68
69
static STRINGS_ZH: NotificationStrings = NotificationStrings {
···
72
password_reset_subject: "密码重置 - {hostname}",
73
password_reset_body: "您好 @{handle},\n\n您的密码重置验证码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请忽略此消息。",
74
email_update_subject: "确认您的新邮箱 - {hostname}",
75
+
email_update_body: "您好 @{handle},\n\n您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在10分钟后过期。\n\n或者直接点击链接:\n{verify_link}\n\n如果这不是您的操作,请忽略此邮件。",
76
account_deletion_subject: "账户删除请求 - {hostname}",
77
account_deletion_body: "您好 @{handle},\n\n您的账户删除确认码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请立即保护您的账户。",
78
plc_operation_subject: "{hostname} - PLC 操作令牌",
···
82
passkey_recovery_subject: "账户恢复 - {hostname}",
83
passkey_recovery_body: "您好 @{handle},\n\n您请求恢复仅通行密钥账户的访问权限。\n\n点击以下链接设置临时密码并恢复访问:\n{url}\n\n此链接将在1小时后过期。\n\n如果这不是您的操作,请忽略此消息。您的账户仍然安全。",
84
signup_verification_subject: "验证您的账户 - {hostname}",
85
+
signup_verification_body: "欢迎!您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在30分钟后过期。\n\n或者直接点击链接:\n{verify_link}\n\n如果您没有在 {hostname} 上创建账户,请忽略此消息。",
86
legacy_login_subject: "安全提醒:检测到传统应用登录 - {hostname}",
87
legacy_login_body: "您好 @{handle},\n\n检测到使用不支持 TOTP 验证的传统应用(如 Bluesky)登录您的账户。\n\n详细信息:\n- 时间:{timestamp}\n- IP 地址:{ip}\n\n此次登录绕过了 TOTP 保护。该会话对敏感操作的权限有限。\n\n如果这不是您的操作,请:\n1. 立即更改密码\n2. 检查您的活跃会话\n3. 考虑在安全设置中禁用传统应用登录\n\n请注意安全,\n{hostname}",
88
migration_verification_subject: "验证您的邮箱 - {hostname}",
89
+
migration_verification_body: "欢迎来到 {hostname}!\n\n您的账户已成功迁移。要完成设置,请验证您的邮箱地址。\n\n您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在 48 小时后过期。\n\n或者直接点击链接:\n{verify_link}\n\n如果您没有迁移账户,请忽略此邮件。",
90
};
91
92
static STRINGS_JA: NotificationStrings = NotificationStrings {
···
95
password_reset_subject: "パスワードリセット - {hostname}",
96
password_reset_body: "@{handle} 様\n\nパスワードリセットコードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。",
97
email_update_subject: "新しいメールアドレスの確認 - {hostname}",
98
+
email_update_body: "@{handle} 様\n\n確認コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは10分後に期限切れとなります。\n\n自己責任でワンクリック認証:\n{verify_link}\n\nこの操作に心当たりがない場合は、このメールを無視してください。",
99
account_deletion_subject: "アカウント削除リクエスト - {hostname}",
100
account_deletion_body: "@{handle} 様\n\nアカウント削除の確認コードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、直ちにアカウントを保護してください。",
101
plc_operation_subject: "{hostname} - PLC 操作トークン",
···
105
passkey_recovery_subject: "アカウント復旧 - {hostname}",
106
passkey_recovery_body: "@{handle} 様\n\nパスキー専用アカウントの復旧をリクエストされました。\n\n以下のリンクをクリックして一時パスワードを設定し、アクセスを回復してください:\n{url}\n\nこのリンクは1時間後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。アカウントは安全なままです。",
107
signup_verification_subject: "アカウント認証 - {hostname}",
108
+
signup_verification_body: "ようこそ!認証コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは30分後に期限切れとなります。\n\n自己責任でワンクリック認証:\n{verify_link}\n\n{hostname} でアカウントを作成していない場合は、このメールを無視してください。",
109
legacy_login_subject: "セキュリティ警告:レガシーログインを検出 - {hostname}",
110
legacy_login_body: "@{handle} 様\n\nTOTP 認証に対応していないレガシーアプリ(Bluesky など)からのログインが検出されました。\n\n詳細:\n- 時刻:{timestamp}\n- IP アドレス:{ip}\n\nこのログインでは TOTP 保護がバイパスされました。このセッションは機密操作に対する権限が制限されています。\n\n心当たりがない場合は:\n1. 直ちにパスワードを変更してください\n2. アクティブなセッションを確認してください\n3. セキュリティ設定でレガシーアプリのログインを無効にすることを検討してください\n\nご注意ください。\n{hostname}",
111
migration_verification_subject: "メールアドレスの認証 - {hostname}",
112
+
migration_verification_body: "{hostname} へようこそ!\n\nアカウントの移行が完了しました。設定を完了するには、メールアドレスを認証してください。\n\n認証コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは48時間後に期限切れとなります。\n\n自己責任でワンクリック認証:\n{verify_link}\n\nアカウントを移行していない場合は、このメールを無視してください。",
113
};
114
115
static STRINGS_KO: NotificationStrings = NotificationStrings {
···
118
password_reset_subject: "비밀번호 재설정 - {hostname}",
119
password_reset_body: "안녕하세요 @{handle}님,\n\n비밀번호 재설정 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요.",
120
email_update_subject: "새 이메일 주소 확인 - {hostname}",
121
+
email_update_body: "안녕하세요 @{handle}님,\n\n인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 10분 후에 만료됩니다.\n\n위험을 감수하고 원클릭 인증:\n{verify_link}\n\n요청하지 않으셨다면 이 이메일을 무시하세요.",
122
account_deletion_subject: "계정 삭제 요청 - {hostname}",
123
account_deletion_body: "안녕하세요 @{handle}님,\n\n계정 삭제 확인 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 즉시 계정을 보호하세요.",
124
plc_operation_subject: "{hostname} - PLC 작업 토큰",
···
128
passkey_recovery_subject: "계정 복구 - {hostname}",
129
passkey_recovery_body: "안녕하세요 @{handle}님,\n\n패스키 전용 계정 복구를 요청하셨습니다.\n\n아래 링크를 클릭하여 임시 비밀번호를 설정하고 액세스를 복구하세요:\n{url}\n\n이 링크는 1시간 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요. 계정은 안전하게 유지됩니다.",
130
signup_verification_subject: "계정 인증 - {hostname}",
131
+
signup_verification_body: "환영합니다! 인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 30분 후에 만료됩니다.\n\n위험을 감수하고 원클릭 인증:\n{verify_link}\n\n{hostname}에서 계정을 만들지 않았다면 이 이메일을 무시하세요.",
132
legacy_login_subject: "보안 알림: 레거시 로그인 감지 - {hostname}",
133
legacy_login_body: "안녕하세요 @{handle}님,\n\nTOTP 인증을 지원하지 않는 레거시 앱(예: Bluesky)을 사용한 로그인이 감지되었습니다.\n\n세부 정보:\n- 시간: {timestamp}\n- IP 주소: {ip}\n\n이 로그인에서 TOTP 보호가 우회되었습니다. 이 세션은 민감한 작업에 대한 권한이 제한됩니다.\n\n본인이 아닌 경우:\n1. 즉시 비밀번호를 변경하세요\n2. 활성 세션을 검토하세요\n3. 보안 설정에서 레거시 앱 로그인 비활성화를 고려하세요\n\n{hostname} 드림",
134
migration_verification_subject: "이메일 인증 - {hostname}",
135
+
migration_verification_body: "{hostname}에 오신 것을 환영합니다!\n\n계정 마이그레이션이 완료되었습니다. 설정을 완료하려면 이메일 주소를 인증하세요.\n\n인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 48시간 후에 만료됩니다.\n\n위험을 감수하고 원클릭 인증:\n{verify_link}\n\n계정을 마이그레이션하지 않았다면 이 이메일을 무시하세요.",
136
};
137
138
static STRINGS_SV: NotificationStrings = NotificationStrings {
···
141
password_reset_subject: "Lösenordsåterställning - {hostname}",
142
password_reset_body: "Hej @{handle},\n\nDin kod för lösenordsåterställning är: {code}\n\nDenna kod upphör om 10 minuter.\n\nOm du inte begärde detta kan du ignorera detta meddelande.",
143
email_update_subject: "Bekräfta din nya e-post - {hostname}",
144
+
email_update_body: "Hej @{handle},\n\nDin verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 10 minuter.\n\nEller om du gillar att leva farligt:\n{verify_link}\n\nOm du inte begärde detta kan du ignorera detta meddelande.",
145
account_deletion_subject: "Begäran om kontoradering - {hostname}",
146
account_deletion_body: "Hej @{handle},\n\nDin bekräftelsekod för kontoradering är: {code}\n\nDenna kod upphör om 10 minuter.\n\nOm du inte begärde detta, skydda ditt konto omedelbart.",
147
plc_operation_subject: "{hostname} - PLC-operationstoken",
···
151
passkey_recovery_subject: "Kontoåterställning - {hostname}",
152
passkey_recovery_body: "Hej @{handle},\n\nDu begärde att återställa ditt endast nyckelkonto.\n\nKlicka på länken nedan för att ställa in ett tillfälligt lösenord och återfå åtkomst:\n{url}\n\nDenna länk upphör om 1 timme.\n\nOm du inte begärde detta kan du ignorera detta meddelande. Ditt konto förblir säkert.",
153
signup_verification_subject: "Verifiera ditt konto - {hostname}",
154
+
signup_verification_body: "Välkommen! Din verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 30 minuter.\n\nEller om du gillar att leva farligt:\n{verify_link}\n\nOm du inte skapade ett konto på {hostname}, ignorera detta meddelande.",
155
legacy_login_subject: "Säkerhetsvarning: Äldre inloggning upptäckt - {hostname}",
156
legacy_login_body: "Hej @{handle},\n\nEn inloggning till ditt konto upptäcktes med en äldre app (som Bluesky) som inte stöder TOTP-verifiering.\n\nDetaljer:\n- Tid: {timestamp}\n- IP-adress: {ip}\n\nDitt TOTP-skydd kringgicks för denna inloggning. Sessionen har begränsade behörigheter för känsliga operationer.\n\nOm detta inte var du:\n1. Ändra ditt lösenord omedelbart\n2. Granska dina aktiva sessioner\n3. Överväg att inaktivera äldre appinloggningar i dina säkerhetsinställningar\n\nVar försiktig,\n{hostname}",
157
migration_verification_subject: "Verifiera din e-post - {hostname}",
158
+
migration_verification_body: "Välkommen till {hostname}!\n\nDitt konto har migrerats framgångsrikt. För att slutföra installationen, verifiera din e-postadress.\n\nDin verifieringskod är:\n{code}\n\nKopiera koden ovan och ange den på:\n{verify_page}\n\nDenna kod upphör om 48 timmar.\n\nEller om du gillar att leva farligt:\n{verify_link}\n\nOm du inte migrerade ditt konto kan du ignorera detta meddelande.",
159
};
160
161
static STRINGS_FI: NotificationStrings = NotificationStrings {
···
164
password_reset_subject: "Salasanan palautus - {hostname}",
165
password_reset_body: "Hei @{handle},\n\nSalasanan palautuskoodisi on: {code}\n\nTämä koodi vanhenee 10 minuutissa.\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta.",
166
email_update_subject: "Vahvista uusi sähköpostisi - {hostname}",
167
+
email_update_body: "Hei @{handle},\n\nVahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 10 minuutissa.\n\nTai jos pidät vaarallisesta elämästä:\n{verify_link}\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta.",
168
account_deletion_subject: "Tilin poistopyyntö - {hostname}",
169
account_deletion_body: "Hei @{handle},\n\nTilin poiston vahvistuskoodisi on: {code}\n\nTämä koodi vanhenee 10 minuutissa.\n\nJos et pyytänyt tätä, suojaa tilisi välittömästi.",
170
plc_operation_subject: "{hostname} - PLC-toimintotunniste",
···
174
passkey_recovery_subject: "Tilin palautus - {hostname}",
175
passkey_recovery_body: "Hei @{handle},\n\nPyysit palauttamaan vain pääsyavaintilisi.\n\nKlikkaa alla olevaa linkkiä asettaaksesi väliaikaisen salasanan ja saadaksesi pääsyn takaisin:\n{url}\n\nTämä linkki vanhenee tunnissa.\n\nJos et pyytänyt tätä, voit jättää tämän viestin huomiotta. Tilisi pysyy turvassa.",
176
signup_verification_subject: "Vahvista tilisi - {hostname}",
177
+
signup_verification_body: "Tervetuloa! Vahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 30 minuutissa.\n\nTai jos pidät vaarallisesta elämästä:\n{verify_link}\n\nJos et luonut tiliä palveluun {hostname}, jätä tämä viesti huomiotta.",
178
legacy_login_subject: "Turvallisuushälytys: Vanha kirjautuminen havaittu - {hostname}",
179
legacy_login_body: "Hei @{handle},\n\nTilillesi havaittiin kirjautuminen vanhalla sovelluksella (kuten Bluesky), joka ei tue TOTP-vahvistusta.\n\nTiedot:\n- Aika: {timestamp}\n- IP-osoite: {ip}\n\nTOTP-suojauksesi ohitettiin tässä kirjautumisessa. Istunnolla on rajoitetut oikeudet arkaluontoisiin toimintoihin.\n\nJos tämä et ollut sinä:\n1. Vaihda salasanasi välittömästi\n2. Tarkista aktiiviset istuntosi\n3. Harkitse vanhojen sovellusten kirjautumisen poistamista käytöstä turvallisuusasetuksissa\n\nOle varovainen,\n{hostname}",
180
migration_verification_subject: "Vahvista sähköpostisi - {hostname}",
181
+
migration_verification_body: "Tervetuloa palveluun {hostname}!\n\nTilisi on siirretty onnistuneesti. Viimeistele asennus vahvistamalla sähköpostiosoitteesi.\n\nVahvistuskoodisi on:\n{code}\n\nKopioi koodi yllä ja syötä se osoitteessa:\n{verify_page}\n\nTämä koodi vanhenee 48 tunnissa.\n\nTai jos pidät vaarallisesta elämästä:\n{verify_link}\n\nJos et siirtänyt tiliäsi, voit jättää tämän viestin huomiotta.",
182
};
183
184
pub fn format_message(template: &str, vars: &[(&str, &str)]) -> String {
+11
-1
src/comms/sender.rs
+11
-1
src/comms/sender.rs
···
1
use async_trait::async_trait;
2
use reqwest::Client;
3
use serde_json::json;
4
use std::process::Stdio;
···
57
value.replace(['\r', '\n'], " ").trim().to_string()
58
}
59
60
pub fn is_valid_phone_number(number: &str) -> bool {
61
if number.len() < 2 || number.len() > 20 {
62
return false;
···
94
95
pub fn format_email(&self, notification: &QueuedComms) -> String {
96
let subject =
97
-
sanitize_header_value(notification.subject.as_deref().unwrap_or("Notification"));
98
let recipient = sanitize_header_value(¬ification.recipient);
99
let from_header = if self.from_name.is_empty() {
100
self.from_address.clone()
···
1
use async_trait::async_trait;
2
+
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
3
use reqwest::Client;
4
use serde_json::json;
5
use std::process::Stdio;
···
58
value.replace(['\r', '\n'], " ").trim().to_string()
59
}
60
61
+
pub fn mime_encode_header(value: &str) -> String {
62
+
if value.is_ascii() {
63
+
sanitize_header_value(value)
64
+
} else {
65
+
let sanitized = sanitize_header_value(value);
66
+
format!("=?UTF-8?B?{}?=", BASE64.encode(sanitized.as_bytes()))
67
+
}
68
+
}
69
+
70
pub fn is_valid_phone_number(number: &str) -> bool {
71
if number.len() < 2 || number.len() > 20 {
72
return false;
···
104
105
pub fn format_email(&self, notification: &QueuedComms) -> String {
106
let subject =
107
+
mime_encode_header(notification.subject.as_deref().unwrap_or("Notification"));
108
let recipient = sanitize_header_value(¬ification.recipient);
109
let from_header = if self.from_name.is_empty() {
110
self.from_address.clone()