Merge dev into feature branch
This commit is contained in:
@@ -33,7 +33,10 @@ class ClientAuthenticationModule extends Module {
|
||||
void binds(Injector i) {
|
||||
// Repositories
|
||||
i.addLazySingleton<AuthRepositoryInterface>(
|
||||
() => AuthRepositoryImpl(apiService: i.get<BaseApiService>()),
|
||||
() => AuthRepositoryImpl(
|
||||
apiService: i.get<BaseApiService>(),
|
||||
firebaseAuthService: i.get<FirebaseAuthService>(),
|
||||
),
|
||||
);
|
||||
|
||||
// UseCases
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:client_authentication/src/domain/repositories/auth_repository_interface.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart' as firebase;
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart'
|
||||
show
|
||||
@@ -10,7 +9,6 @@ import 'package:krow_domain/krow_domain.dart'
|
||||
AppException,
|
||||
BaseApiService,
|
||||
ClientSession,
|
||||
InvalidCredentialsException,
|
||||
NetworkException,
|
||||
PasswordMismatchException,
|
||||
SignInFailedException,
|
||||
@@ -21,20 +19,23 @@ import 'package:krow_domain/krow_domain.dart'
|
||||
|
||||
/// Production implementation of the [AuthRepositoryInterface] for the client app.
|
||||
///
|
||||
/// Uses Firebase Auth client-side for sign-in (to maintain local auth state for
|
||||
/// the [AuthInterceptor]), then calls V2 `GET /auth/session` to retrieve
|
||||
/// business context. Sign-up provisioning (tenant, business, memberships) is
|
||||
/// handled entirely server-side by the V2 API.
|
||||
/// Uses [FirebaseAuthService] from core for local Firebase sign-in (to maintain
|
||||
/// local auth state for the [AuthInterceptor]), then calls V2 `GET /auth/session`
|
||||
/// to retrieve business context. Sign-up provisioning (tenant, business,
|
||||
/// memberships) is handled entirely server-side by the V2 API.
|
||||
class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
/// Creates an [AuthRepositoryImpl] with the given [BaseApiService].
|
||||
AuthRepositoryImpl({required BaseApiService apiService})
|
||||
: _apiService = apiService;
|
||||
/// Creates an [AuthRepositoryImpl] with the given dependencies.
|
||||
AuthRepositoryImpl({
|
||||
required BaseApiService apiService,
|
||||
required FirebaseAuthService firebaseAuthService,
|
||||
}) : _apiService = apiService,
|
||||
_firebaseAuthService = firebaseAuthService;
|
||||
|
||||
/// The V2 API service for backend calls.
|
||||
final BaseApiService _apiService;
|
||||
|
||||
/// Firebase Auth instance for client-side sign-in/sign-up.
|
||||
firebase.FirebaseAuth get _auth => firebase.FirebaseAuth.instance;
|
||||
/// Core Firebase Auth service abstraction.
|
||||
final FirebaseAuthService _firebaseAuthService;
|
||||
|
||||
@override
|
||||
Future<User> signInWithEmail({
|
||||
@@ -42,38 +43,26 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
// Step 1: Call V2 sign-in endpoint — server handles Firebase Auth
|
||||
// Step 1: Call V2 sign-in endpoint -- server handles Firebase Auth
|
||||
// via Identity Toolkit and returns a full auth envelope.
|
||||
final ApiResponse response = await _apiService.post(
|
||||
AuthEndpoints.clientSignIn,
|
||||
data: <String, dynamic>{
|
||||
'email': email,
|
||||
'password': password,
|
||||
},
|
||||
data: <String, dynamic>{'email': email, 'password': password},
|
||||
);
|
||||
|
||||
final Map<String, dynamic> body =
|
||||
response.data as Map<String, dynamic>;
|
||||
final Map<String, dynamic> body = response.data as Map<String, dynamic>;
|
||||
|
||||
// Step 2: Sign in locally so AuthInterceptor can attach Bearer tokens
|
||||
// to subsequent requests. The V2 API already validated credentials, so
|
||||
// email/password sign-in establishes the local Firebase Auth state.
|
||||
final firebase.UserCredential credential =
|
||||
await _auth.signInWithEmailAndPassword(
|
||||
await _firebaseAuthService.signInWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
final firebase.User? firebaseUser = credential.user;
|
||||
if (firebaseUser == null) {
|
||||
throw const SignInFailedException(
|
||||
technicalMessage: 'Local Firebase sign-in failed after V2 sign-in',
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Populate session store from the V2 auth envelope directly
|
||||
// (no need for a separate GET /auth/session call).
|
||||
return _populateStoreFromAuthEnvelope(body, firebaseUser, email);
|
||||
return _populateStoreFromAuthEnvelope(body, email);
|
||||
} on AppException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
@@ -106,38 +95,34 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
// Step 2: Sign in locally to Firebase Auth so AuthInterceptor works
|
||||
// for subsequent requests. The V2 API already created the Firebase
|
||||
// account, so this should succeed.
|
||||
final firebase.UserCredential credential =
|
||||
await _auth.signInWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
final firebase.User? firebaseUser = credential.user;
|
||||
if (firebaseUser == null) {
|
||||
try {
|
||||
await _firebaseAuthService.signInWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
} on SignInFailedException {
|
||||
throw const SignUpFailedException(
|
||||
technicalMessage: 'Local Firebase sign-in failed after V2 sign-up',
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Populate store from the sign-up response envelope.
|
||||
return _populateStoreFromAuthEnvelope(body, firebaseUser, email);
|
||||
} on firebase.FirebaseAuthException catch (e) {
|
||||
if (e.code == 'email-already-in-use') {
|
||||
throw AccountExistsException(
|
||||
technicalMessage: 'Firebase: ${e.message}',
|
||||
);
|
||||
} else if (e.code == 'weak-password') {
|
||||
throw WeakPasswordException(technicalMessage: 'Firebase: ${e.message}');
|
||||
} else if (e.code == 'network-request-failed') {
|
||||
throw NetworkException(technicalMessage: 'Firebase: ${e.message}');
|
||||
} else {
|
||||
throw SignUpFailedException(
|
||||
technicalMessage: 'Firebase auth error: ${e.message}',
|
||||
);
|
||||
}
|
||||
return _populateStoreFromAuthEnvelope(body, email);
|
||||
} on AppException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
// Map common Firebase-originated errors from the V2 API response
|
||||
// to domain exceptions.
|
||||
final String errorMessage = e.toString();
|
||||
if (errorMessage.contains('EMAIL_EXISTS') ||
|
||||
errorMessage.contains('email-already-in-use')) {
|
||||
throw AccountExistsException(technicalMessage: errorMessage);
|
||||
} else if (errorMessage.contains('WEAK_PASSWORD') ||
|
||||
errorMessage.contains('weak-password')) {
|
||||
throw WeakPasswordException(technicalMessage: errorMessage);
|
||||
} else if (errorMessage.contains('network-request-failed')) {
|
||||
throw NetworkException(technicalMessage: errorMessage);
|
||||
}
|
||||
throw SignUpFailedException(technicalMessage: 'Unexpected error: $e');
|
||||
}
|
||||
}
|
||||
@@ -155,16 +140,13 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
// Step 1: Call V2 sign-out endpoint for server-side token revocation.
|
||||
await _apiService.post(AuthEndpoints.clientSignOut);
|
||||
} catch (e) {
|
||||
developer.log(
|
||||
'V2 sign-out request failed: $e',
|
||||
name: 'AuthRepository',
|
||||
);
|
||||
developer.log('V2 sign-out request failed: $e', name: 'AuthRepository');
|
||||
// Continue with local sign-out even if server-side fails.
|
||||
}
|
||||
|
||||
try {
|
||||
// Step 2: Sign out from local Firebase Auth.
|
||||
await _auth.signOut();
|
||||
// Step 2: Sign out from local Firebase Auth via core service.
|
||||
await _firebaseAuthService.signOut();
|
||||
} catch (e) {
|
||||
throw Exception('Error signing out locally: $e');
|
||||
}
|
||||
@@ -181,7 +163,6 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
/// returns a domain [User].
|
||||
User _populateStoreFromAuthEnvelope(
|
||||
Map<String, dynamic> envelope,
|
||||
firebase.User firebaseUser,
|
||||
String fallbackEmail,
|
||||
) {
|
||||
final Map<String, dynamic>? userJson =
|
||||
@@ -202,14 +183,15 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
'userId': userJson['id'] ?? userJson['userId'],
|
||||
},
|
||||
};
|
||||
final ClientSession clientSession =
|
||||
ClientSession.fromJson(normalisedEnvelope);
|
||||
final ClientSession clientSession = ClientSession.fromJson(
|
||||
normalisedEnvelope,
|
||||
);
|
||||
ClientSessionStore.instance.setSession(clientSession);
|
||||
}
|
||||
|
||||
final String userId =
|
||||
userJson?['id'] as String? ?? firebaseUser.uid;
|
||||
final String? email = userJson?['email'] as String? ?? fallbackEmail;
|
||||
final String userId = userJson?['id'] as String? ??
|
||||
(_firebaseAuthService.currentUserUid ?? '');
|
||||
final String email = userJson?['email'] as String? ?? fallbackEmail;
|
||||
|
||||
return User(
|
||||
id: userId,
|
||||
|
||||
@@ -14,7 +14,6 @@ dependencies:
|
||||
flutter_bloc: ^8.1.0
|
||||
flutter_modular: ^6.3.0
|
||||
equatable: ^2.0.5
|
||||
firebase_auth: ^6.1.2
|
||||
|
||||
# Architecture Packages
|
||||
design_system:
|
||||
|
||||
Reference in New Issue
Block a user