feat(auth): gestion reAuthRequired + suppression flux changement mot de passe manuel
- AuthStatusResult: nouveau champ reAuthRequired (ancien compte nécessitant UPDATE_PASSWORD) - AuthBloc._onLoginRequested: si reAuthRequired → logout silencieux + re-déclenchement AppAuth automatique (Keycloak affiche l'écran de changement de mot de passe dans Chrome Custom Tab) - AuthBloc._onStatusChecked: si reAuthRequired → logout + AuthUnauthenticated (redirection login) - Remplacement du flux premierLoginComplet (boolean) par enum côté backend - Suppression de AuthPasswordChangeRequired, AuthPasswordChanging, change_password_page.dart
This commit is contained in:
@@ -19,11 +19,7 @@ abstract class AuthEvent extends Equatable {
|
||||
}
|
||||
|
||||
class AuthLoginRequested extends AuthEvent {
|
||||
final String email;
|
||||
final String password;
|
||||
const AuthLoginRequested(this.email, this.password);
|
||||
@override
|
||||
List<Object?> get props => [email, password];
|
||||
const AuthLoginRequested();
|
||||
}
|
||||
|
||||
class AuthLogoutRequested extends AuthEvent { const AuthLogoutRequested(); }
|
||||
@@ -80,13 +76,15 @@ class AuthPendingOnboarding extends AuthState {
|
||||
final String onboardingState; // NO_SUBSCRIPTION | AWAITING_PAYMENT | PAYMENT_INITIATED | AWAITING_VALIDATION
|
||||
final String? souscriptionId;
|
||||
final String? organisationId;
|
||||
final String? typeOrganisation;
|
||||
const AuthPendingOnboarding({
|
||||
required this.onboardingState,
|
||||
this.souscriptionId,
|
||||
this.organisationId,
|
||||
this.typeOrganisation,
|
||||
});
|
||||
@override
|
||||
List<Object?> get props => [onboardingState, souscriptionId, organisationId];
|
||||
List<Object?> get props => [onboardingState, souscriptionId, organisationId, typeOrganisation];
|
||||
}
|
||||
|
||||
// === BLOC ===
|
||||
@@ -104,10 +102,30 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
Future<void> _onLoginRequested(AuthLoginRequested event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
try {
|
||||
final rawUser = await _authService.login(event.email, event.password);
|
||||
var rawUser = await _authService.loginWithAppAuth();
|
||||
if (rawUser != null) {
|
||||
// Vérification du statut du compte UnionFlow (indépendant de Keycloak)
|
||||
final status = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
|
||||
var status = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
|
||||
|
||||
// Ancien compte détecté : UPDATE_PASSWORD vient d'être assigné dans Keycloak.
|
||||
// Déclencher une nouvelle authentification AppAuth pour afficher l'écran de changement.
|
||||
if (status != null && status.reAuthRequired) {
|
||||
AppLogger.info('AuthBloc: réauthentification requise (ancien compte), re-déclenchement AppAuth');
|
||||
await _authService.logout();
|
||||
final reAuthUser = await _authService.loginWithAppAuth();
|
||||
if (reAuthUser == null) {
|
||||
emit(const AuthError('Connexion annulée.'));
|
||||
return;
|
||||
}
|
||||
rawUser = reAuthUser;
|
||||
status = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
|
||||
// Garde-fou : si toujours reAuthRequired après la seconde tentative → erreur de config
|
||||
if (status != null && status.reAuthRequired) {
|
||||
AppLogger.error('AuthBloc: reAuthRequired persistant après réauthentification');
|
||||
emit(const AuthError('Erreur de configuration du compte. Contactez votre administrateur.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (status != null && status.isPendingOnboarding) {
|
||||
// OrgAdmin en attente → rediriger vers l'onboarding (sans déconnecter)
|
||||
@@ -120,6 +138,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
onboardingState: status.onboardingState,
|
||||
souscriptionId: status.souscriptionId,
|
||||
organisationId: orgId,
|
||||
typeOrganisation: status.typeOrganisation,
|
||||
));
|
||||
return;
|
||||
}
|
||||
@@ -133,7 +152,17 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final user = await _enrichUserWithOrgContext(rawUser);
|
||||
// Si premier login venant d'être complété, rafraîchir le token pour obtenir
|
||||
// les rôles MEMBRE + MEMBRE_ACTIF assignés par le backend lors de l'activation.
|
||||
User user;
|
||||
if (status != null && status.premierLoginComplet) {
|
||||
await _authService.refreshToken();
|
||||
final refreshedRawUser = await _authService.getCurrentUser();
|
||||
user = await _enrichUserWithOrgContext(refreshedRawUser ?? rawUser);
|
||||
} else {
|
||||
user = await _enrichUserWithOrgContext(rawUser);
|
||||
}
|
||||
|
||||
final permissions = await PermissionEngine.getEffectivePermissions(user);
|
||||
final token = await _authService.getValidToken();
|
||||
await DashboardCacheManager.invalidateForRole(user.primaryRole);
|
||||
@@ -175,6 +204,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
// Vérification du statut du compte (au redémarrage de l'app)
|
||||
final status = await _authService.getAuthStatus(AppConfig.apiBaseUrl);
|
||||
|
||||
// Ancien compte sans UPDATE_PASSWORD : effacer la session locale et renvoyer vers le login.
|
||||
// L'utilisateur sera invité à se reconnecter — Keycloak affichera l'écran de changement de mot de passe.
|
||||
if (status != null && status.reAuthRequired) {
|
||||
AppLogger.info('AuthBloc: réauthentification requise au démarrage (ancien compte)');
|
||||
await _authService.logout();
|
||||
emit(AuthUnauthenticated());
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != null && status.isPendingOnboarding) {
|
||||
final user = await _enrichUserWithOrgContext(rawUser);
|
||||
final orgId = status.organisationId ??
|
||||
@@ -185,6 +223,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
onboardingState: status.onboardingState,
|
||||
souscriptionId: status.souscriptionId,
|
||||
organisationId: orgId,
|
||||
typeOrganisation: status.typeOrganisation ?? 'ASSOCIATION',
|
||||
));
|
||||
return;
|
||||
}
|
||||
@@ -198,7 +237,17 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final user = await _enrichUserWithOrgContext(rawUser);
|
||||
// Si premier login venant d'être complété, rafraîchir le token pour obtenir
|
||||
// les rôles MEMBRE + MEMBRE_ACTIF assignés par le backend lors de l'activation.
|
||||
User user;
|
||||
if (status != null && status.premierLoginComplet) {
|
||||
await _authService.refreshToken();
|
||||
final refreshedRawUser = await _authService.getCurrentUser();
|
||||
user = await _enrichUserWithOrgContext(refreshedRawUser ?? rawUser);
|
||||
} else {
|
||||
user = await _enrichUserWithOrgContext(rawUser);
|
||||
}
|
||||
|
||||
final permissions = await PermissionEngine.getEffectivePermissions(user);
|
||||
final token = await _authService.getValidToken();
|
||||
emit(AuthAuthenticated(
|
||||
|
||||
Reference in New Issue
Block a user