Shuffle/app/lib/features/auth/data/auth_status_tracker.dart
cschlaefke 87c0d7d1ec feat(auth): add password reset (forgot password, recovery link, new password)
ForgotPasswordScreen with neutral reaction (F12), CheckEmail reset mode on
its own path, recovery link handling and ResetPasswordScreen with the
recovery guard held until the user confirms the success message. Carries
the from parameter between login, register and forgot password. Removes
the route placeholders.

Refs #1

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 09:47:03 +01:00

72 lines
2.4 KiB
Dart

import 'package:shuffle/features/auth/domain/auth_status.dart';
import 'package:shuffle/features/auth/domain/auth_user.dart';
// `AuthUser` heißt auch in supabase_flutter; hier ist die Domänenklasse gemeint.
import 'package:supabase_flutter/supabase_flutter.dart' hide AuthUser;
/// Bildet `AuthChangeEvent`s auf [AuthStatus] ab (Spec #1 § 5.5).
///
/// Abweichung von der Wortlaut-Zuordnung in § 5.5: Die Recovery bleibt
/// bestehen, bis [finishRecovery] oder `signedOut` eintrifft. Ein
/// `tokenRefreshed` oder `signedIn` während der Recovery hebt den Guard nicht
/// auf. `userUpdated` (Passwort geändert) beendet ihn ebenfalls **nicht**
/// mehr, sondern setzt nur `passwordChanged`: Sonst würde der Router den
/// Nutzer von `/reset-password` wegleiten, bevor die Erfolgsmeldung
/// (KTO-01 Kriterium 9) zu sehen ist. Erst die ausdrückliche Bestätigung des
/// Nutzers beendet den Guard. Der Zustand lebt nur im Prozess
/// (Spec #1 § 5.4, R12): Aus dem `amr`-Claim lässt er sich nicht ableiten.
class AuthStatusTracker {
bool _recovery = false;
bool _passwordChanged = false;
/// Status für [session] unter Berücksichtigung der laufenden Recovery.
AuthStatus statusFor(Session? session) {
final user = session?.user;
if (user == null) {
return const AuthStatus.signedOut();
}
final authUser = AuthUser(
id: user.id,
email: user.email,
emailConfirmed: user.emailConfirmedAt != null,
);
return _recovery
? AuthStatus.passwordRecovery(
authUser,
passwordChanged: _passwordChanged,
)
: AuthStatus.signedIn(authUser);
}
/// Verarbeitet ein Ereignis und liefert den neuen Status.
AuthStatus handle(AuthChangeEvent event, Session? session) {
switch (event) {
case AuthChangeEvent.passwordRecovery:
_recovery = session != null;
_passwordChanged = false;
case AuthChangeEvent.userUpdated:
// The password change itself: stay in the guard (see class doc).
if (_recovery) {
_passwordChanged = true;
}
case AuthChangeEvent.signedOut:
_end();
default:
break;
}
if (session == null) {
_end();
}
return statusFor(session);
}
/// Ends the recovery after the user confirmed the success message.
AuthStatus finishRecovery(Session? session) {
_end();
return statusFor(session);
}
void _end() {
_recovery = false;
_passwordChanged = false;
}
}