fix(oauth): fail with a clear message when SSO is loaded over plain HTTP

OAuth2/OIDC with PKCE needs crypto.subtle.digest(), which browsers
only expose in secure contexts (HTTPS or localhost). Loading the
webmail over http://host without a TLS proxy in front surfaced as
"TypeError: can't access property 'digest', crypto.subtle is
undefined" when the user clicked "Sign in with SSO" — unhelpful.

Guard handleOAuthLogin with window.isSecureContext + a crypto.subtle
check and surface a translated message ("SSO requires a secure
connection…") in an amber warning banner instead. Key added to all 10
locales.

Closes #23.
This commit is contained in:
Matthieu MALVACHE
2026-04-16 23:12:38 +02:00
parent 24ad26e388
commit f3bf6efedc
11 changed files with 39 additions and 10 deletions
+19
View File
@@ -40,6 +40,7 @@ export default function LoginPage() {
const [oauthDiscoveryDone, setOauthDiscoveryDone] = useState(false);
const [oauthLoading, setOauthLoading] = useState(false);
const [oauthRetryCount, setOauthRetryCount] = useState(0);
const [oauthLocalError, setOauthLocalError] = useState<string | null>(null);
const suggestionsRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
@@ -256,6 +257,17 @@ export default function LoginPage() {
const handleOAuthLogin = async () => {
if (!oauthMetadata || !oauthClientId) return;
// PKCE needs Web Crypto (SubtleCrypto.digest). That API is only exposed
// in secure contexts — i.e. HTTPS or localhost. Fail loudly before we
// try to call it so the user sees a fix-this message instead of a
// mystery "crypto.subtle is undefined" TypeError.
if (typeof window === "undefined" || !window.isSecureContext || typeof crypto?.subtle?.digest !== "function") {
setOauthLocalError(t("oauth_error.requires_https"));
return;
}
setOauthLocalError(null);
setOauthLoading(true);
const verifier = generateCodeVerifier();
@@ -344,6 +356,13 @@ export default function LoginPage() {
</div>
)}
{oauthLocalError && (
<div className="mb-6 p-4 bg-amber-500/10 border border-amber-500/20 rounded-lg flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-amber-700 dark:text-amber-300">{oauthLocalError}</p>
</div>
)}
{/* Login Form */}
<form
onSubmit={handleSubmit}
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Fehlende Autorisierungsdaten. Bitte erneut anmelden.",
"token_exchange_failed": "Authentifizierung konnte nicht abgeschlossen werden. Bitte erneut versuchen.",
"access_denied": "Zugriff verweigert. Bitte kontaktieren Sie Ihren Administrator.",
"back_to_login": "Zurück zur Anmeldung"
"back_to_login": "Zurück zur Anmeldung",
"requires_https": "Single Sign-On erfordert eine sichere Verbindung. Bitte greifen Sie über HTTPS auf dieses Webmail zu (oder localhost in der Entwicklung)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Missing authorization data. Please try signing in again.",
"token_exchange_failed": "Failed to complete authentication. Please try again.",
"access_denied": "Access was denied. Please contact your administrator.",
"back_to_login": "Back to login"
"back_to_login": "Back to login",
"requires_https": "Single Sign-On requires a secure connection. Please access this webmail over HTTPS (or localhost during development)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Faltan datos de autorización. Intenta iniciar sesión de nuevo.",
"token_exchange_failed": "No se pudo completar la autenticación. Inténtalo de nuevo.",
"access_denied": "Acceso denegado. Contacta a tu administrador.",
"back_to_login": "Volver al inicio de sesión"
"back_to_login": "Volver al inicio de sesión",
"requires_https": "El inicio de sesión único requiere una conexión segura. Accede a este correo web por HTTPS (o localhost en desarrollo)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Données d'autorisation manquantes. Veuillez réessayer.",
"token_exchange_failed": "Impossible de finaliser l'authentification. Veuillez réessayer.",
"access_denied": "L'accès a été refusé. Veuillez contacter votre administrateur.",
"back_to_login": "Retour à la connexion"
"back_to_login": "Retour à la connexion",
"requires_https": "L'authentification SSO nécessite une connexion sécurisée. Accédez à ce webmail via HTTPS (ou localhost en développement)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Dati di autorizzazione mancanti. Prova ad accedere di nuovo.",
"token_exchange_failed": "Impossibile completare l'autenticazione. Riprova.",
"access_denied": "Accesso negato. Contatta il tuo amministratore.",
"back_to_login": "Torna al login"
"back_to_login": "Torna al login",
"requires_https": "Il Single Sign-On richiede una connessione sicura. Accedi a questa webmail tramite HTTPS (o localhost in sviluppo)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "認証データが不足しています。再度サインインしてください。",
"token_exchange_failed": "認証を完了できませんでした。再度お試しください。",
"access_denied": "アクセスが拒否されました。管理者にお問い合わせください。",
"back_to_login": "ログインに戻る"
"back_to_login": "ログインに戻る",
"requires_https": "シングルサインオンには安全な接続が必要です。HTTPS(または開発時は localhost)でこのウェブメールにアクセスしてください。"
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Ontbrekende autorisatiegegevens. Probeer opnieuw in te loggen.",
"token_exchange_failed": "Authenticatie kon niet worden voltooid. Probeer het opnieuw.",
"access_denied": "Toegang geweigerd. Neem contact op met uw beheerder.",
"back_to_login": "Terug naar inloggen"
"back_to_login": "Terug naar inloggen",
"requires_https": "Single Sign-On vereist een beveiligde verbinding. Open deze webmail via HTTPS (of localhost tijdens de ontwikkeling)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Dados de autorização ausentes. Tente entrar novamente.",
"token_exchange_failed": "Não foi possível concluir a autenticação. Tente novamente.",
"access_denied": "Acesso negado. Entre em contato com seu administrador.",
"back_to_login": "Voltar ao login"
"back_to_login": "Voltar ao login",
"requires_https": "O Single Sign-On requer uma ligação segura. Aceda a este webmail através de HTTPS (ou localhost em desenvolvimento)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Отсутствуют данные авторизации. Пожалуйста, попробуйте войти еще раз.",
"token_exchange_failed": "Не удалось завершить аутентификацию. Пожалуйста, попробуйте еще раз.",
"access_denied": "Доступ был запрещен. Пожалуйста, свяжитесь с вашим администратором.",
"back_to_login": "Вернуться к входу"
"back_to_login": "Вернуться к входу",
"requires_https": "Единый вход требует защищенного соединения. Откройте этот веб-клиент почты по HTTPS (или localhost при разработке)."
}
},
"sidebar": {
+2 -1
View File
@@ -40,7 +40,8 @@
"missing_params": "Відсутні дані авторизації. Спробуйте ввійти знову.",
"token_exchange_failed": "Не вдалося завершити автентифікацію. Спробуйте ще раз.",
"access_denied": "У доступі відмовлено. Будь ласка, зверніться до свого адміністратора.",
"back_to_login": "Назад до входу"
"back_to_login": "Назад до входу",
"requires_https": "Єдиний вхід потребує захищеного з'єднання. Відкрийте цей вебклієнт пошти через HTTPS (або localhost під час розробки)."
}
},
"sidebar": {