feat: detect CORS errors and show actionable guidance on login

Distinguish CORS-blocked requests from genuine network failures using
a no-cors probe, so users deploying via Docker see a specific message
pointing to their JMAP server's CORS settings instead of a misleading
"unable to reach the server" error.
This commit is contained in:
Matthieu MALVACHE
2026-02-23 18:21:23 +01:00
committed by Matthieu MALVACHE
parent a0bb200da4
commit 913f673f73
12 changed files with 23 additions and 3 deletions
+1
View File
@@ -117,6 +117,7 @@ This webmail client is designed to work seamlessly with [**Stalwart Mail Server*
- SPF/DKIM/DMARC status indicators
- No password storage (session-based auth)
- TOTP two-factor authentication support
- CORS misconfiguration detection with actionable error messages
- Shared folder support with proper permissions
- Newsletter unsubscribe support (RFC 2369)
- CSP headers and security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy)
+1 -1
View File
@@ -22,6 +22,7 @@ This document tracks the development status and planned features for JMAP Webmai
### JMAP Server Connection
- [x] Session establishment and keep-alive
- [x] Connection error handling and retries
- [x] CORS error detection with actionable user guidance
- [x] Storage quota display
- [x] Server capability detection
- [x] Shared folders support (multi-account access)
@@ -250,7 +251,6 @@ This document tracks the development status and planned features for JMAP Webmai
### Security Enhancements
- [ ] Rate limiting
- [ ] CORS configuration
## Known Issues
+10 -1
View File
@@ -133,7 +133,16 @@ export class JMAPClient {
// Start keep-alive mechanism
this.startKeepAlive();
} catch (error) {
console.error('Connection failed:', error);
if (error instanceof TypeError && (error.message === 'Failed to fetch' || error.message.includes('NetworkError'))) {
let serverReachable = false;
try {
await fetch(sessionUrl, { mode: 'no-cors' });
serverReachable = true;
} catch { /* genuinely unreachable */ }
if (serverReachable) {
throw new Error('CORS_ERROR');
}
}
throw error;
}
}
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Ungültige E-Mail-Adresse oder Passwort",
"connection_failed": "Verbindung zum Server fehlgeschlagen",
"cors_blocked": "Der Server ist erreichbar, blockiert aber Cross-Origin-Anfragen. Überprüfen Sie die CORS-Einstellungen Ihres JMAP-Servers und erlauben Sie diese Domain.",
"generic": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
"totp_invalid": "Ungültiger Authentifizierungscode. Überprüfen Sie Ihre Authenticator-App.",
"server_error": "Der Server ist vorübergehend nicht erreichbar. Bitte versuchen Sie es später erneut."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Invalid email or password. Please check your credentials and try again.",
"connection_failed": "Unable to reach the server. Check your internet connection and try again.",
"cors_blocked": "The server is reachable but is blocking cross-origin requests. Check your JMAP server's CORS settings and allow this domain.",
"server_error": "The server is temporarily unavailable. Please try again later.",
"generic": "An unexpected error occurred. If this persists, contact your administrator.",
"totp_invalid": "Invalid authentication code. Please check your authenticator app and try again."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Correo electrónico o contraseña inválidos",
"connection_failed": "No se pudo conectar con el servidor",
"cors_blocked": "El servidor es accesible pero está bloqueando las solicitudes de origen cruzado. Verifique la configuración CORS de su servidor JMAP y permita este dominio.",
"generic": "Ocurrió un error. Por favor, inténtelo de nuevo.",
"totp_invalid": "Código de autenticación inválido. Verifica tu aplicación de autenticación.",
"server_error": "El servidor no está disponible temporalmente. Inténtalo más tarde."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Email ou mot de passe invalide",
"connection_failed": "Échec de la connexion au serveur",
"cors_blocked": "Le serveur est joignable mais bloque les requêtes cross-origin. Vérifiez la configuration CORS de votre serveur JMAP et autorisez ce domaine.",
"generic": "Une erreur s'est produite. Veuillez réessayer.",
"totp_invalid": "Code d'authentification invalide. Vérifiez votre application d'authentification.",
"server_error": "Le serveur est temporairement indisponible. Veuillez réessayer plus tard."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Email o password non valida",
"connection_failed": "Impossibile connettersi al server",
"cors_blocked": "Il server è raggiungibile ma sta bloccando le richieste cross-origin. Controlla le impostazioni CORS del tuo server JMAP e consenti questo dominio.",
"generic": "Si è verificato un errore. Riprova.",
"totp_invalid": "Codice di autenticazione non valido. Controlla la tua app di autenticazione.",
"server_error": "Il server non è temporaneamente disponibile. Riprova più tardi."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "メールアドレスまたはパスワードが無効です",
"connection_failed": "サーバーへの接続に失敗しました",
"cors_blocked": "サーバーには到達できますが、クロスオリジンリクエストがブロックされています。JMAPサーバーのCORS設定を確認し、このドメインを許可してください。",
"generic": "エラーが発生しました。もう一度お試しください。",
"totp_invalid": "認証コードが無効です。認証アプリを確認してください。",
"server_error": "サーバーが一時的に利用できません。後でもう一度お試しください。"
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "Ongeldig e-mailadres of wachtwoord",
"connection_failed": "Kan geen verbinding maken met de server",
"cors_blocked": "De server is bereikbaar maar blokkeert cross-origin verzoeken. Controleer de CORS-instellingen van uw JMAP-server en sta dit domein toe.",
"generic": "Er is een fout opgetreden. Probeer het opnieuw.",
"totp_invalid": "Ongeldige authenticatiecode. Controleer uw authenticator-app.",
"server_error": "De server is tijdelijk niet beschikbaar. Probeer het later opnieuw."
+1
View File
@@ -11,6 +11,7 @@
"error": {
"invalid_credentials": "E-mail ou senha inválidos",
"connection_failed": "Falha ao conectar com o servidor",
"cors_blocked": "O servidor está acessível mas está bloqueando requisições de origem cruzada. Verifique as configurações de CORS do seu servidor JMAP e permita este domínio.",
"generic": "Ocorreu um erro. Por favor, tente novamente.",
"totp_invalid": "Código de autenticação inválido. Verifique seu aplicativo de autenticação.",
"server_error": "O servidor está temporariamente indisponível. Tente novamente mais tarde."
+3 -1
View File
@@ -98,7 +98,9 @@ export const useAuthStore = create<AuthState>()(
let errorKey = 'generic';
if (error instanceof Error) {
if (error.message.includes('Invalid username or password') ||
if (error.message === 'CORS_ERROR') {
errorKey = 'cors_blocked';
} else if (error.message.includes('Invalid username or password') ||
error.message.includes('401') ||
error.message.includes('Unauthorized')) {
errorKey = 'invalid_credentials';