fix: explain a refused token exchange instead of asking for Basic auth on a TOTP login

When Stalwart accepted the password and code but refused to issue
tokens (in Lite: `invalid_client` for an OAuth client with a secret),
login retried `password$totp` over Basic. Stalwart 0.16+ rejects that, so
the browser showed its own Basic-auth dialog and the login page then
reported a wrong code. The login now stops with a message pointing at
the OAuth client settings. Password-only logins keep the Basic fallback.
This commit is contained in:
Linus Rath
2026-10-02 16:32:15 +02:00
parent 64b39c504a
commit 1c1bfb0ce8
29 changed files with 91 additions and 27 deletions
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "حدث خطأ غير متوقع. إذا استمرت المشكلة، تواصل مع المسؤول.",
"totp_required": "مطلوب رمز مصادقة ثنائية. يرجى إدخال الرمز أدناه.",
"totp_invalid": "رمز المصادقة غير صحيح. تحقق من تطبيق المصادقة وحاول مرة أخرى.",
"oauth_discovery_failed": "تسجيل الدخول الموحد (SSO) مفعّل، لكن تعذّر الوصول إلى مزوّد الهوية. تحقق من إعدادات OAuth."
"oauth_discovery_failed": "تسجيل الدخول الموحد (SSO) مفعّل، لكن تعذّر الوصول إلى مزوّد الهوية. تحقق من إعدادات OAuth.",
"token_exchange_failed": "تم قبول كلمة المرور والرمز، لكن خادم البريد رفض بدء جلسة لهذا التطبيق. اطلب من المسؤول التحقق من إعدادات عميل OAuth."
},
"show_password": "إظهار كلمة المرور",
"hide_password": "إخفاء كلمة المرور",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "S'ha produït un error inesperat. Si el problema persisteix, contacteu amb l'administrador.",
"totp_required": "Cal un codi d'autenticació de dos factors. Introduïu-lo a continuació.",
"totp_invalid": "El codi d'autenticació no és vàlid. Comproveu l'aplicació d'autenticació i torneu-ho a provar.",
"oauth_discovery_failed": "El SSO està activat, però no s'ha pogut contactar amb el proveïdor d'identitat. Comproveu la configuració d'OAuth."
"oauth_discovery_failed": "El SSO està activat, però no s'ha pogut contactar amb el proveïdor d'identitat. Comproveu la configuració d'OAuth.",
"token_exchange_failed": "La contrasenya i el codi s'han acceptat, però el servidor de correu s'ha negat a iniciar una sessió per a aquesta aplicació. Demaneu a l'administrador que revisi la configuració del client OAuth."
},
"show_password": "Mostra la contrasenya",
"hide_password": "Amaga la contrasenya",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Došlo k neočekávané chybě. Pokud problém přetrvává, kontaktujte správce.",
"totp_required": "Je vyžadován kód dvoufázového ověření. Zadejte kód níže.",
"totp_invalid": "Neplatný ověřovací kód. Zkontrolujte svou ověřovací aplikaci a zkuste to znovu.",
"oauth_discovery_failed": "SSO je povoleno, ale nelze se připojit k poskytovateli identity. Zkontrolujte konfiguraci OAuth."
"oauth_discovery_failed": "SSO je povoleno, ale nelze se připojit k poskytovateli identity. Zkontrolujte konfiguraci OAuth.",
"token_exchange_failed": "Heslo a kód byly přijaty, ale poštovní server odmítl pro tuto aplikaci zahájit relaci. Požádejte správce o kontrolu nastavení klienta OAuth."
},
"show_password": "Zobrazit heslo",
"hide_password": "Skrýt heslo",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Der opstod en uventet fejl. Kontakt din administrator, hvis problemet fortsætter.",
"totp_required": "En to-faktor-godkendelseskode er påkrævet. Indtast din kode nedenfor.",
"totp_invalid": "Ugyldig godkendelseskode. Kontrollér din autentificeringsapp og prøv igen.",
"oauth_discovery_failed": "SSO er aktiveret, men identitetsudbyderen kunne ikke nås. Kontrollér din OAuth-konfiguration."
"oauth_discovery_failed": "SSO er aktiveret, men identitetsudbyderen kunne ikke nås. Kontrollér din OAuth-konfiguration.",
"token_exchange_failed": "Din adgangskode og kode blev accepteret, men mailserveren nægtede at starte en session for denne app. Bed din administrator om at kontrollere OAuth-klientindstillingerne."
},
"show_password": "Vis adgangskode",
"hide_password": "Skjul adgangskode",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
"totp_required": "Ein Zwei-Faktor-Authentifizierungscode ist erforderlich. Bitte geben Sie Ihren Code unten ein.",
"totp_invalid": "Ungültiger Authentifizierungscode. Überprüfen Sie Ihre Authenticator-App.",
"oauth_discovery_failed": "SSO ist aktiviert, aber der Identitätsanbieter ist nicht erreichbar. Überprüfen Sie Ihre OAuth-Konfiguration."
"oauth_discovery_failed": "SSO ist aktiviert, aber der Identitätsanbieter ist nicht erreichbar. Überprüfen Sie Ihre OAuth-Konfiguration.",
"token_exchange_failed": "Passwort und Code wurden akzeptiert, aber der Mailserver hat für diese App keine Sitzung gestartet. Bitten Sie Ihre Administration, die OAuth-Client-Einstellungen zu prüfen."
},
"show_password": "Passwort anzeigen",
"hide_password": "Passwort verbergen",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "An unexpected error occurred. If this persists, contact your administrator.",
"totp_required": "A two-factor authentication code is required. Please enter your code below.",
"totp_invalid": "Invalid authentication code. Please check your authenticator app and try again.",
"oauth_discovery_failed": "SSO is enabled but the identity provider could not be reached. Check your OAuth configuration."
"oauth_discovery_failed": "SSO is enabled but the identity provider could not be reached. Check your OAuth configuration.",
"token_exchange_failed": "Your password and code were accepted, but the mail server refused to start a session for this app. Ask your administrator to check its OAuth client settings."
},
"show_password": "Show password",
"hide_password": "Hide password",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Ocurrió un error. Por favor, inténtelo de nuevo.",
"totp_required": "Se requiere un código de autenticación de dos factores. Introduce tu código a continuación.",
"totp_invalid": "Código de autenticación inválido. Verifica tu aplicación de autenticación.",
"oauth_discovery_failed": "SSO está habilitado pero no se pudo contactar al proveedor de identidad. Verifica tu configuración OAuth."
"oauth_discovery_failed": "SSO está habilitado pero no se pudo contactar al proveedor de identidad. Verifica tu configuración OAuth.",
"token_exchange_failed": "Tu contraseña y tu código fueron aceptados, pero el servidor de correo se negó a iniciar una sesión para esta aplicación. Pide a tu administrador que revise la configuración de su cliente OAuth."
},
"show_password": "Mostrar contraseña",
"hide_password": "Ocultar contraseña",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "خطای غیرمنتظره‌ای رخ داد. اگر ادامه داشت با مدیر سیستم تماس بگیرید.",
"totp_required": "کد احراز هویت دو مرحله‌ای لازم است.",
"totp_invalid": "کد احراز هویت نامعتبر است. برنامه authenticator خود را بررسی کنید.",
"oauth_discovery_failed": "ورود یکپارچه فعال است اما ارائه‌دهنده هویت در دسترس نیست."
"oauth_discovery_failed": "ورود یکپارچه فعال است اما ارائه‌دهنده هویت در دسترس نیست.",
"token_exchange_failed": "رمز عبور و کد پذیرفته شد، اما سرور ایمیل از شروع نشست برای این برنامه خودداری کرد. از مدیر سیستم بخواهید تنظیمات کلاینت OAuth را بررسی کند."
},
"show_password": "نمایش رمز عبور",
"hide_password": "مخفی کردن رمز عبور",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Une erreur s'est produite. Veuillez réessayer.",
"totp_required": "Un code d'authentification à deux facteurs est requis. Veuillez saisir votre code ci-dessous.",
"totp_invalid": "Code d'authentification invalide. Vérifiez votre application d'authentification.",
"oauth_discovery_failed": "Le SSO est activé mais le fournisseur d'identité est injoignable. Vérifiez votre configuration OAuth."
"oauth_discovery_failed": "Le SSO est activé mais le fournisseur d'identité est injoignable. Vérifiez votre configuration OAuth.",
"token_exchange_failed": "Votre mot de passe et votre code ont été acceptés, mais le serveur de messagerie a refusé d'ouvrir une session pour cette application. Demandez à votre administrateur de vérifier les paramètres de son client OAuth."
},
"show_password": "Afficher le mot de passe",
"hide_password": "Masquer le mot de passe",
+2 -1
View File
@@ -21,7 +21,8 @@
"generic": "אירעה שגיאה בלתי צפויה. אם זה נמשך, פנה למנהל המערכת שלך.",
"totp_required": "נדרש קוד אימות דו-גורמי. אנא הזן את הקוד שלך למטה.",
"totp_invalid": "קוד אימות לא חוקי. בדוק את אפליקציית המאמת שלך ונסה שוב.",
"oauth_discovery_failed": "SSO מופעל אך לא ניתן היה להשיג את ספק הזהות. בדוק את תצורת ה-OAuth שלך."
"oauth_discovery_failed": "SSO מופעל אך לא ניתן היה להשיג את ספק הזהות. בדוק את תצורת ה-OAuth שלך.",
"token_exchange_failed": "הסיסמה והקוד התקבלו, אך שרת הדואר סירב לפתוח הפעלה עבור האפליקציה הזו. בקשו ממנהל המערכת לבדוק את הגדרות לקוח ה-OAuth."
},
"show_password": "הצג סיסמה",
"hide_password": "הסתר את הסיסמה",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Váratlan hiba történt. Ha a probléma továbbra is fennáll, vedd fel a kapcsolatot a rendszergazdával.",
"totp_required": "Kétfaktoros hitelesítési kód szükséges. Kérjük, add meg a kódot lent.",
"totp_invalid": "Érvénytelen hitelesítési kód. Ellenőrizd a hitelesítő alkalmazásod és próbáld újra.",
"oauth_discovery_failed": "SSO engedélyezve van, de az identitásszolgáltató nem érhető el. Ellenőrizd az OAuth konfigurációt."
"oauth_discovery_failed": "SSO engedélyezve van, de az identitásszolgáltató nem érhető el. Ellenőrizd az OAuth konfigurációt.",
"token_exchange_failed": "A jelszót és a kódot elfogadtuk, de a levelezőszerver nem indított munkamenetet ehhez az alkalmazáshoz. Kérje meg a rendszergazdát, hogy ellenőrizze az OAuth-kliens beállításait."
},
"show_password": "Jelszó megjelenítése",
"hide_password": "Jelszó elrejtése",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Si è verificato un errore. Riprova.",
"totp_required": "È richiesto un codice di autenticazione a due fattori. Inserisci il codice qui sotto.",
"totp_invalid": "Codice di autenticazione non valido. Controlla la tua app di autenticazione.",
"oauth_discovery_failed": "SSO è abilitato ma il provider di identità non è raggiungibile. Controlla la configurazione OAuth."
"oauth_discovery_failed": "SSO è abilitato ma il provider di identità non è raggiungibile. Controlla la configurazione OAuth.",
"token_exchange_failed": "Password e codice sono stati accettati, ma il server di posta ha rifiutato di avviare una sessione per questa app. Chiedi all'amministratore di controllare le impostazioni del client OAuth."
},
"show_password": "Mostra password",
"hide_password": "Nascondi password",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "エラーが発生しました。もう一度お試しください。",
"totp_required": "二要素認証コードが必要です。以下にコードを入力してください。",
"totp_invalid": "認証コードが無効です。認証アプリを確認してください。",
"oauth_discovery_failed": "SSOは有効ですが、IDプロバイダーに接続できません。OAuth設定を確認してください。"
"oauth_discovery_failed": "SSOは有効ですが、IDプロバイダーに接続できません。OAuth設定を確認してください。",
"token_exchange_failed": "パスワードとコードは受け付けられましたが、メールサーバーがこのアプリのセッション開始を拒否しました。管理者に OAuth クライアントの設定を確認してもらってください。"
},
"show_password": "パスワードを表示",
"hide_password": "パスワードを隠す",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "예상치 못한 오류가 발생했어요. 계속되면 관리자에게 문의해 주세요.",
"totp_required": "2단계 인증 코드가 필요해요. 아래에 코드를 입력해 주세요.",
"totp_invalid": "인증 코드가 올바르지 않아요. 인증 앱을 확인하고 다시 시도해 주세요.",
"oauth_discovery_failed": "SSO가 활성화되어 있지만 ID 공급자에 연결할 수 없어요. OAuth 설정을 확인해 주세요."
"oauth_discovery_failed": "SSO가 활성화되어 있지만 ID 공급자에 연결할 수 없어요. OAuth 설정을 확인해 주세요.",
"token_exchange_failed": "비밀번호와 코드는 확인되었지만 메일 서버가 이 앱의 세션 시작을 거부했습니다. 관리자에게 OAuth 클라이언트 설정을 확인해 달라고 요청하세요."
},
"show_password": "비밀번호 표시",
"hide_password": "비밀번호 숨기기",
+1
View File
@@ -24,6 +24,7 @@
"generic": "Notikusi neparedzēta kļūda. Ja tā atkārtojas, sazinieties ar administratoru.",
"totp_invalid": "Nepareizs autentifikācijas kods. Pārbaudiet lietotni un mēģiniet vēlreiz.",
"oauth_discovery_failed": "SSO ir iespējots, bet autentifikācijas pakalpojums nav pieejams. Pārbaudiet OAuth iestatījumus.",
"token_exchange_failed": "Parole un kods tika pieņemti, taču pasta serveris atteicās sākt sesiju šai lietotnei. Lūdziet administratoram pārbaudīt OAuth klienta iestatījumus.",
"totp_required": "Nepieciešams divu faktoru autentifikācijas kods. Lūdzu, ievadiet kodu zemāk."
},
"show_password": "Rādīt paroli",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Санамсаргүй алдаа гарлаа. Хэрэв давтагдсаар байвал админтайгаа холбогдоно уу.",
"totp_required": "Хоёр шаттай баталгаажуулалтын код шаардлагатай. Доор кодоо оруулна уу.",
"totp_invalid": "Баталгаажуулалтын код буруу байна. Authenticator апп-аа шалгаад дахин оролдоно уу.",
"oauth_discovery_failed": "SSO идэвхтэй боловч identity provider-д хүрч чадсангүй. OAuth тохиргоогоо шалгана уу."
"oauth_discovery_failed": "SSO идэвхтэй боловч identity provider-д хүрч чадсангүй. OAuth тохиргоогоо шалгана уу.",
"token_exchange_failed": "Нууц үг болон код зөв байсан ч шуудангийн сервер энэ аппад сешн эхлүүлэхээс татгалзлаа. Админаасаа OAuth клиентийн тохиргоог шалгахыг хүснэ үү."
},
"show_password": "Нууц үг харуулах",
"hide_password": "Нууц үг нуух",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Det oppstod en uventet feil. Kontakt administratoren din hvis feilen vedvarer.",
"totp_required": "Du må skrive inn en tofaktorautentiseringskode nedenfor.",
"totp_invalid": "Ugyldig autentiseringskode. Sjekk autentiseringsappen din og prøv igjen.",
"oauth_discovery_failed": "SSO er aktivert, men identitetsleverandøren kunne ikke nås. Sjekk OAuth-konfigurasjonen."
"oauth_discovery_failed": "SSO er aktivert, men identitetsleverandøren kunne ikke nås. Sjekk OAuth-konfigurasjonen.",
"token_exchange_failed": "Passordet og koden ble godtatt, men e-postserveren nektet å starte en økt for denne appen. Be administratoren din om å sjekke OAuth-klientinnstillingene."
},
"show_password": "Vis passord",
"hide_password": "Skjul passord",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Er is een fout opgetreden. Probeer het opnieuw.",
"totp_required": "Een tweefactorauthenticatiecode is vereist. Voer uw code hieronder in.",
"totp_invalid": "Ongeldige authenticatiecode. Controleer uw authenticator-app.",
"oauth_discovery_failed": "SSO is ingeschakeld maar de identiteitsprovider is niet bereikbaar. Controleer uw OAuth-configuratie."
"oauth_discovery_failed": "SSO is ingeschakeld maar de identiteitsprovider is niet bereikbaar. Controleer uw OAuth-configuratie.",
"token_exchange_failed": "Uw wachtwoord en code zijn geaccepteerd, maar de mailserver weigerde een sessie voor deze app te starten. Vraag uw beheerder de OAuth-clientinstellingen te controleren."
},
"show_password": "Wachtwoord tonen",
"hide_password": "Wachtwoord verbergen",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Wystąpił nieoczekiwany błąd. Jeśli problem się powtarza, skontaktuj się z administratorem.",
"totp_required": "Wymagany jest kod uwierzytelniania dwuskładnikowego. Wprowadź kod poniżej.",
"totp_invalid": "Nieprawidłowy kod uwierzytelniania. Sprawdź aplikację uwierzytelniającą i spróbuj ponownie.",
"oauth_discovery_failed": "SSO jest włączone, ale nie można połączyć się z dostawcą tożsamości. Sprawdź konfigurację OAuth."
"oauth_discovery_failed": "SSO jest włączone, ale nie można połączyć się z dostawcą tożsamości. Sprawdź konfigurację OAuth.",
"token_exchange_failed": "Hasło i kod zostały zaakceptowane, ale serwer poczty odmówił rozpoczęcia sesji dla tej aplikacji. Poproś administratora o sprawdzenie ustawień klienta OAuth."
},
"show_password": "Pokaż hasło",
"hide_password": "Ukryj hasło",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Ocorreu um erro. Por favor, tente novamente.",
"totp_required": "É necessário um código de autenticação de dois fatores. Insira seu código abaixo.",
"totp_invalid": "Código de autenticação inválido. Verifique seu aplicativo de autenticação.",
"oauth_discovery_failed": "SSO está ativado mas o provedor de identidade não pôde ser contatado. Verifique sua configuração OAuth."
"oauth_discovery_failed": "SSO está ativado mas o provedor de identidade não pôde ser contatado. Verifique sua configuração OAuth.",
"token_exchange_failed": "Sua senha e o código foram aceitos, mas o servidor de e-mail se recusou a iniciar uma sessão para este aplicativo. Peça ao administrador que verifique as configurações do cliente OAuth."
},
"show_password": "Mostrar senha",
"hide_password": "Ocultar senha",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "A apărut o eroare neașteptată. Dacă problema persistă, contactați administratorul.",
"totp_required": "Este necesar un cod de autentificare în doi pași. Vă rugăm să introduceți codul mai jos.",
"totp_invalid": "Cod de autentificare invalid. Vă rugăm să verificați aplicația de autentificare și să încercați din nou.",
"oauth_discovery_failed": "SSO este activată, dar furnizorul de identitate nu a putut fi contactat. Verificați configurația OAuth."
"oauth_discovery_failed": "SSO este activată, dar furnizorul de identitate nu a putut fi contactat. Verificați configurația OAuth.",
"token_exchange_failed": "Parola și codul au fost acceptate, dar serverul de e-mail a refuzat să pornească o sesiune pentru această aplicație. Cereți administratorului să verifice setările clientului OAuth."
},
"show_password": "Afișează parola",
"hide_password": "Ascunde parola",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Произошла непредвиденная ошибка. Если она повторяется, обратитесь к администратору.",
"totp_required": "Требуется код двухфакторной аутентификации. Пожалуйста, введите его ниже.",
"totp_invalid": "Неверный код аутентификации. Проверьте приложение аутентификатора и повторите попытку.",
"oauth_discovery_failed": "SSO включён, но поставщик удостоверений недоступен. Проверьте настройки OAuth."
"oauth_discovery_failed": "SSO включён, но поставщик удостоверений недоступен. Проверьте настройки OAuth.",
"token_exchange_failed": "Пароль и код приняты, но почтовый сервер отказался открыть сеанс для этого приложения. Попросите администратора проверить настройки OAuth-клиента."
},
"show_password": "Показать пароль",
"hide_password": "Скрыть пароль",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Vyskytla sa neočakávaná chyba. Ak problém pretrváva, kontaktujte správcu.",
"totp_required": "Je vyžadovaný kód dvojfaktorového overenia. Zadajte kód nižšie.",
"totp_invalid": "Neplatný overovací kód. Skontrolujte svoju overovaciu aplikáciu a skúste znova.",
"oauth_discovery_failed": "SSO je povolené, ale poskytovateľa identity sa nepodarilo dosiahnuť. Skontrolujte konfiguráciu OAuth."
"oauth_discovery_failed": "SSO je povolené, ale poskytovateľa identity sa nepodarilo dosiahnuť. Skontrolujte konfiguráciu OAuth.",
"token_exchange_failed": "Heslo a kód boli prijaté, ale poštový server odmietol pre túto aplikáciu spustiť reláciu. Požiadajte správcu o kontrolu nastavení klienta OAuth."
},
"show_password": "Zobraziť heslo",
"hide_password": "Skryť heslo",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Beklenmedik bir hata oluştu. Bu sorun devam ederse yöneticinizle iletişime geçin.",
"totp_required": "İki faktörlü doğrulama kodu gereklidir. Lütfen kodunuzu aşağıya girin.",
"totp_invalid": "Geçersiz doğrulama kodu. Lütfen doğrulama uygulamanızı kontrol edip tekrar deneyin.",
"oauth_discovery_failed": "SSO etkin ancak kimlik sağlayıcısına ulaşılamıyor. OAuth yapılandırmanızı kontrol edin."
"oauth_discovery_failed": "SSO etkin ancak kimlik sağlayıcısına ulaşılamıyor. OAuth yapılandırmanızı kontrol edin.",
"token_exchange_failed": "Parolanız ve kodunuz kabul edildi ancak posta sunucusu bu uygulama için oturum başlatmayı reddetti. Yöneticinizden OAuth istemci ayarlarını kontrol etmesini isteyin."
},
"show_password": "Şifreyi göster",
"hide_password": "Şifreyi gizle",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "Сталася неочікувана помилка. Якщо це не зникне, зверніться до адміністратора.",
"totp_required": "Потрібен код двофакторної автентифікації. Будь ласка, введіть свій код нижче.",
"totp_invalid": "Недійсний код автентифікації. Перевірте свою програму автентифікації та повторіть спробу.",
"oauth_discovery_failed": "Система єдиного входу ввімкнена, але зв’язатися з постачальником ідентифікаційної інформації не вдалося. Перевірте конфігурацію OAuth."
"oauth_discovery_failed": "Система єдиного входу ввімкнена, але зв’язатися з постачальником ідентифікаційної інформації не вдалося. Перевірте конфігурацію OAuth.",
"token_exchange_failed": "Пароль і код прийнято, але поштовий сервер відмовився відкрити сеанс для цього застосунку. Попросіть адміністратора перевірити налаштування OAuth-клієнта."
},
"show_password": "Показати пароль",
"hide_password": "Приховати пароль",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "發生意外錯誤。如果問題持續存在,請聯絡管理員。",
"totp_required": "需要雙因素驗證碼。請在下方輸入驗證碼。",
"totp_invalid": "驗證碼無效。請檢查驗證器應用程式後再試一次。",
"oauth_discovery_failed": "SSO 已啟用,但無法連線至身分提供者。請檢查 OAuth 設定。"
"oauth_discovery_failed": "SSO 已啟用,但無法連線至身分提供者。請檢查 OAuth 設定。",
"token_exchange_failed": "密碼與驗證碼已通過,但郵件伺服器拒絕為此應用程式建立工作階段。請聯絡管理員檢查其 OAuth 用戶端設定。"
},
"show_password": "顯示密碼",
"hide_password": "隱藏密碼",
+2 -1
View File
@@ -24,7 +24,8 @@
"generic": "发生意外错误。如果问题持续存在,请联系管理员。",
"totp_required": "需要双重验证代码。请在下方输入验证码。",
"totp_invalid": "验证码无效。请检查您的身份验证器应用,然后重试。",
"oauth_discovery_failed": "SSO 已启用,但无法访问身份提供者。检查您的 OAuth 配置。"
"oauth_discovery_failed": "SSO 已启用,但无法访问身份提供者。检查您的 OAuth 配置。",
"token_exchange_failed": "密码和验证码已通过,但邮件服务器拒绝为此应用建立会话。请联系管理员检查其 OAuth 客户端设置。"
},
"show_password": "显示密码",
"hide_password": "隐藏密码",
+27
View File
@@ -46,6 +46,8 @@ function stalwartFetch(options: {
loginAnswer?: unknown;
refreshAnswer?: () => Response;
loginStatus?: number;
/** Stalwart's answer to the authorization_code grant (default: tokens). */
tokenAnswer?: () => Response;
/** Advertise an RFC 7009 revocation endpoint in the metadata document. */
revocation?: boolean;
} = {}) {
@@ -78,6 +80,7 @@ function stalwartFetch(options: {
if (params.get('grant_type') === 'refresh_token') {
return options.refreshAnswer ? options.refreshAnswer() : jsonResponse({ access_token: 'AT-refreshed', expires_in: 3600, refresh_token: 'RT-2' });
}
if (options.tokenAnswer) return options.tokenAnswer();
return jsonResponse({ access_token: 'AT-1', expires_in: 3600, refresh_token: 'RT-1' });
}
throw new Error(`unexpected fetch ${url}`);
@@ -259,6 +262,30 @@ describe('auth-store in the static Lite build', () => {
expect(connectSpy).not.toHaveBeenCalled();
});
// Stalwart answers `invalid_client` at /auth/token when the OAuth client has
// a secret, which Lite cannot send. Retrying `password$totp` over Basic
// only popped the browser's Basic-auth dialog and then blamed the code.
it('reports a refused token exchange after a TOTP login instead of retrying over Basic', async () => {
const { calls } = stalwartFetch({ tokenAnswer: () => jsonResponse({ error: 'invalid_client' }, 400) });
const ok = await useAuthStore.getState().login(SERVER, 'alice', 'pw', '123456', true);
expect(ok).toBe(false);
expect(useAuthStore.getState().error).toBe('token_exchange_failed');
expect(calls).toEqual([`POST ${SERVER}/api/auth`, `POST ${SERVER}/auth/token`]);
expect(connectSpy).not.toHaveBeenCalled();
expect(liteStorageKeys()).toEqual([]);
});
it('still falls back to Basic for a password-only login when the token exchange is refused', async () => {
stalwartFetch({ tokenAnswer: () => jsonResponse({ error: 'invalid_client' }, 400) });
const ok = await useAuthStore.getState().login(SERVER, 'alice', 'pw', undefined, true);
expect(ok).toBe(true);
expect(useAuthStore.getState().client?.getAuthHeader()).toBe(`Basic ${btoa('alice:pw')}`);
});
it('keeps no password in web storage when token login exists but failed', async () => {
stalwartFetch({ loginStatus: 500 });
+11 -1
View File
@@ -138,6 +138,7 @@ export interface LogoutOptions {
const ERROR_PATTERNS: Array<{ key: string; matches: string[] }> = [
{ key: 'cors_blocked', matches: ['CORS_ERROR'] },
{ key: 'totp_required', matches: ['TOTP_REQUIRED'] },
{ key: 'token_exchange_failed', matches: ['TOKEN_EXCHANGE_FAILED'] },
{ key: 'invalid_credentials', matches: ['Invalid username or password', '401', 'Unauthorized'] },
{ key: 'connection_failed', matches: ['network', 'Failed to fetch', 'NetworkError', 'ECONNREFUSED', 'Load failed', 'cancelled'] },
{ key: 'server_error', matches: ['500', '502', '503', '504', 'Internal Server Error', 'Service Unavailable'] },
@@ -1321,10 +1322,19 @@ export const useAuthStore = create<AuthState>()(
if (errorBody?.error === 'totp_required') {
throw new Error('TOTP_REQUIRED');
}
// The server took the password and code but would not issue
// tokens (in Lite: an OAuth client with a secret, which a
// browser cannot send). A server with the structured login
// endpoint is 0.16+ and refuses `password$totp` over Basic,
// so the legacy fallback would only pop the browser's
// Basic-auth dialog and then report a wrong code.
if (totp && errorBody?.error === 'token_exchange_failed') {
throw new Error('TOKEN_EXCHANGE_FAILED');
}
debug.warn('auth', 'TOTP login exchange failed, trying legacy basic auth:', tokenRes.status, errorBody);
}
} catch (err) {
if (err instanceof Error && err.message === 'TOTP_REQUIRED') throw err;
if (err instanceof Error && (err.message === 'TOTP_REQUIRED' || err.message === 'TOKEN_EXCHANGE_FAILED')) throw err;
// Unreachable (a server without /api/auth rarely answers CORS).
tokenLoginUnavailable = true;
debug.warn('auth', 'TOTP login exchange error, trying legacy basic auth:', err);