import 'package:firebase_auth/firebase_auth.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:travel_mate/services/error_service.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; /// Service for handling Firebase authentication operations. /// /// This service provides methods for user authentication including email/password /// sign-in, Google sign-in, Apple sign-in, password reset, and account management. /// It acts as a wrapper around Firebase Auth functionality. class AuthService { /// Error service for logging authentication errors. final _errorService = ErrorService(); /// Firebase Auth instance for authentication operations. final FirebaseAuth firebaseAuth = FirebaseAuth.instance; /// Gets the currently authenticated user. /// /// Returns null if no user is currently signed in. User? get currentUser => firebaseAuth.currentUser; /// Stream that emits authentication state changes. /// /// Emits the current user when authenticated, null when not authenticated. Stream get authStateChanges => firebaseAuth.authStateChanges(); /// Signs in a user with email and password. /// /// Returns a [UserCredential] containing the authenticated user's information. /// Throws [FirebaseAuthException] if authentication fails. Future signInWithEmailAndPassword({ required String email, required String password, }) async { return await firebaseAuth.signInWithEmailAndPassword( email: email, password: password, ); } /// Creates a new user account with email and password. /// /// Returns a [UserCredential] containing the new user's information. /// Throws [FirebaseAuthException] if account creation fails. Future signUpWithEmailAndPassword({ required String email, required String password, }) async { return await firebaseAuth.createUserWithEmailAndPassword( email: email, password: password, ); } /// Signs out the current user. /// /// Clears the authentication state and signs out the user from Firebase. Future signOut() async { await firebaseAuth.signOut(); } /// Sends a password reset email to the specified email address. /// /// The user will receive an email with instructions to reset their password. /// Throws [FirebaseAuthException] if the email is invalid or other errors occur. Future resetPassword(String email) async { await firebaseAuth.sendPasswordResetEmail(email: email); } /// Updates the display name of the current user. /// /// Requires a user to be currently authenticated. /// Throws if no user is signed in. Future updateDisplayName({required String displayName}) async { await currentUser!.updateDisplayName(displayName); } /// Deletes the current user's account permanently. /// /// Requires re-authentication with the user's current password for security. /// This operation cannot be undone. /// /// [password] - The user's current password for re-authentication /// [email] - The user's email address for re-authentication Future deleteAccount() async { try { await currentUser!.delete(); await firebaseAuth.signOut(); } on FirebaseAuthException catch (e) { if (e.code == 'requires-recent-login') { _errorService.logError( 'Delete account requires recent login', StackTrace.current, ); rethrow; } _errorService.logError( 'Error deleting account: ${e.code} - ${e.message}', StackTrace.current, ); rethrow; } catch (e) { _errorService.logError( 'Unknown error deleting account: $e', StackTrace.current, ); rethrow; } } /// Resets the user's password after re-authentication. /// /// This method allows users to change their password by providing their /// current password for security verification. /// /// [currentPassword] - The user's current password for verification /// [newPassword] - The new password to set /// [email] - The user's email address for re-authentication Future resetPasswordFromCurrentPassword({ required String currentPassword, required String newPassword, required String email, }) async { // Re-authenticate the user for security AuthCredential credential = EmailAuthProvider.credential( email: email, password: currentPassword, ); await currentUser!.reauthenticateWithCredential(credential); // Update to the new password await currentUser!.updatePassword(newPassword); } /// Ensures Google Sign-In is properly initialized. /// /// This method must be called before attempting Google authentication. Future ensureInitialized() { return GoogleSignInPlatform.instance.init(const InitParameters()); } /// Signs in a user using Google authentication. /// /// Handles the complete Google Sign-In flow including platform initialization /// and credential exchange with Firebase. /// /// Returns a [UserCredential] containing the authenticated user's information. /// Throws various exceptions if authentication fails. Future signInWithGoogle() async { try { await ensureInitialized(); final AuthenticationResults result = await GoogleSignInPlatform.instance .authenticate(const AuthenticateParameters()); final String? idToken = result.authenticationTokens.idToken; if (idToken == null) { throw FirebaseAuthException( code: 'ERROR_MISSING_GOOGLE_ID_TOKEN', message: 'Missing Google ID Token', ); } else { final OAuthCredential credential = GoogleAuthProvider.credential( idToken: idToken, ); UserCredential userCredential = await firebaseAuth.signInWithCredential( credential, ); // Return the UserCredential instead of void return userCredential; } } on GoogleSignInException catch (e) { _errorService.logError('Google Sign-In error: $e', StackTrace.current); _errorService.showError( message: 'La connexion avec Google a échoué. Veuillez réessayer.', ); rethrow; } on FirebaseAuthException catch (e) { _errorService.logError( 'Firebase error during Google Sign-In initialization: $e', StackTrace.current, ); if (e.code == 'account-exists-with-different-credential') { _errorService.showError( message: 'Un compte existe déjà avec cette adresse email. Veuillez vous connecter avec la méthode utilisée précédemment.', ); } else { _errorService.showError( message: 'Une erreur est survenue lors de la connexion avec Google. Veuillez réessayer plus tard.', ); } rethrow; } catch (e) { _errorService.logError( 'Unknown error during Google Sign-In initialization: $e', StackTrace.current, ); _errorService.showError( message: 'Une erreur inattendue est survenue. Veuillez réessayer.', ); rethrow; } } /// Signs in a user using Apple authentication. /// /// Handles the complete Apple Sign-In flow including platform initialization /// and credential exchange with Firebase. Supports both iOS and Android platforms. /// /// Returns a [UserCredential] containing the authenticated user's information. /// Throws various exceptions if authentication fails or platform is not supported. Future signInWithApple() async { try { await ensureInitialized(); // Check if Apple Sign-In is available on this platform final bool isAvailable = await SignInWithApple.isAvailable(); if (!isAvailable) { throw FirebaseAuthException( code: 'ERROR_APPLE_SIGNIN_NOT_AVAILABLE', message: 'Apple Sign-In is not available on this platform', ); } // Request Apple ID credential with platform-specific configuration final AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential( scopes: [ AppleIDAuthorizationScopes.email, AppleIDAuthorizationScopes.fullName, ], // Configuration for Android/Web webAuthenticationOptions: WebAuthenticationOptions( clientId: 'be.devdayronvl.TravelMate.service', redirectUri: Uri.parse( 'https://travelmate-a47f5.firebaseapp.com/__/auth/handler', ), ), ); // Create OAuth credential for Firebase final OAuthCredential oauthCredential = OAuthProvider("apple.com") .credential( idToken: credential.identityToken, accessToken: credential.authorizationCode, ); // Sign in with Firebase UserCredential userCredential = await firebaseAuth.signInWithCredential( oauthCredential, ); // Update display name if it's the first sign-in and we have name info if (userCredential.additionalUserInfo?.isNewUser == true && credential.givenName != null && credential.familyName != null) { final String displayName = '${credential.givenName} ${credential.familyName}'; await userCredential.user?.updateDisplayName(displayName); } return userCredential; } on SignInWithAppleException catch (e) { _errorService.logError('Apple Sign-In error: $e', StackTrace.current); _errorService.showError( message: 'La connexion avec Apple a échoué. Veuillez réessayer.', ); throw FirebaseAuthException( code: 'ERROR_APPLE_SIGNIN_FAILED', message: 'Apple Sign-In failed', ); } on FirebaseAuthException catch (e) { _errorService.logError( 'Firebase error during Apple Sign-In: $e', StackTrace.current, ); if (e.code == 'account-exists-with-different-credential') { _errorService.showError( message: 'Un compte existe déjà avec cette adresse email. Veuillez vous connecter avec la méthode utilisée précédemment.', ); } else if (e.code == 'invalid-credential') { _errorService.showError( message: 'Les informations de connexion sont invalides.', ); } else if (e.code == 'user-disabled') { _errorService.showError( message: 'Ce compte utilisateur a été désactivé.', ); } else { _errorService.showError( message: 'Une erreur est survenue lors de la connexion avec Apple. Veuillez réessayer plus tard.', ); } rethrow; } catch (e) { _errorService.logError( 'Unknown error during Apple Sign-In: $e', StackTrace.current, ); _errorService.showError( message: 'Une erreur inattendue est survenue. Veuillez réessayer.', ); throw FirebaseAuthException( code: 'ERROR_APPLE_SIGNIN_UNKNOWN', message: 'Unknown error during Apple Sign-In', ); } } }