272 lines
9.4 KiB
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',
|
|
);
|
|
}
|
|
}
|
|
}
|