/// Custom authentication exceptions for the Sage application. /// /// Provides specific, user-friendly error types that map from Supabase errors /// to meaningful exceptions that can be handled consistently throughout the app. /// Follows clean architecture principles for domain error handling. /// Base authentication exception /// /// All custom auth exceptions extend this base class to provide /// a common interface for error handling throughout the application. abstract class AuthException implements Exception { /// User-friendly error message final String message; /// Optional error code for programmatic handling final String? code; /// Optional underlying error for debugging final dynamic originalError; /// Creates a new AuthException const AuthException({ required this.message, this.code, this.originalError, }); @override String toString() => 'AuthException: $message'; } /// Exception thrown when user provides invalid credentials class InvalidCredentialsException extends AuthException { const InvalidCredentialsException({ String message = 'Invalid email or password', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'INVALID_CREDENTIALS', originalError: originalError, ); } /// Exception thrown when user is not found in the system class UserNotFoundException extends AuthException { const UserNotFoundException({ String message = 'User not found', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'USER_NOT_FOUND', originalError: originalError, ); } /// Exception thrown when password doesn't meet security requirements class WeakPasswordException extends AuthException { const WeakPasswordException({ String message = 'Password does not meet security requirements', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'WEAK_PASSWORD', originalError: originalError, ); } /// Exception thrown when trying to register with an already used email class EmailAlreadyInUseException extends AuthException { const EmailAlreadyInUseException({ String message = 'Email address is already in use', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'EMAIL_ALREADY_IN_USE', originalError: originalError, ); } /// Exception thrown when network operations fail class NetworkException extends AuthException { const NetworkException({ String message = 'Network connection failed', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'NETWORK_ERROR', originalError: originalError, ); } /// Exception thrown when user session has expired class SessionExpiredException extends AuthException { const SessionExpiredException({ String message = 'Your session has expired. Please sign in again.', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'SESSION_EXPIRED', originalError: originalError, ); } /// Exception thrown when email verification is required class EmailNotVerifiedException extends AuthException { const EmailNotVerifiedException({ String message = 'Please verify your email address before continuing', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'EMAIL_NOT_VERIFIED', originalError: originalError, ); } /// Exception thrown when too many login attempts are made class TooManyRequestsException extends AuthException { const TooManyRequestsException({ String message = 'Too many requests. Please try again later.', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'TOO_MANY_REQUESTS', originalError: originalError, ); } /// Exception thrown when authentication is disabled by administrator class AuthDisabledException extends AuthException { const AuthDisabledException({ String message = 'Authentication is currently disabled', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'AUTH_DISABLED', originalError: originalError, ); } /// Exception thrown for unknown authentication errors class UnknownAuthException extends AuthException { const UnknownAuthException({ String message = 'An unknown authentication error occurred', String? code, dynamic originalError, }) : super( message: message, code: code ?? 'UNKNOWN_ERROR', originalError: originalError, ); } /// Utility class for converting Supabase errors to custom exceptions /// /// Provides a centralized way to map Supabase error responses /// to our custom exception types with appropriate error messages. class AuthExceptionFactory { /// Converts a Supabase error to a custom AuthException /// /// [error] - The error object returned by Supabase /// Returns the appropriate custom AuthException static AuthException fromSupabaseError(dynamic error) { if (error == null) { return const NetworkException(message: 'Unknown error occurred'); } // Extract error information from Supabase error String errorMessage = ''; String? errorCode; if (error is Map) { errorMessage = error['message']?.toString() ?? error['error']?.toString() ?? ''; errorCode = error['code']?.toString(); } else { errorMessage = error.toString(); } // Normalize error message to lowercase for comparison final lowerMessage = errorMessage.toLowerCase(); // Map common Supabase errors to custom exceptions if (lowerMessage.contains('invalid login credentials') || lowerMessage.contains('wrong password') || lowerMessage.contains('invalid email')) { return InvalidCredentialsException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Invalid email or password', code: errorCode, originalError: error, ); } if (lowerMessage.contains('user not found') || lowerMessage.contains('no user found') || lowerMessage.contains('user does not exist')) { return UserNotFoundException( message: _extractUserFriendlyMessage(errorMessage) ?? 'User not found', code: errorCode, originalError: error, ); } if (lowerMessage.contains('weak password') || lowerMessage.contains('password too short') || lowerMessage.contains('password should be')) { return WeakPasswordException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Password does not meet security requirements', code: errorCode, originalError: error, ); } if (lowerMessage.contains('already registered') || lowerMessage.contains('email already in use') || lowerMessage.contains('duplicate')) { return EmailAlreadyInUseException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Email address is already in use', code: errorCode, originalError: error, ); } if (lowerMessage.contains('email not confirmed') || lowerMessage.contains('email not verified') || lowerMessage.contains('please verify your email')) { return EmailNotVerifiedException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Please verify your email address before continuing', code: errorCode, originalError: error, ); } if (lowerMessage.contains('too many requests') || lowerMessage.contains('rate limit') || lowerMessage.contains('try again later')) { return TooManyRequestsException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Too many requests. Please try again later.', code: errorCode, originalError: error, ); } if (lowerMessage.contains('network') || lowerMessage.contains('connection') || lowerMessage.contains('timeout') || lowerMessage.contains('offline')) { return NetworkException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Network connection failed', code: errorCode, originalError: error, ); } if (lowerMessage.contains('session') && lowerMessage.contains('expired')) { return SessionExpiredException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Your session has expired. Please sign in again.', code: errorCode, originalError: error, ); } if (lowerMessage.contains('auth') && lowerMessage.contains('disabled')) { return AuthDisabledException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Authentication is currently disabled', code: errorCode, originalError: error, ); } // Fallback for unknown errors return UnknownAuthException( message: _extractUserFriendlyMessage(errorMessage) ?? 'Authentication failed', code: errorCode, originalError: error, ); } /// Extracts a user-friendly message from Supabase error /// /// [errorMessage] - The raw error message from Supabase /// Returns a cleaned, user-friendly message or null if not extractable static String? _extractUserFriendlyMessage(String errorMessage) { if (errorMessage.isEmpty) return null; // Remove technical prefixes and clean up the message String cleaned = errorMessage; // Remove common prefixes cleaned = cleaned.replaceFirst(RegExp(r'^Exception: '), ''); cleaned = cleaned.replaceFirst(RegExp(r'^Error: '), ''); cleaned = cleaned.replaceFirst(RegExp(r'^AuthException: '), ''); // Remove double quotes if present cleaned = cleaned.replaceAll('"', ''); // Trim whitespace cleaned = cleaned.trim(); // Capitalize first letter if (cleaned.isNotEmpty) { cleaned = cleaned[0].toUpperCase() + cleaned.substring(1); } return cleaned.isNotEmpty ? cleaned : null; } }