Files
TravelMate/lib/services/auth_service.dart

272 lines
9.4 KiB
Dart

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<User?> 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<UserCredential> 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<UserCredential> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<UserCredential> 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);
rethrow;
} on FirebaseAuthException catch (e) {
_errorService.logError(
'Firebase error during Google Sign-In initialization: $e',
StackTrace.current,
);
rethrow;
} catch (e) {
_errorService.logError(
'Unknown error during Google Sign-In initialization: $e',
StackTrace.current,
);
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<UserCredential> 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',
redirectUri: Uri.parse(
'https://your-project-id.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);
throw FirebaseAuthException(
code: 'ERROR_APPLE_SIGNIN_FAILED',
message: 'Apple Sign-In failed: ${e.toString()}',
);
} on FirebaseAuthException catch (e) {
_errorService.logError(
'Firebase error during Apple Sign-In: $e',
StackTrace.current,
);
rethrow;
} catch (e) {
_errorService.logError(
'Unknown error during Apple Sign-In: $e',
StackTrace.current,
);
throw FirebaseAuthException(
code: 'ERROR_APPLE_SIGNIN_UNKNOWN',
message: 'Unknown error during Apple Sign-In: $e',
);
}
}
}