Our Personal Data Server from scratch! tranquil.farm
pds rust database fun oauth atproto
238
fork

Configure Feed

Select the types of activity you want to include in your feed.

Misc fixes

+51 -73
+3 -2
frontend/src/lib/api.ts
··· 57 57 message: res.statusText, 58 58 })); 59 59 if ( 60 - res.status === 401 && err.error === "AuthenticationFailed" && token && 61 - tokenRefreshCallback && !skipRetry 60 + res.status === 401 && 61 + (err.error === "AuthenticationFailed" || err.error === "ExpiredToken") && 62 + token && tokenRefreshCallback && !skipRetry 62 63 ) { 63 64 const newToken = await tokenRefreshCallback(); 64 65 if (newToken && newToken !== token) {
+2
frontend/src/locales/en.json
··· 172 172 "navCommsDesc": "Discord, Telegram, Signal channels", 173 173 "navRepo": "Repository Explorer", 174 174 "navRepoDesc": "Browse and manage raw AT Protocol records", 175 + "navDelegation": "Delegation", 176 + "navDelegationDesc": "Manage account controllers and delegated accounts", 175 177 "navAdmin": "Admin Panel", 176 178 "navAdminDesc": "Server stats and admin operations" 177 179 },
+2
frontend/src/locales/fi.json
··· 172 172 "navCommsDesc": "Discord-, Telegram-, Signal-kanavat", 173 173 "navRepo": "Tietovarastoselaaja", 174 174 "navRepoDesc": "Selaa ja hallitse raakoja AT Protocol -tietueita", 175 + "navDelegation": "Delegointi", 176 + "navDelegationDesc": "Hallitse tilin ohjaajia ja delegoituja tilejä", 175 177 "navAdmin": "Ylläpitopaneeli", 176 178 "navAdminDesc": "Palvelintilastot ja ylläpitotoiminnot" 177 179 },
+2
frontend/src/locales/ja.json
··· 172 172 "navCommsDesc": "Discord、Telegram、Signal チャンネル", 173 173 "navRepo": "リポジトリエクスプローラー", 174 174 "navRepoDesc": "AT Protocol レコードを閲覧・管理", 175 + "navDelegation": "委任", 176 + "navDelegationDesc": "アカウントコントローラーと委任アカウントを管理", 175 177 "navAdmin": "管理パネル", 176 178 "navAdminDesc": "サーバー統計と管理操作" 177 179 },
+2
frontend/src/locales/ko.json
··· 172 172 "navCommsDesc": "Discord, Telegram, Signal 채널", 173 173 "navRepo": "저장소 탐색기", 174 174 "navRepoDesc": "AT Protocol 레코드 탐색 및 관리", 175 + "navDelegation": "위임", 176 + "navDelegationDesc": "계정 컨트롤러 및 위임된 계정 관리", 175 177 "navAdmin": "관리 패널", 176 178 "navAdminDesc": "서버 통계 및 관리 작업" 177 179 },
+2
frontend/src/locales/sv.json
··· 172 172 "navCommsDesc": "Discord, Telegram, Signal-kanaler", 173 173 "navRepo": "Dataförvarsutforskare", 174 174 "navRepoDesc": "Bläddra och hantera råa AT Protocol-poster", 175 + "navDelegation": "Delegering", 176 + "navDelegationDesc": "Hantera kontokontrollanter och delegerade konton", 175 177 "navAdmin": "Adminpanel", 176 178 "navAdminDesc": "Serverstatistik och administratörsoperationer" 177 179 },
+3
frontend/src/locales/zh.json
··· 172 172 "navCommsDesc": "Discord、Telegram、Signal 渠道设置", 173 173 "navRepo": "数据浏览器", 174 174 "navRepoDesc": "浏览和管理原始 AT Protocol 记录", 175 + "navDelegation": "账户委托", 176 + "navDelegationDesc": "管理控制者和委托账户", 175 177 "navAdmin": "管理后台", 176 178 "navAdminDesc": "服务器统计和管理操作" 177 179 }, ··· 912 914 "actor": "执行者", 913 915 "controller": "控制者", 914 916 "account": "账户", 917 + "accountCreated": "已创建委托账户:{handle}", 915 918 "details": "详情", 916 919 "actionGrantCreated": "授权创建", 917 920 "actionGrantRevoked": "授权撤销",
+2 -2
frontend/src/routes/Dashboard.svelte
··· 187 187 <p>{$_('dashboard.navRepoDesc')}</p> 188 188 </a> 189 189 <a href="#/controllers" class="nav-card"> 190 - <h3>Delegation</h3> 191 - <p>Manage account controllers and delegated accounts</p> 190 + <h3>{$_('dashboard.navDelegation')}</h3> 191 + <p>{$_('dashboard.navDelegationDesc')}</p> 192 192 </a> 193 193 {#if auth.session.isAdmin} 194 194 <a href="#/admin" class="nav-card admin-card">
+1 -1
src/api/server/verify_email.rs
··· 28 28 identifier: input.email, 29 29 }; 30 30 31 - let result = super::verify_token::verify_token_internal(&state, None, token_input).await?; 31 + let result = super::verify_token::verify_token_internal(&state, token_input).await?; 32 32 33 33 Ok(Json(VerifyMigrationEmailOutput { 34 34 success: result.success,
+2 -46
src/api/server/verify_token.rs
··· 1 1 use axum::{ 2 2 Json, 3 3 extract::State, 4 - http::{HeaderMap, StatusCode}, 4 + http::StatusCode, 5 5 }; 6 6 use serde::{Deserialize, Serialize}; 7 7 use serde_json::json; ··· 30 30 31 31 pub async fn verify_token( 32 32 State(state): State<AppState>, 33 - headers: HeaderMap, 34 33 Json(input): Json<VerifyTokenInput>, 35 34 ) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> { 36 - verify_token_internal(&state, Some(&headers), input).await 35 + verify_token_internal(&state, input).await 37 36 } 38 37 39 38 pub async fn verify_token_internal( 40 39 state: &AppState, 41 - headers: Option<&HeaderMap>, 42 40 input: VerifyTokenInput, 43 41 ) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> { 44 42 let normalized_token = normalize_token_input(&input.token); ··· 95 93 .await 96 94 } 97 95 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 96 handle_channel_update(state, &token_data.did, &token_data.channel, &identifier).await 108 97 } 109 98 VerificationPurpose::Signup => { ··· 111 100 .await 112 101 } 113 102 } 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 103 } 148 104 149 105 async fn handle_migration_verification(
+1 -3
src/api/verification.rs
··· 2 2 use axum::{ 3 3 Json, 4 4 extract::State, 5 - http::HeaderMap, 6 5 response::{IntoResponse, Response}, 7 6 }; 8 7 use serde::Deserialize; ··· 18 17 19 18 pub async fn confirm_channel_verification( 20 19 State(state): State<AppState>, 21 - headers: HeaderMap, 22 20 Json(input): Json<ConfirmChannelVerificationInput>, 23 21 ) -> Response { 24 22 let token_input = crate::api::server::VerifyTokenInput { ··· 26 24 identifier: input.identifier, 27 25 }; 28 26 29 - match crate::api::server::verify_token_internal(&state, Some(&headers), token_input).await { 27 + match crate::api::server::verify_token_internal(&state, token_input).await { 30 28 Ok(output) => Json(json!({"success": output.success})).into_response(), 31 29 Err((status, err_json)) => (status, err_json).into_response(), 32 30 }
+18 -18
src/comms/locale.rs
··· 49 49 password_reset_subject: "Password Reset - {hostname}", 50 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 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})", 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 53 account_deletion_subject: "Account Deletion Request - {hostname}", 54 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 55 plc_operation_subject: "{hostname} - PLC Operation Token", ··· 59 59 passkey_recovery_subject: "Account Recovery - {hostname}", 60 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 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})", 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 63 legacy_login_subject: "Security Alert: Legacy Login Detected - {hostname}", 64 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 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})", 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 67 }; 68 68 69 69 static STRINGS_ZH: NotificationStrings = NotificationStrings { ··· 72 72 password_reset_subject: "密码重置 - {hostname}", 73 73 password_reset_body: "您好 @{handle},\n\n您的密码重置验证码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请忽略此消息。", 74 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})", 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 76 account_deletion_subject: "账户删除请求 - {hostname}", 77 77 account_deletion_body: "您好 @{handle},\n\n您的账户删除确认码是:{code}\n\n此验证码将在10分钟后过期。\n\n如果这不是您的操作,请立即保护您的账户。", 78 78 plc_operation_subject: "{hostname} - PLC 操作令牌", ··· 82 82 passkey_recovery_subject: "账户恢复 - {hostname}", 83 83 passkey_recovery_body: "您好 @{handle},\n\n您请求恢复仅通行密钥账户的访问权限。\n\n点击以下链接设置临时密码并恢复访问:\n{url}\n\n此链接将在1小时后过期。\n\n如果这不是您的操作,请忽略此消息。您的账户仍然安全。", 84 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})", 85 + signup_verification_body: "欢迎!您的验证码是:\n{code}\n\n复制上述验证码并在此输入:\n{verify_page}\n\n此验证码将在30分钟后过期。\n\n或者直接点击链接:\n{verify_link}\n\n如果您没有在 {hostname} 上创建账户,请忽略此消息。", 86 86 legacy_login_subject: "安全提醒:检测到传统应用登录 - {hostname}", 87 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 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})", 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 90 }; 91 91 92 92 static STRINGS_JA: NotificationStrings = NotificationStrings { ··· 95 95 password_reset_subject: "パスワードリセット - {hostname}", 96 96 password_reset_body: "@{handle} 様\n\nパスワードリセットコードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。", 97 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})", 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 99 account_deletion_subject: "アカウント削除リクエスト - {hostname}", 100 100 account_deletion_body: "@{handle} 様\n\nアカウント削除の確認コードは:{code}\n\nこのコードは10分後に期限切れとなります。\n\nこの操作に心当たりがない場合は、直ちにアカウントを保護してください。", 101 101 plc_operation_subject: "{hostname} - PLC 操作トークン", ··· 105 105 passkey_recovery_subject: "アカウント復旧 - {hostname}", 106 106 passkey_recovery_body: "@{handle} 様\n\nパスキー専用アカウントの復旧をリクエストされました。\n\n以下のリンクをクリックして一時パスワードを設定し、アクセスを回復してください:\n{url}\n\nこのリンクは1時間後に期限切れとなります。\n\nこの操作に心当たりがない場合は、このメッセージを無視してください。アカウントは安全なままです。", 107 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})", 108 + signup_verification_body: "ようこそ!認証コードは:\n{code}\n\n上記のコードをコピーして、こちらで入力してください:\n{verify_page}\n\nこのコードは30分後に期限切れとなります。\n\n自己責任でワンクリック認証:\n{verify_link}\n\n{hostname} でアカウントを作成していない場合は、このメールを無視してください。", 109 109 legacy_login_subject: "セキュリティ警告:レガシーログインを検出 - {hostname}", 110 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 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})", 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 113 }; 114 114 115 115 static STRINGS_KO: NotificationStrings = NotificationStrings { ··· 118 118 password_reset_subject: "비밀번호 재설정 - {hostname}", 119 119 password_reset_body: "안녕하세요 @{handle}님,\n\n비밀번호 재설정 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요.", 120 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})", 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 122 account_deletion_subject: "계정 삭제 요청 - {hostname}", 123 123 account_deletion_body: "안녕하세요 @{handle}님,\n\n계정 삭제 확인 코드는: {code}\n\n이 코드는 10분 후에 만료됩니다.\n\n요청하지 않으셨다면 즉시 계정을 보호하세요.", 124 124 plc_operation_subject: "{hostname} - PLC 작업 토큰", ··· 128 128 passkey_recovery_subject: "계정 복구 - {hostname}", 129 129 passkey_recovery_body: "안녕하세요 @{handle}님,\n\n패스키 전용 계정 복구를 요청하셨습니다.\n\n아래 링크를 클릭하여 임시 비밀번호를 설정하고 액세스를 복구하세요:\n{url}\n\n이 링크는 1시간 후에 만료됩니다.\n\n요청하지 않으셨다면 이 메시지를 무시하세요. 계정은 안전하게 유지됩니다.", 130 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})", 131 + signup_verification_body: "환영합니다! 인증 코드는:\n{code}\n\n위 코드를 복사하여 여기에 입력하세요:\n{verify_page}\n\n이 코드는 30분 후에 만료됩니다.\n\n위험을 감수하고 원클릭 인증:\n{verify_link}\n\n{hostname}에서 계정을 만들지 않았다면 이 이메일을 무시하세요.", 132 132 legacy_login_subject: "보안 알림: 레거시 로그인 감지 - {hostname}", 133 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 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})", 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 136 }; 137 137 138 138 static STRINGS_SV: NotificationStrings = NotificationStrings { ··· 141 141 password_reset_subject: "Lösenordsåterställning - {hostname}", 142 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 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})", 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 145 account_deletion_subject: "Begäran om kontoradering - {hostname}", 146 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 147 plc_operation_subject: "{hostname} - PLC-operationstoken", ··· 151 151 passkey_recovery_subject: "Kontoåterställning - {hostname}", 152 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 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})", 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 155 legacy_login_subject: "Säkerhetsvarning: Äldre inloggning upptäckt - {hostname}", 156 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 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})", 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 159 }; 160 160 161 161 static STRINGS_FI: NotificationStrings = NotificationStrings { ··· 164 164 password_reset_subject: "Salasanan palautus - {hostname}", 165 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 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})", 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 168 account_deletion_subject: "Tilin poistopyyntö - {hostname}", 169 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 170 plc_operation_subject: "{hostname} - PLC-toimintotunniste", ··· 174 174 passkey_recovery_subject: "Tilin palautus - {hostname}", 175 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 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})", 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 178 legacy_login_subject: "Turvallisuushälytys: Vanha kirjautuminen havaittu - {hostname}", 179 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 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})", 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 182 }; 183 183 184 184 pub fn format_message(template: &str, vars: &[(&str, &str)]) -> String {
+11 -1
src/comms/sender.rs
··· 1 1 use async_trait::async_trait; 2 + use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; 2 3 use reqwest::Client; 3 4 use serde_json::json; 4 5 use std::process::Stdio; ··· 57 58 value.replace(['\r', '\n'], " ").trim().to_string() 58 59 } 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 + 60 70 pub fn is_valid_phone_number(number: &str) -> bool { 61 71 if number.len() < 2 || number.len() > 20 { 62 72 return false; ··· 94 104 95 105 pub fn format_email(&self, notification: &QueuedComms) -> String { 96 106 let subject = 97 - sanitize_header_value(notification.subject.as_deref().unwrap_or("Notification")); 107 + mime_encode_header(notification.subject.as_deref().unwrap_or("Notification")); 98 108 let recipient = sanitize_header_value(&notification.recipient); 99 109 let from_header = if self.from_name.is_empty() { 100 110 self.from_address.clone()