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:
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"error": {
|
||||
"invalid_credentials": "メールアドレスまたはパスワードが無効です",
|
||||
"connection_failed": "サーバーへの接続に失敗しました",
|
||||
"cors_blocked": "サーバーには到達できますが、クロスオリジンリクエストがブロックされています。JMAPサーバーのCORS設定を確認し、このドメインを許可してください。",
|
||||
"generic": "エラーが発生しました。もう一度お試しください。",
|
||||
"totp_invalid": "認証コードが無効です。認証アプリを確認してください。",
|
||||
"server_error": "サーバーが一時的に利用できません。後でもう一度お試しください。"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user