Files
b0esche_cloud/b0esche_cloud/lib/blocs/auth/auth_bloc.dart
Leon Bösche 5caf3f6b62 idle
2026-01-08 22:09:52 +01:00

275 lines
7.6 KiB
Dart

import 'package:bloc/bloc.dart';
import '../session/session_bloc.dart';
import '../session/session_event.dart';
import '../session/session_state.dart';
import 'auth_event.dart';
import 'auth_state.dart';
import '../../services/api_client.dart';
import '../../models/api_error.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final ApiClient apiClient;
final SessionBloc sessionBloc;
AuthBloc({required this.apiClient, required this.sessionBloc})
: super(AuthInitial()) {
on<SignupStarted>(_onSignupStarted);
on<RegistrationChallengeRequested>(_onRegistrationChallengeRequested);
on<RegistrationResponseSubmitted>(_onRegistrationResponseSubmitted);
on<LoginRequested>(_onLoginRequested);
on<PasswordLoginRequested>(_onPasswordLoginRequested);
on<AuthenticationChallengeRequested>(_onAuthenticationChallengeRequested);
on<AuthenticationResponseSubmitted>(_onAuthenticationResponseSubmitted);
on<LogoutRequested>(_onLogoutRequested);
on<CheckAuthRequested>(_onCheckAuthRequested);
}
Future<void> _onSignupStarted(
SignupStarted event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Creating account...'));
try {
final response = await apiClient.post(
'/auth/signup',
data: {
'username': event.username,
'email': event.email,
'displayName': event.displayName,
'password': event.password ?? '',
},
fromJson: (data) => data,
);
final userId = response['userId'];
emit(
SignupInProgress(
username: event.username,
email: event.email,
displayName: event.displayName,
),
);
// Trigger registration challenge request
add(RegistrationChallengeRequested(userId: userId));
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onRegistrationChallengeRequested(
RegistrationChallengeRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Getting registration challenge...'));
try {
final response = await apiClient.post(
'/auth/registration-challenge',
data: {'userId': event.userId},
fromJson: (data) => data,
);
emit(
RegistrationChallengeReceived(
userId: event.userId,
challenge: response['challenge'],
rpName: response['rp']['name'],
rpId: response['rp']['id'],
),
);
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onRegistrationResponseSubmitted(
RegistrationResponseSubmitted event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Verifying registration...'));
try {
final response = await apiClient.post(
'/auth/registration-verify',
data: {
'userId': event.userId,
'challenge': event.challenge,
'credentialId': event.credentialId,
'publicKey': event.publicKey,
'clientDataJSON': event.clientDataJSON,
'attestationObject': event.attestationObject,
},
fromJson: (data) => data,
);
final token = response['token'];
final user = response['user'];
sessionBloc.add(SessionStarted(token));
emit(
AuthAuthenticated(
token: token,
userId: user['id'],
username: user['username'],
email: user['email'],
),
);
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onLoginRequested(
LoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Requesting authentication challenge...'));
try {
add(AuthenticationChallengeRequested(username: event.username));
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onAuthenticationChallengeRequested(
AuthenticationChallengeRequested event,
Emitter<AuthState> emit,
) async {
try {
final response = await apiClient.post(
'/auth/authentication-challenge',
data: {'username': event.username},
fromJson: (data) => data,
);
final credentialIds =
(response['allowCredentials'] as List?)
?.map((id) => id.toString())
.toList() ??
[];
emit(
AuthenticationChallengeReceived(
challenge: response['challenge'],
credentialIds: credentialIds,
),
);
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onAuthenticationResponseSubmitted(
AuthenticationResponseSubmitted event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Verifying authentication...'));
try {
final response = await apiClient.post(
'/auth/authentication-verify',
data: {
'username': event.username,
'challenge': event.challenge,
'credentialId': event.credentialId,
'authenticatorData': event.authenticatorData,
'clientDataJSON': event.clientDataJSON,
'signature': event.signature,
},
fromJson: (data) => data,
);
final token = response['token'];
final user = response['user'];
sessionBloc.add(SessionStarted(token));
emit(
AuthAuthenticated(
token: token,
userId: user['id'],
username: user['username'],
email: user['email'],
),
);
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
Future<void> _onLogoutRequested(
LogoutRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Logging out...'));
await Future.delayed(const Duration(milliseconds: 500));
sessionBloc.add(SessionExpired());
emit(AuthUnauthenticated());
}
Future<void> _onPasswordLoginRequested(
PasswordLoginRequested event,
Emitter<AuthState> emit,
) async {
emit(AuthLoading(message: 'Authenticating...'));
try {
final response = await apiClient.post(
'/auth/password-login',
data: {'username': event.username, 'password': event.password},
fromJson: (data) => data,
);
final token = response['token'];
final user = response['user'];
sessionBloc.add(SessionStarted(token));
emit(
AuthAuthenticated(
token: token,
userId: user['id'],
username: user['username'],
email: user['email'],
),
);
} catch (e) {
final errorMessage = _extractErrorMessage(e);
emit(AuthFailure(errorMessage));
}
}
String _extractErrorMessage(dynamic error) {
if (error is ApiError) {
return error.message;
}
return error.toString();
}
Future<void> _onCheckAuthRequested(
CheckAuthRequested event,
Emitter<AuthState> emit,
) async {
// Check if session is active from persistent storage
final sessionState = sessionBloc.state;
if (sessionState is SessionActive) {
// Session already active - emit authenticated state with minimal info
// The full user info will be fetched when needed
emit(
AuthAuthenticated(
token: sessionState.token,
userId: '',
username: '',
email: '',
),
);
} else {
emit(AuthUnauthenticated());
}
}
}