From 5c8894d531d2a5f1a945114548fe79f96f0e214f Mon Sep 17 00:00:00 2001 From: Lewis Date: Wed, 18 Mar 2026 18:36:16 +0200 Subject: [PATCH] refactor: update routes, backend verification tweaks, and restyle --- Cargo.lock | 40 +-- Cargo.toml | 2 +- crates/tranquil-api/src/identity/account.rs | 27 +- crates/tranquil-api/src/lib.rs | 4 - crates/tranquil-api/src/server/email.rs | 34 -- crates/tranquil-api/src/server/mod.rs | 2 +- .../tranquil-api/src/server/verify_email.rs | 23 +- .../tranquil-api/src/server/verify_token.rs | 52 ++- crates/tranquil-db-traits/src/user.rs | 6 - crates/tranquil-db/src/postgres/user.rs | 27 -- .../src/endpoints/authorize.rs | 11 +- .../src/auth/verification_token.rs | 22 +- crates/tranquil-pds/src/comms/service.rs | 11 +- crates/tranquil-pds/tests/legacy_2fa.rs | 2 +- frontend/src/App.svelte | 14 +- .../src/components/CommsChannelPicker.svelte | 20 -- .../components/dashboard/CommsContent.svelte | 32 -- frontend/src/lib/api.ts | 9 - frontend/src/lib/auth.svelte.ts | 17 +- frontend/src/lib/registration/flow.svelte.ts | 24 -- frontend/src/locales/en.json | 19 +- frontend/src/locales/fi.json | 19 +- frontend/src/locales/ja.json | 19 +- frontend/src/locales/ko.json | 19 +- frontend/src/locales/sv.json | 19 +- frontend/src/locales/zh.json | 19 +- frontend/src/routes/OAuthAccounts.svelte | 24 +- frontend/src/routes/OAuthConsent.svelte | 16 +- frontend/src/routes/OAuthLogin.svelte | 26 +- frontend/src/routes/OAuthVerifyCode.svelte | 161 +++++++++ frontend/src/routes/Register.svelte | 312 +++++++----------- frontend/src/routes/ResetPassword.svelte | 16 - .../src/routes/SsoRegisterComplete.svelte | 63 +--- frontend/src/routes/Verify.svelte | 2 +- frontend/src/styles/base.css | 97 +----- frontend/src/styles/pages.css | 207 ++++-------- 36 files changed, 567 insertions(+), 850 deletions(-) create mode 100644 frontend/src/routes/OAuthVerifyCode.svelte diff --git a/Cargo.lock b/Cargo.lock index b0c343b..5a17f81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "tranquil-api" -version = "0.4.4" +version = "0.4.5" dependencies = [ "anyhow", "axum", @@ -6142,7 +6142,7 @@ dependencies = [ [[package]] name = "tranquil-auth" -version = "0.4.4" +version = "0.4.5" dependencies = [ "anyhow", "base32", @@ -6165,7 +6165,7 @@ dependencies = [ [[package]] name = "tranquil-cache" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "base64 0.22.1", @@ -6179,7 +6179,7 @@ dependencies = [ [[package]] name = "tranquil-comms" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "base64 0.22.1", @@ -6194,7 +6194,7 @@ dependencies = [ [[package]] name = "tranquil-config" -version = "0.4.4" +version = "0.4.5" dependencies = [ "confique", "serde", @@ -6202,7 +6202,7 @@ dependencies = [ [[package]] name = "tranquil-crypto" -version = "0.4.4" +version = "0.4.5" dependencies = [ "aes-gcm", "base64 0.22.1", @@ -6218,7 +6218,7 @@ dependencies = [ [[package]] name = "tranquil-db" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "chrono", @@ -6235,7 +6235,7 @@ dependencies = [ [[package]] name = "tranquil-db-traits" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "base64 0.22.1", @@ -6251,7 +6251,7 @@ dependencies = [ [[package]] name = "tranquil-infra" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "bytes", @@ -6262,7 +6262,7 @@ dependencies = [ [[package]] name = "tranquil-lexicon" -version = "0.4.4" +version = "0.4.5" dependencies = [ "chrono", "hickory-resolver", @@ -6280,7 +6280,7 @@ dependencies = [ [[package]] name = "tranquil-oauth" -version = "0.4.4" +version = "0.4.5" dependencies = [ "anyhow", "axum", @@ -6303,7 +6303,7 @@ dependencies = [ [[package]] name = "tranquil-oauth-server" -version = "0.4.4" +version = "0.4.5" dependencies = [ "axum", "base64 0.22.1", @@ -6336,7 +6336,7 @@ dependencies = [ [[package]] name = "tranquil-pds" -version = "0.4.4" +version = "0.4.5" dependencies = [ "aes-gcm", "anyhow", @@ -6424,7 +6424,7 @@ dependencies = [ [[package]] name = "tranquil-repo" -version = "0.4.4" +version = "0.4.5" dependencies = [ "bytes", "cid", @@ -6436,7 +6436,7 @@ dependencies = [ [[package]] name = "tranquil-ripple" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "backon", @@ -6461,7 +6461,7 @@ dependencies = [ [[package]] name = "tranquil-scopes" -version = "0.4.4" +version = "0.4.5" dependencies = [ "axum", "futures", @@ -6477,7 +6477,7 @@ dependencies = [ [[package]] name = "tranquil-server" -version = "0.4.4" +version = "0.4.5" dependencies = [ "axum", "clap", @@ -6497,7 +6497,7 @@ dependencies = [ [[package]] name = "tranquil-storage" -version = "0.4.4" +version = "0.4.5" dependencies = [ "async-trait", "aws-config", @@ -6514,7 +6514,7 @@ dependencies = [ [[package]] name = "tranquil-sync" -version = "0.4.4" +version = "0.4.5" dependencies = [ "anyhow", "axum", @@ -6536,7 +6536,7 @@ dependencies = [ [[package]] name = "tranquil-types" -version = "0.4.4" +version = "0.4.5" dependencies = [ "chrono", "cid", diff --git a/Cargo.toml b/Cargo.toml index 1cffd97..c455706 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.4.4" +version = "0.4.5" edition = "2024" license = "AGPL-3.0-or-later" diff --git a/crates/tranquil-api/src/identity/account.rs b/crates/tranquil-api/src/identity/account.rs index 0248eda..df9f25a 100644 --- a/crates/tranquil-api/src/identity/account.rs +++ b/crates/tranquil-api/src/identity/account.rs @@ -153,9 +153,7 @@ pub async fn create_account( let verification_channel = input .verification_channel .unwrap_or(tranquil_db_traits::CommsChannel::Email); - let verification_recipient = if is_migration { - None - } else { + let verification_recipient = { Some(match verification_channel { tranquil_db_traits::CommsChannel::Email => match &input.email { Some(email) if !email.trim().is_empty() => email.trim().to_string(), @@ -372,9 +370,11 @@ pub async fn create_account( return ApiError::InternalError(None).into_response(); } let hostname = &tranquil_config::get().server.hostname; - let verification_required = if let Some(ref user_email) = email { + let verification_required = if let Some(ref recipient) = verification_recipient { let token = tranquil_pds::auth::verification_token::generate_migration_token( - &did_typed, user_email, + &did_typed, + verification_channel, + recipient, ); let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token); @@ -382,13 +382,14 @@ pub async fn create_account( state.user_repo.as_ref(), state.infra_repo.as_ref(), reactivated.user_id, - user_email, + verification_channel, + recipient, &formatted_token, hostname, ) .await { - warn!("Failed to enqueue migration verification email: {:?}", e); + warn!("Failed to enqueue migration verification: {:?}", e); } true } else { @@ -403,7 +404,7 @@ pub async fn create_account( access_jwt: access_meta.token, refresh_jwt: refresh_meta.token, verification_required, - verification_channel: tranquil_db_traits::CommsChannel::Email, + verification_channel, }), ) .into_response(); @@ -668,10 +669,11 @@ pub async fn create_account( ); } } - } else if let Some(ref user_email) = email { + } else if let Some(ref recipient) = verification_recipient { let token = tranquil_pds::auth::verification_token::generate_migration_token( &did_for_commit, - user_email, + verification_channel, + recipient, ); let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token); @@ -679,13 +681,14 @@ pub async fn create_account( state.user_repo.as_ref(), state.infra_repo.as_ref(), user_id, - user_email, + verification_channel, + recipient, &formatted_token, hostname, ) .await { - warn!("Failed to enqueue migration verification email: {:?}", e); + warn!("Failed to enqueue migration verification: {:?}", e); } } diff --git a/crates/tranquil-api/src/lib.rs b/crates/tranquil-api/src/lib.rs index d547fef..eda0ebf 100644 --- a/crates/tranquil-api/src/lib.rs +++ b/crates/tranquil-api/src/lib.rs @@ -215,10 +215,6 @@ pub fn api_routes() -> axum::Router { "/_account.checkEmailInUse", post(server::check_email_in_use), ) - .route( - "/_account.checkCommsChannelInUse", - post(server::check_comms_channel_in_use), - ) .route( "/com.atproto.server.reserveSigningKey", post(server::reserve_signing_key), diff --git a/crates/tranquil-api/src/server/email.rs b/crates/tranquil-api/src/server/email.rs index d1663fe..087ea91 100644 --- a/crates/tranquil-api/src/server/email.rs +++ b/crates/tranquil-api/src/server/email.rs @@ -606,37 +606,3 @@ pub async fn check_email_in_use( })) .into_response() } - -#[derive(Deserialize)] -pub struct CheckCommsChannelInUseInput { - pub channel: CommsChannel, - pub identifier: String, -} - -pub async fn check_comms_channel_in_use( - State(state): State, - _rate_limit: RateLimited, - Json(input): Json, -) -> Response { - let identifier = input.identifier.trim(); - if identifier.is_empty() { - return ApiError::InvalidRequest("identifier is required".into()).into_response(); - } - - let count = match state - .user_repo - .count_accounts_by_comms_identifier(input.channel, identifier) - .await - { - Ok(c) => c, - Err(e) => { - error!("DB error checking comms channel usage: {:?}", e); - return ApiError::InternalError(None).into_response(); - } - }; - - Json(json!({ - "inUse": count > 0, - })) - .into_response() -} diff --git a/crates/tranquil-api/src/server/mod.rs b/crates/tranquil-api/src/server/mod.rs index d3da180..7dbc6cc 100644 --- a/crates/tranquil-api/src/server/mod.rs +++ b/crates/tranquil-api/src/server/mod.rs @@ -23,7 +23,7 @@ pub use account_status::{ }; pub use app_password::{create_app_password, list_app_passwords, revoke_app_password}; pub use email::{ - authorize_email_update, check_channel_verified, check_comms_channel_in_use, check_email_in_use, + authorize_email_update, check_channel_verified, check_email_in_use, check_email_update_status, check_email_verified, confirm_email, request_email_update, update_email, }; diff --git a/crates/tranquil-api/src/server/verify_email.rs b/crates/tranquil-api/src/server/verify_email.rs index a99c688..72f8c53 100644 --- a/crates/tranquil-api/src/server/verify_email.rs +++ b/crates/tranquil-api/src/server/verify_email.rs @@ -40,7 +40,8 @@ pub async fn verify_migration_email( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ResendMigrationVerificationInput { - pub email: String, + pub channel: Option, + pub identifier: String, } #[derive(Serialize)] @@ -53,9 +54,12 @@ pub async fn resend_migration_verification( State(state): State, Json(input): Json, ) -> Result, ApiError> { - let email = input.email.trim().to_lowercase(); + let channel = input + .channel + .unwrap_or(tranquil_db_traits::CommsChannel::Email); + let identifier = input.identifier.trim().to_lowercase(); - let user = match state.user_repo.get_by_email(&email).await { + let user = match state.user_repo.get_by_email(&identifier).await { Ok(Some(u)) => u, Ok(None) => { return Ok(Json(ResendMigrationVerificationOutput { sent: true })); @@ -71,23 +75,28 @@ pub async fn resend_migration_verification( } let hostname = &tranquil_config::get().server.hostname; - let token = tranquil_pds::auth::verification_token::generate_migration_token(&user.did, &email); + let token = tranquil_pds::auth::verification_token::generate_migration_token( + &user.did, + channel, + &identifier, + ); let formatted_token = tranquil_pds::auth::verification_token::format_token_for_display(&token); if let Err(e) = tranquil_pds::comms::comms_repo::enqueue_migration_verification( state.user_repo.as_ref(), state.infra_repo.as_ref(), user.id, - &email, + channel, + &identifier, &formatted_token, hostname, ) .await { - warn!(error = ?e, "Failed to enqueue migration verification email"); + warn!(error = ?e, channel = ?channel, "Failed to enqueue migration verification"); } - info!(did = %user.did, "Resent migration verification email"); + info!(did = %user.did, channel = ?channel, "Resent migration verification"); Ok(Json(ResendMigrationVerificationOutput { sent: true })) } diff --git a/crates/tranquil-api/src/server/verify_token.rs b/crates/tranquil-api/src/server/verify_token.rs index 14fbc13..bfbe492 100644 --- a/crates/tranquil-api/src/server/verify_token.rs +++ b/crates/tranquil-api/src/server/verify_token.rs @@ -72,10 +72,6 @@ async fn handle_migration_verification( channel: CommsChannel, identifier: &str, ) -> Result, ApiError> { - if channel != CommsChannel::Email { - return Err(ApiError::InvalidChannel); - } - let user = state .user_repo .get_verification_info(did) @@ -83,19 +79,43 @@ async fn handle_migration_verification( .log_db_err("during migration verification")? .ok_or(ApiError::AccountNotFound)?; - if user.email.as_ref().map(|e| e.to_lowercase()) != Some(identifier.to_string()) { - return Err(ApiError::IdentifierMismatch); - } + match channel { + CommsChannel::Email => { + if user.email.as_ref().map(|e| e.to_lowercase()) != Some(identifier.to_string()) { + return Err(ApiError::IdentifierMismatch); + } + if !user.channel_verification.email { + state + .user_repo + .set_email_verified_flag(user.id) + .await + .log_db_err("updating email_verified status")?; + } + } + CommsChannel::Discord => { + state + .user_repo + .set_discord_verified_flag(user.id) + .await + .log_db_err("updating discord verified status")?; + } + CommsChannel::Telegram => { + state + .user_repo + .set_telegram_verified_flag(user.id) + .await + .log_db_err("updating telegram verified status")?; + } + CommsChannel::Signal => { + state + .user_repo + .set_signal_verified_flag(user.id) + .await + .log_db_err("updating signal verified status")?; + } + }; - if !user.channel_verification.email { - state - .user_repo - .set_email_verified_flag(user.id) - .await - .log_db_err("updating email_verified status")?; - } - - info!(did = %did, "Migration email verified successfully"); + info!(did = %did, channel = ?channel, "Migration verification completed successfully"); Ok(Json(VerifyTokenOutput { success: true, diff --git a/crates/tranquil-db-traits/src/user.rs b/crates/tranquil-db-traits/src/user.rs index 43071a2..4e427fb 100644 --- a/crates/tranquil-db-traits/src/user.rs +++ b/crates/tranquil-db-traits/src/user.rs @@ -429,12 +429,6 @@ pub trait UserRepository: Send + Sync { async fn count_accounts_by_email(&self, email: &str) -> Result; - async fn count_accounts_by_comms_identifier( - &self, - channel: CommsChannel, - identifier: &str, - ) -> Result; - async fn get_handles_by_email(&self, email: &str) -> Result, DbError>; async fn set_password_reset_code( diff --git a/crates/tranquil-db/src/postgres/user.rs b/crates/tranquil-db/src/postgres/user.rs index 1e01790..250faf6 100644 --- a/crates/tranquil-db/src/postgres/user.rs +++ b/crates/tranquil-db/src/postgres/user.rs @@ -1654,33 +1654,6 @@ impl UserRepository for PostgresUserRepository { .map_err(map_sqlx_error) } - async fn count_accounts_by_comms_identifier( - &self, - channel: CommsChannel, - identifier: &str, - ) -> Result { - let query = match channel { - CommsChannel::Email => { - "SELECT COUNT(*) FROM users WHERE LOWER(email) = LOWER($1) AND deactivated_at IS NULL" - } - CommsChannel::Discord => { - "SELECT COUNT(*) FROM users WHERE LOWER(discord_username) = LOWER($1) AND deactivated_at IS NULL" - } - CommsChannel::Telegram => { - "SELECT COUNT(*) FROM users WHERE LOWER(telegram_username) = LOWER($1) AND deactivated_at IS NULL" - } - CommsChannel::Signal => { - "SELECT COUNT(*) FROM users WHERE signal_username = $1 AND deactivated_at IS NULL" - } - }; - sqlx::query_scalar(query) - .bind(identifier) - .fetch_one(&self.pool) - .await - .map(|c: Option| c.unwrap_or(0)) - .map_err(map_sqlx_error) - } - async fn get_handles_by_email(&self, email: &str) -> Result, DbError> { sqlx::query_scalar!( "SELECT handle FROM users WHERE LOWER(email) = LOWER($1) AND deactivated_at IS NULL ORDER BY created_at DESC", diff --git a/crates/tranquil-oauth-server/src/endpoints/authorize.rs b/crates/tranquil-oauth-server/src/endpoints/authorize.rs index 5f7ec5a..7f7bef6 100644 --- a/crates/tranquil-oauth-server/src/endpoints/authorize.rs +++ b/crates/tranquil-oauth-server/src/endpoints/authorize.rs @@ -323,9 +323,16 @@ pub async fn authorize_get( .await && !accounts.is_empty() { + let login_hint_param = request_data + .parameters + .login_hint + .as_ref() + .map(|h| format!("&login_hint={}", url_encode(h))) + .unwrap_or_default(); return redirect_see_other(&format!( - "/app/oauth/accounts?request_uri={}", - url_encode(&request_uri) + "/app/oauth/accounts?request_uri={}{}", + url_encode(&request_uri), + login_hint_param )); } redirect_see_other(&format!( diff --git a/crates/tranquil-pds/src/auth/verification_token.rs b/crates/tranquil-pds/src/auth/verification_token.rs index ab17813..216283e 100644 --- a/crates/tranquil-pds/src/auth/verification_token.rs +++ b/crates/tranquil-pds/src/auth/verification_token.rs @@ -80,13 +80,8 @@ pub fn generate_signup_token(did: &Did, channel: CommsChannel, identifier: &str) generate_token(did, VerificationPurpose::Signup, channel, identifier) } -pub fn generate_migration_token(did: &Did, email: &str) -> String { - generate_token( - did, - VerificationPurpose::Migration, - CommsChannel::Email, - email, - ) +pub fn generate_migration_token(did: &Did, channel: CommsChannel, identifier: &str) -> String { + generate_token(did, VerificationPurpose::Migration, channel, identifier) } pub fn generate_channel_update_token(did: &Did, channel: CommsChannel, identifier: &str) -> String { @@ -196,16 +191,17 @@ pub fn verify_signup_token( pub fn verify_migration_token( token: &str, - expected_email: &str, + expected_channel: CommsChannel, + expected_identifier: &str, ) -> Result { let parsed = verify_token_signature(token)?; if parsed.purpose != VerificationPurpose::Migration { return Err(VerifyError::PurposeMismatch); } - if parsed.channel != CommsChannel::Email { + if parsed.channel != expected_channel { return Err(VerifyError::ChannelMismatch); } - let expected_hash = hash_identifier(expected_email); + let expected_hash = hash_identifier(expected_identifier); if parsed.identifier_hash != expected_hash { return Err(VerifyError::IdentifierMismatch); } @@ -345,8 +341,8 @@ mod tests { init(); let did: Did = "did:plc:test123".parse().unwrap(); let email = "test@example.com"; - let token = generate_migration_token(&did, email); - let result = verify_migration_token(&token, email); + let token = generate_migration_token(&did, CommsChannel::Email, email); + let result = verify_migration_token(&token, CommsChannel::Email, email); assert!(result.is_ok(), "Expected Ok, got {:?}", result); let parsed = result.unwrap(); assert_eq!(parsed.did, did); @@ -409,7 +405,7 @@ mod tests { let did: Did = "did:plc:test123".parse().unwrap(); let email = "test@example.com"; let signup_token = generate_signup_token(&did, CommsChannel::Email, email); - let result = verify_migration_token(&signup_token, email); + let result = verify_migration_token(&signup_token, CommsChannel::Email, email); assert!(matches!(result, Err(VerifyError::PurposeMismatch))); } diff --git a/crates/tranquil-pds/src/comms/service.rs b/crates/tranquil-pds/src/comms/service.rs index 7b39a1e..cb7640c 100644 --- a/crates/tranquil-pds/src/comms/service.rs +++ b/crates/tranquil-pds/src/comms/service.rs @@ -499,7 +499,8 @@ pub mod repo { user_repo: &dyn UserRepository, infra_repo: &dyn InfraRepository, user_id: Uuid, - email: &str, + channel: tranquil_db_traits::CommsChannel, + recipient: &str, token: &str, hostname: &str, ) -> Result { @@ -508,12 +509,12 @@ pub mod repo { .await? .ok_or(DbError::NotFound)?; let strings = get_strings(prefs.preferred_locale.as_deref().unwrap_or("en")); - let encoded_email = urlencoding::encode(email); + let encoded_recipient = urlencoding::encode(recipient); let encoded_token = urlencoding::encode(token); let verify_page = format!("https://{}/app/verify", hostname); let verify_link = format!( "https://{}/app/verify?token={}&identifier={}", - hostname, encoded_token, encoded_email + hostname, encoded_token, encoded_recipient ); let body = format_message( strings.migration_verification_body, @@ -531,9 +532,9 @@ pub mod repo { infra_repo .enqueue_comms( Some(user_id), - tranquil_db_traits::CommsChannel::Email, + channel, CommsType::MigrationVerification, - email, + recipient, Some(&subject), &body, None, diff --git a/crates/tranquil-pds/tests/legacy_2fa.rs b/crates/tranquil-pds/tests/legacy_2fa.rs index b917cf6..52eee85 100644 --- a/crates/tranquil-pds/tests/legacy_2fa.rs +++ b/crates/tranquil-pds/tests/legacy_2fa.rs @@ -127,7 +127,7 @@ async fn test_legacy_2fa_auth_factor_required() { body["message"] .as_str() .unwrap_or("") - .contains("sign in code") + .contains("sign-in code") ); } diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index fc1d6a7..db8e18d 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -15,17 +15,15 @@ import OAuthConsent from './routes/OAuthConsent.svelte' import OAuthLogin from './routes/OAuthLogin.svelte' import OAuthAccounts from './routes/OAuthAccounts.svelte' - import OAuth2FA from './routes/OAuth2FA.svelte' - import OAuthTotp from './routes/OAuthTotp.svelte' + import OAuthVerifyCode from './routes/OAuthVerifyCode.svelte' import OAuthPasskey from './routes/OAuthPasskey.svelte' import OAuthDelegation from './routes/OAuthDelegation.svelte' import OAuthError from './routes/OAuthError.svelte' import SsoRegisterComplete from './routes/SsoRegisterComplete.svelte' import Register from './routes/Register.svelte' - import RegisterPassword from './routes/RegisterPassword.svelte' + import ActAs from './routes/ActAs.svelte' import Migration from './routes/Migration.svelte' - import UiTest from './routes/UiTest.svelte' import { _ } from './lib/i18n' initI18n() @@ -105,9 +103,8 @@ case '/oauth/accounts': return OAuthAccounts case '/oauth/2fa': - return OAuth2FA case '/oauth/totp': - return OAuthTotp + return OAuthVerifyCode case '/oauth/passkey': return OAuthPasskey case '/oauth/delegation': @@ -118,17 +115,14 @@ return SsoRegisterComplete case '/register': case '/oauth/register': + case '/oauth/register-password': return Register case '/oauth/register-sso': return RegisterSso - case '/oauth/register-password': - return RegisterPassword case '/act-as': return ActAs case '/migrate': return Migration - case '/ui-test': - return UiTest default: return Login } diff --git a/frontend/src/components/CommsChannelPicker.svelte b/frontend/src/components/CommsChannelPicker.svelte index 0bc6f06..e3e7d7a 100644 --- a/frontend/src/components/CommsChannelPicker.svelte +++ b/frontend/src/components/CommsChannelPicker.svelte @@ -10,15 +10,11 @@ signalUsername: string availableChannels: VerificationChannel[] disabled?: boolean - discordInUse?: boolean - telegramInUse?: boolean - signalInUse?: boolean onChannelChange: (channel: VerificationChannel) => void onEmailChange: (value: string) => void onDiscordChange: (value: string) => void onTelegramChange: (value: string) => void onSignalChange: (value: string) => void - onCheckInUse?: (channel: 'discord' | 'telegram' | 'signal', identifier: string) => void } let { @@ -29,15 +25,11 @@ signalUsername, availableChannels, disabled = false, - discordInUse = false, - telegramInUse = false, - signalInUse = false, onChannelChange, onEmailChange, onDiscordChange, onTelegramChange, onSignalChange, - onCheckInUse, }: Props = $props() function channelLabel(ch: string): string { @@ -92,14 +84,10 @@ type="text" value={discordUsername} oninput={(e) => onDiscordChange((e.target as HTMLInputElement).value)} - onblur={() => onCheckInUse?.('discord', discordUsername)} placeholder={$_('register.discordUsernamePlaceholder')} {disabled} required /> - {#if discordInUse} -

{$_('register.discordInUseWarning')}

- {/if} {:else if channel === 'telegram'}
@@ -109,14 +97,10 @@ type="text" value={telegramUsername} oninput={(e) => onTelegramChange((e.target as HTMLInputElement).value)} - onblur={() => onCheckInUse?.('telegram', telegramUsername)} placeholder={$_('register.telegramUsernamePlaceholder')} {disabled} required /> - {#if telegramInUse} -

{$_('register.telegramInUseWarning')}

- {/if}
{:else if channel === 'signal'}
@@ -126,14 +110,10 @@ type="tel" value={signalUsername} oninput={(e) => onSignalChange((e.target as HTMLInputElement).value)} - onblur={() => onCheckInUse?.('signal', signalUsername)} placeholder={$_('register.signalUsernamePlaceholder')} {disabled} required />

{$_('register.signalUsernameHint')}

- {#if signalInUse} -

{$_('register.signalInUseWarning')}

- {/if}
{/if} diff --git a/frontend/src/components/dashboard/CommsContent.svelte b/frontend/src/components/dashboard/CommsContent.svelte index c6473da..f287364 100644 --- a/frontend/src/components/dashboard/CommsContent.svelte +++ b/frontend/src/components/dashboard/CommsContent.svelte @@ -33,9 +33,6 @@ let verifyingChannel = $state(null) let verificationCode = $state('') let historyLoading = $state(true) - let discordInUse = $state(false) - let telegramInUse = $state(false) - let signalInUse = $state(false) let messages = $state discordInUse = false, telegram: () => telegramInUse = false, signal: () => signalInUse = false } - resetMap[channel]() - return - } - try { - const result = await api.checkCommsChannelInUse(channel, trimmed) - const setMap = { discord: (v: boolean) => discordInUse = v, telegram: (v: boolean) => telegramInUse = v, signal: (v: boolean) => signalInUse = v } - setMap[channel](result.inUse) - } catch { - const resetMap = { discord: () => discordInUse = false, telegram: () => telegramInUse = false, signal: () => signalInUse = false } - resetMap[channel]() - } - } - const channels = ['email', 'discord', 'telegram', 'signal'] function getChannelName(id: string): string { @@ -261,14 +241,10 @@ id="discord" type="text" bind:value={discordUsername} - onblur={() => checkChannelInUse('discord', discordUsername)} placeholder={$_('register.discordUsernamePlaceholder')} disabled={saving} /> - {#if discordInUse} -

{$_('comms.discordInUseWarning')}

- {/if} {#if discordUsername && discordUsername === savedDiscordUsername && !discordVerified && discordBotUsername} {@const encodedHandle = session.handle.replaceAll('.', '_')}
@@ -296,14 +272,10 @@ id="telegram" type="text" bind:value={telegramUsername} - onblur={() => checkChannelInUse('telegram', telegramUsername)} placeholder={$_('register.telegramUsernamePlaceholder')} disabled={saving} />
- {#if telegramInUse} -

{$_('comms.telegramInUseWarning')}

- {/if} {#if telegramUsername && telegramUsername === savedTelegramUsername && !telegramVerified && telegramBotUsername} {@const encodedHandle = session.handle.replaceAll('.', '_')}
@@ -329,14 +301,10 @@ id="signal" type="text" bind:value={signalUsername} - onblur={() => checkChannelInUse('signal', signalUsername)} placeholder={$_('register.signalUsernamePlaceholder')} disabled={saving} />
- {#if signalInUse} -

{$_('comms.signalInUseWarning')}

- {/if} {#if signalUsername && signalUsername === savedSignalUsername && !signalVerified}
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 7479182..ca5f1cb 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -525,15 +525,6 @@ export const api = { }); }, - checkCommsChannelInUse( - channel: "email" | "discord" | "telegram" | "signal", - identifier: string, - ): Promise<{ inUse: boolean }> { - return xrpc("_account.checkCommsChannelInUse", { - method: "POST", - body: { channel, identifier }, - }); - }, async getSession(token: AccessToken): Promise { const raw = await xrpc("com.atproto.server.getSession", { token }); diff --git a/frontend/src/lib/auth.svelte.ts b/frontend/src/lib/auth.svelte.ts index 2173fa5..4cc246f 100644 --- a/frontend/src/lib/auth.svelte.ts +++ b/frontend/src/lib/auth.svelte.ts @@ -1,4 +1,4 @@ -import { api, ApiError, castSession, typedApi } from "./api.ts"; +import { api, ApiError, castSession } from "./api.ts"; import type { CreateAccountParams, CreateAccountResult, @@ -15,7 +15,6 @@ import { unsafeAsRefreshToken, } from "./types/branded.ts"; import { err, isErr, isOk, ok, type Result } from "./types/result.ts"; -import { assertNever } from "./types/exhaustive.ts"; import { checkForOAuthCallback, clearAllOAuthState, @@ -392,15 +391,15 @@ export async function login( : null; setLoading(previousSession); - const result = await typedApi.createSession(identifier, password); - if (isErr(result)) { - const error = toAuthError(result.error); + try { + const session = await api.createSession(identifier, password); + setAuthenticated(session); + return ok(session); + } catch (e) { + const error = toAuthError(e); setError(error); return err(error); } - - setAuthenticated(result.value); - return ok(result.value); } export async function loginWithOAuth(): Promise> { @@ -654,7 +653,7 @@ export function matchAuthState(handlers: { case "error": return handlers.error(current.error, current.savedAccounts); default: - return assertNever(current); + throw new Error(`Unexpected auth state: ${(current as { kind: string }).kind}`); } } diff --git a/frontend/src/lib/registration/flow.svelte.ts b/frontend/src/lib/registration/flow.svelte.ts index f65269e..4c3d903 100644 --- a/frontend/src/lib/registration/flow.svelte.ts +++ b/frontend/src/lib/registration/flow.svelte.ts @@ -38,9 +38,6 @@ export interface RegistrationFlowState { selectedDomain: string; handleAvailable: boolean | null; checkingHandle: boolean; - discordInUse: boolean; - telegramInUse: boolean; - signalInUse: boolean; } export function createRegistrationFlow( @@ -73,9 +70,6 @@ export function createRegistrationFlow( selectedDomain: "", handleAvailable: null, checkingHandle: false, - discordInUse: false, - telegramInUse: false, - signalInUse: false, }); function getPdsEndpoint(): string { @@ -152,23 +146,6 @@ export function createRegistrationFlow( } } - async function checkCommsChannelInUse( - channel: "discord" | "telegram" | "signal", - identifier: string, - ): Promise { - const trimmed = identifier.trim(); - if (!trimmed) { - state[`${channel}InUse`] = false; - return; - } - try { - const result = await api.checkCommsChannelInUse(channel, trimmed); - state[`${channel}InUse`] = result.inUse; - } catch { - state[`${channel}InUse`] = false; - } - } - function proceedFromInfo() { state.error = null; if (state.info.didType === "web-external") { @@ -498,7 +475,6 @@ export function createRegistrationFlow( finalizeSession, goBack, checkHandleAvailability, - checkCommsChannelInUse, setError(msg: string) { state.error = msg; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 3729dec..766113e 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord Username", "discordUsernamePlaceholder": "yourusername", - "discordInUseWarning": "Discord username in use by another account", "telegram": "Telegram", "telegramUsername": "Telegram Username", "telegramUsernamePlaceholder": "@yourusername", - "telegramInUseWarning": "Telegram username in use by another account", "signal": "Signal", "signalUsername": "Signal Username", "signalUsernamePlaceholder": "username.01", - "signalInUseWarning": "Signal username in use by another account", "notConfigured": "not configured", "inviteCode": "Invite Code", "inviteCodePlaceholder": "Enter your invite code", @@ -412,9 +409,6 @@ "verifiedSuccess": "{channel} verified successfully", "messageHistory": "Message History", "noMessages": "No messages found.", - "discordInUseWarning": "This Discord username is already associated with another account.", - "telegramInUseWarning": "This Telegram username is already associated with another account.", - "signalInUseWarning": "This Signal username is already associated with another account.", "telegramStartBot": "Or send /start {handle} to @{botUsername} manually", "telegramOpenLink": "Open Telegram to verify", "discordStartBot": "DM @{botUsername} on Discord and send /start {handle}", @@ -1027,12 +1021,17 @@ "continue": "Continue" }, "emailVerify": { - "title": "Verify Your Email", + "title": "Verify Your Account", "desc": "A verification code has been sent to {email}.", - "hint": "Enter the code below, or click the link in the email to continue automatically.", + "hint": "Enter the code below, or click the link in the message to continue automatically.", "tokenLabel": "Verification Code", - "tokenPlaceholder": "Enter code from email", - "resend": "Resend Code" + "tokenPlaceholder": "Enter verification code", + "resend": "Resend Code", + "telegramInstructions": "Message the Telegram bot to verify your account.", + "discordInstructions": "Message the Discord bot to verify your account.", + "openTelegram": "Open Telegram to verify", + "openDiscord": "Open Discord to verify", + "waitingForVerification": "Waiting for verification..." }, "plcToken": { "title": "Verify Migration", diff --git a/frontend/src/locales/fi.json b/frontend/src/locales/fi.json index 4c6a764..003926e 100644 --- a/frontend/src/locales/fi.json +++ b/frontend/src/locales/fi.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord-käyttäjänimi", "discordUsernamePlaceholder": "käyttäjänimesi", - "discordInUseWarning": "Tämä Discord-käyttäjänimi on jo yhdistetty toiseen tiliin.", "telegram": "Telegram", "telegramUsername": "Telegram-käyttäjänimi", "telegramUsernamePlaceholder": "@käyttäjänimesi", - "telegramInUseWarning": "Tämä Telegram-käyttäjänimi on jo yhdistetty toiseen tiliin.", "signal": "Signal", "signalUsername": "Signal-käyttäjänimi", "signalUsernamePlaceholder": "käyttäjänimi.01", - "signalInUseWarning": "Tämä Signal-käyttäjänimi on jo yhdistetty toiseen tiliin.", "notConfigured": "ei määritetty", "inviteCode": "Kutsukoodi", "inviteCodePlaceholder": "Syötä kutsukoodisi", @@ -408,9 +405,6 @@ "verifiedSuccess": "{channel} vahvistettu", "messageHistory": "Viestihistoria", "noMessages": "Viestejä ei löytynyt.", - "discordInUseWarning": "Tämä Discord-käyttäjänimi on jo yhdistetty toiseen tiliin.", - "telegramInUseWarning": "Tämä Telegram-käyttäjänimi on jo yhdistetty toiseen tiliin.", - "signalInUseWarning": "Tämä Signal-käyttäjänimi on jo yhdistetty toiseen tiliin.", "telegramStartBot": "Tai lähetä /start {handle} käyttäjälle @{botUsername} manuaalisesti", "telegramOpenLink": "Avaa Telegram vahvistaaksesi", "discordStartBot": "Lähetä @{botUsername}-botille viesti /start {handle} Discordissa", @@ -1026,12 +1020,17 @@ "continue": "Jatka" }, "emailVerify": { - "title": "Vahvista sähköpostisi", + "title": "Vahvista tilisi", "desc": "Vahvistuskoodi on lähetetty osoitteeseen {email}.", - "hint": "Syötä koodi alle tai klikkaa sähköpostissa olevaa linkkiä jatkaaksesi automaattisesti.", + "hint": "Syötä koodi alle tai klikkaa viestissä olevaa linkkiä jatkaaksesi automaattisesti.", "tokenLabel": "Vahvistuskoodi", - "tokenPlaceholder": "Syötä sähköpostista saatu koodi", - "resend": "Lähetä koodi uudelleen" + "tokenPlaceholder": "Syötä vahvistuskoodi", + "resend": "Lähetä koodi uudelleen", + "telegramInstructions": "Lähetä viesti Telegram-botille vahvistaaksesi tilisi.", + "discordInstructions": "Lähetä viesti Discord-botille vahvistaaksesi tilisi.", + "openTelegram": "Avaa Telegram vahvistaaksesi", + "openDiscord": "Avaa Discord vahvistaaksesi", + "waitingForVerification": "Odotetaan vahvistusta..." }, "plcToken": { "title": "Vahvista siirto", diff --git a/frontend/src/locales/ja.json b/frontend/src/locales/ja.json index 287b563..4f22e80 100644 --- a/frontend/src/locales/ja.json +++ b/frontend/src/locales/ja.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord ユーザー名", "discordUsernamePlaceholder": "yourusername", - "discordInUseWarning": "この Discord ユーザー名は既に別のアカウントに関連付けられています。", "telegram": "Telegram", "telegramUsername": "Telegram ユーザー名", "telegramUsernamePlaceholder": "@yourusername", - "telegramInUseWarning": "この Telegram ユーザー名は既に別のアカウントに関連付けられています。", "signal": "Signal", "signalUsername": "Signal ユーザー名", "signalUsernamePlaceholder": "username.01", - "signalInUseWarning": "この Signal ユーザー名は既に別のアカウントに使用されています。", "notConfigured": "未設定", "inviteCode": "招待コード", "inviteCodePlaceholder": "招待コードを入力", @@ -408,9 +405,6 @@ "verifiedSuccess": "{channel} を確認しました", "messageHistory": "メッセージ履歴", "noMessages": "メッセージが見つかりません。", - "discordInUseWarning": "この Discord ユーザー名は既に別のアカウントに関連付けられています。", - "telegramInUseWarning": "この Telegram ユーザー名は既に別のアカウントに関連付けられています。", - "signalInUseWarning": "この Signal ユーザー名は既に別のアカウントに使用されています。", "telegramStartBot": "または @{botUsername} に /start {handle} を手動で送信", "telegramOpenLink": "Telegram で確認する", "discordStartBot": "Discordで @{botUsername} にDMして /start {handle} を送信", @@ -1026,12 +1020,17 @@ "continue": "続ける" }, "emailVerify": { - "title": "メールアドレスを確認", + "title": "アカウントを確認", "desc": "確認コードが {email} に送信されました。", - "hint": "下記にコードを入力するか、メール内のリンクをクリックして自動的に続行できます。", + "hint": "下記にコードを入力するか、メッセージ内のリンクをクリックして自動的に続行できます。", "tokenLabel": "確認コード", - "tokenPlaceholder": "メールに記載されたコードを入力", - "resend": "コードを再送信" + "tokenPlaceholder": "確認コードを入力", + "resend": "コードを再送信", + "telegramInstructions": "Telegram ボットにメッセージを送信してアカウントを確認してください。", + "discordInstructions": "Discord ボットにメッセージを送信してアカウントを確認してください。", + "openTelegram": "Telegram で確認する", + "openDiscord": "Discord で確認する", + "waitingForVerification": "確認を待っています..." }, "plcToken": { "title": "移行を確認", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index d3b06f6..626986c 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord 사용자명", "discordUsernamePlaceholder": "yourusername", - "discordInUseWarning": "이 Discord 사용자명은 이미 다른 계정과 연결되어 있습니다.", "telegram": "Telegram", "telegramUsername": "Telegram 사용자 이름", "telegramUsernamePlaceholder": "@yourusername", - "telegramInUseWarning": "이 Telegram 사용자 이름은 이미 다른 계정과 연결되어 있습니다.", "signal": "Signal", "signalUsername": "Signal 사용자명", "signalUsernamePlaceholder": "username.01", - "signalInUseWarning": "이 Signal 사용자명은 이미 다른 계정에서 사용 중입니다.", "notConfigured": "구성되지 않음", "inviteCode": "초대 코드", "inviteCodePlaceholder": "초대 코드 입력", @@ -408,9 +405,6 @@ "verifiedSuccess": "{channel} 인증 완료", "messageHistory": "메시지 기록", "noMessages": "메시지가 없습니다.", - "discordInUseWarning": "이 Discord 사용자명은 이미 다른 계정과 연결되어 있습니다.", - "telegramInUseWarning": "이 Telegram 사용자 이름은 이미 다른 계정과 연결되어 있습니다.", - "signalInUseWarning": "이 Signal 사용자명은 이미 다른 계정에서 사용 중입니다.", "telegramStartBot": "또는 @{botUsername}에게 /start {handle}을 직접 보내세요", "telegramOpenLink": "Telegram에서 인증하기", "discordStartBot": "Discord에서 @{botUsername}에게 DM으로 /start {handle} 보내기", @@ -1026,12 +1020,17 @@ "continue": "계속" }, "emailVerify": { - "title": "이메일 인증", + "title": "계정 인증", "desc": "인증 코드가 {email}(으)로 전송되었습니다.", - "hint": "아래에 코드를 입력하거나, 이메일의 링크를 클릭하여 자동으로 계속할 수 있습니다.", + "hint": "아래에 코드를 입력하거나, 메시지의 링크를 클릭하여 자동으로 계속할 수 있습니다.", "tokenLabel": "인증 코드", - "tokenPlaceholder": "이메일에서 받은 코드 입력", - "resend": "코드 재전송" + "tokenPlaceholder": "인증 코드 입력", + "resend": "코드 재전송", + "telegramInstructions": "Telegram 봇에 메시지를 보내 계정을 인증하세요.", + "discordInstructions": "Discord 봇에 메시지를 보내 계정을 인증하세요.", + "openTelegram": "Telegram에서 인증하기", + "openDiscord": "Discord에서 인증하기", + "waitingForVerification": "인증 대기 중..." }, "plcToken": { "title": "마이그레이션 확인", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 904f0aa..425d9aa 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord-användarnamn", "discordUsernamePlaceholder": "dittanvändarnamn", - "discordInUseWarning": "Detta Discord-användarnamn är redan kopplat till ett annat konto.", "telegram": "Telegram", "telegramUsername": "Telegram-användarnamn", "telegramUsernamePlaceholder": "@dittanvändarnamn", - "telegramInUseWarning": "Detta Telegram-användarnamn är redan kopplat till ett annat konto.", "signal": "Signal", "signalUsername": "Signal-användarnamn", "signalUsernamePlaceholder": "användarnamn.01", - "signalInUseWarning": "Detta Signal-användarnamn är redan kopplat till ett annat konto.", "notConfigured": "ej konfigurerad", "inviteCode": "Inbjudningskod", "inviteCodePlaceholder": "Ange din inbjudningskod", @@ -408,9 +405,6 @@ "verifiedSuccess": "{channel} verifierad", "messageHistory": "Meddelandehistorik", "noMessages": "Inga meddelanden hittades.", - "discordInUseWarning": "Detta Discord-användarnamn är redan kopplat till ett annat konto.", - "telegramInUseWarning": "Detta Telegram-användarnamn är redan kopplat till ett annat konto.", - "signalInUseWarning": "Detta Signal-användarnamn är redan kopplat till ett annat konto.", "telegramStartBot": "Eller skicka /start {handle} till @{botUsername} manuellt", "telegramOpenLink": "Öppna Telegram för att verifiera", "discordStartBot": "DM:a @{botUsername} på Discord och skicka /start {handle}", @@ -1026,12 +1020,17 @@ "continue": "Fortsätt" }, "emailVerify": { - "title": "Verifiera din e-post", + "title": "Verifiera ditt konto", "desc": "En verifieringskod har skickats till {email}.", - "hint": "Ange koden nedan eller klicka på länken i e-postmeddelandet för att fortsätta automatiskt.", + "hint": "Ange koden nedan eller klicka på länken i meddelandet för att fortsätta automatiskt.", "tokenLabel": "Verifieringskod", - "tokenPlaceholder": "Ange kod från e-post", - "resend": "Skicka kod igen" + "tokenPlaceholder": "Ange verifieringskod", + "resend": "Skicka kod igen", + "telegramInstructions": "Skicka ett meddelande till Telegram-boten för att verifiera ditt konto.", + "discordInstructions": "Skicka ett meddelande till Discord-boten för att verifiera ditt konto.", + "openTelegram": "Öppna Telegram för att verifiera", + "openDiscord": "Öppna Discord för att verifiera", + "waitingForVerification": "Väntar på verifiering..." }, "plcToken": { "title": "Verifiera flytt", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index c5b4084..e1f407c 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -86,15 +86,12 @@ "discord": "Discord", "discordUsername": "Discord 用户名", "discordUsernamePlaceholder": "yourusername", - "discordInUseWarning": "此 Discord 用户名已与另一个账户关联。", "telegram": "Telegram", "telegramUsername": "Telegram 用户名", "telegramUsernamePlaceholder": "@yourusername", - "telegramInUseWarning": "此 Telegram 用户名已与另一个账户关联。", "signal": "Signal", "signalUsername": "Signal 用户名", "signalUsernamePlaceholder": "username.01", - "signalInUseWarning": "此 Signal 用户名已被其他账户使用。", "notConfigured": "未配置", "inviteCode": "邀请码", "inviteCodePlaceholder": "输入您的邀请码", @@ -408,9 +405,6 @@ "verifiedSuccess": "{channel} 验证成功", "messageHistory": "消息历史", "noMessages": "暂无消息记录", - "discordInUseWarning": "此 Discord 用户名已与另一个账户关联。", - "telegramInUseWarning": "此 Telegram 用户名已与另一个账户关联。", - "signalInUseWarning": "此 Signal 用户名已与另一个账户关联。", "telegramStartBot": "或手动向 @{botUsername} 发送 /start {handle}", "telegramOpenLink": "打开 Telegram 验证", "discordStartBot": "在 Discord 上私信 @{botUsername} 并发送 /start {handle}", @@ -1026,12 +1020,17 @@ "continue": "继续" }, "emailVerify": { - "title": "验证您的邮箱", + "title": "验证您的账户", "desc": "验证码已发送至 {email}。", - "hint": "在下方输入验证码,或点击邮件中的链接自动继续。", + "hint": "在下方输入验证码,或点击消息中的链接自动继续。", "tokenLabel": "验证码", - "tokenPlaceholder": "输入邮件中的验证码", - "resend": "重新发送" + "tokenPlaceholder": "输入验证码", + "resend": "重新发送", + "telegramInstructions": "向 Telegram 机器人发送消息以验证您的账户。", + "discordInstructions": "向 Discord 机器人发送消息以验证您的账户。", + "openTelegram": "打开 Telegram 验证", + "openDiscord": "打开 Discord 验证", + "waitingForVerification": "等待验证..." }, "plcToken": { "title": "验证迁移", diff --git a/frontend/src/routes/OAuthAccounts.svelte b/frontend/src/routes/OAuthAccounts.svelte index dbd46de..f419790 100644 --- a/frontend/src/routes/OAuthAccounts.svelte +++ b/frontend/src/routes/OAuthAccounts.svelte @@ -13,13 +13,12 @@ let submitting = $state(false) let accounts = $state([]) - function getRequestUri(): string | null { - const params = new URLSearchParams(window.location.search) - return params.get('request_uri') + function getParam(name: string): string | null { + return new URLSearchParams(window.location.search).get(name) } async function fetchAccounts() { - const requestUri = getRequestUri() + const requestUri = getParam('request_uri') if (!requestUri) { error = 'Missing request_uri parameter' loading = false @@ -36,6 +35,19 @@ } const data = await response.json() accounts = data.accounts || [] + + const loginHint = getParam('login_hint') + if (loginHint && accounts.length > 0) { + const hint = loginHint.toLowerCase() + const matched = accounts.find( + (a) => a.did === hint || a.handle.toLowerCase() === hint + ) + if (matched) { + loading = false + handleSelectAccount(matched.did) + return + } + } } catch { error = 'Failed to connect to server' } finally { @@ -44,7 +56,7 @@ } async function handleSelectAccount(did: string) { - const requestUri = getRequestUri() + const requestUri = getParam('request_uri') if (!requestUri) { error = 'Missing request_uri parameter' return @@ -98,7 +110,7 @@ } function handleDifferentAccount() { - const requestUri = getRequestUri() + const requestUri = getParam('request_uri') if (requestUri) { navigate(routes.oauthLogin, { params: { request_uri: requestUri } }) } else { diff --git a/frontend/src/routes/OAuthConsent.svelte b/frontend/src/routes/OAuthConsent.svelte index 201376c..94ce589 100644 --- a/frontend/src/routes/OAuthConsent.svelte +++ b/frontend/src/routes/OAuthConsent.svelte @@ -310,15 +310,15 @@
{$_('oauthConsent.delegatedAccess')}
- {$_('oauthConsent.actingAs')} - {consentData.did} + +
- {$_('oauthConsent.controller')} - @{consentData.controller_handle || consentData.controller_did} + +
- {$_('oauthConsent.accessLevel')} + {consentData.delegation_level}
@@ -340,11 +340,11 @@
{/if} {:else} - {$_('oauth.consent.signingInAs')} + {#if consentData.handle} - @{consentData.handle} + {/if} - {consentData.did} + {/if} diff --git a/frontend/src/routes/OAuthLogin.svelte b/frontend/src/routes/OAuthLogin.svelte index 64dad69..674c4e9 100644 --- a/frontend/src/routes/OAuthLogin.svelte +++ b/frontend/src/routes/OAuthLogin.svelte @@ -494,18 +494,17 @@ {$_('oauth.login.rememberDevice')} - +
+ + +
{/if} - -
- -
{:else} {#if hasPassword || !securityStatusChecked}
@@ -526,17 +525,14 @@
+
{/if} - -
- -
{/if} diff --git a/frontend/src/routes/OAuthVerifyCode.svelte b/frontend/src/routes/OAuthVerifyCode.svelte new file mode 100644 index 0000000..25c8b76 --- /dev/null +++ b/frontend/src/routes/OAuthVerifyCode.svelte @@ -0,0 +1,161 @@ + + +
+

{mode === 'totp' ? $_('oauth.totp.title') : $_('oauth.twoFactorCode.title')}

+ {#if mode === '2fa'} +

+ {$_('oauth.twoFactorCode.subtitle', { values: { channel } })} +

+ {/if} + + {#if error} +
{error}
+ {/if} + +
+
+ + {#if mode === 'totp'} + + {#if isBackupCode || isTotpCode} +

+ {isBackupCode ? $_('oauth.totp.hintBackupCode') : $_('oauth.totp.hintTotpCode')} +

+ {/if} + {:else} + + {/if} +
+ + {#if mode === 'totp'} + + {/if} + +
+ + +
+
+
diff --git a/frontend/src/routes/Register.svelte b/frontend/src/routes/Register.svelte index 6ca37bb..8030456 100644 --- a/frontend/src/routes/Register.svelte +++ b/frontend/src/routes/Register.svelte @@ -1,5 +1,5 @@