import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'auth_client.dart';
abstract interface class AuthTokenStorage {
Future<String?> readRefreshToken();
Future<void> writeTokens({
required String accessToken,
required String refreshToken,
});
Future<void> deleteTokens();
}
class SecureAuthTokenStorage implements AuthTokenStorage {
static const _accessTokenKey = 'machweis_access_token';
static const _refreshTokenKey = 'machweis_refresh_token';
final FlutterSecureStorage _storage = const FlutterSecureStorage();
@override
Future<void> deleteTokens() => Future.wait([
_storage.delete(key: _accessTokenKey),
_storage.delete(key: _refreshTokenKey),
]);
@override
Future<String?> readRefreshToken() => _storage.read(key: _refreshTokenKey);
@override
Future<void> writeTokens({
required String accessToken,
required String refreshToken,
}) => Future.wait([
_storage.write(key: _accessTokenKey, value: accessToken),
_storage.write(key: _refreshTokenKey, value: refreshToken),
]);
}
enum AuthStatus { loading, signedOut, signedIn, webUnavailable }
class AuthStore extends ChangeNotifier {
AuthStore({AuthClient? client, AuthTokenStorage? storage})
: _client = client ?? AuthClient(),
_storage = storage ?? SecureAuthTokenStorage();
static final instance = AuthStore();
final AuthClient _client;
final AuthTokenStorage _storage;
AuthStatus status = AuthStatus.signedOut;
AuthSession? session;
String? error;
bool get isSignedIn => status == AuthStatus.signedIn;
Future<String> accessToken({bool forceRefresh = false}) async {
final current = session;
if (current == null) {
throw const AuthException('Keine Anmeldung vorhanden.');
}
if (!forceRefresh) return current.accessToken;
final refreshed = kIsWeb
? await _client.webRefresh()
: await _client.refresh(current.refreshToken);
await _persist(refreshed);
session = refreshed;
status = AuthStatus.signedIn;
notifyListeners();
return refreshed.accessToken;
}
Future<void> initialize() async {
if (kIsWeb) {
try {
session = await _client.webMe();
status = AuthStatus.signedIn;
} on AuthException {
session = null;
status = AuthStatus.signedOut;
} finally {
notifyListeners();
}
return;
}
final refresh = await _storage.readRefreshToken();
if (refresh == null) {
status = AuthStatus.signedOut;
notifyListeners();
return;
}
try {
session = await _client.refresh(refresh);
await _client.me(session!.accessToken);
await _persist(session!);
status = AuthStatus.signedIn;
} on AuthException {
await _storage.deleteTokens();
session = null;
status = AuthStatus.signedOut;
} finally {
notifyListeners();
}
}
Future<void> login(String email, String password) => _authenticate(
() => kIsWeb
? _client.webLogin(email: email, password: password)
: _client.login(email: email, password: password),
);
Future<void> registerOwner(
String tenantName,
String email,
String password,
List<String> industryProfiles,
) => _authenticate(
() => _client.registerOwner(
tenantName: tenantName,
email: email,
password: password,
industryProfiles: industryProfiles,
),
);
Future<void> acceptInvitation(String token, String password) => _authenticate(
() => _client.acceptInvitation(token: token, password: password),
);
Future<Map<String, dynamic>> inspectInvitation(String token) =>
_client.inspectInvitation(token);
Future<void> _authenticate(Future<AuthSession> Function() action) async {
error = null;
status = AuthStatus.loading;
notifyListeners();
try {
session = await action();
await _persist(session!);
status = AuthStatus.signedIn;
} on AuthException catch (value) {
await _deleteTokensSafely();
session = null;
error = value.message;
status = AuthStatus.signedOut;
} on Object catch (value) {
await _deleteTokensSafely();
session = null;
error =
'Die Anmeldung konnte nicht abgeschlossen werden. Bitte versuche es erneut.';
status = AuthStatus.signedOut;
if (kDebugMode) {
debugPrint(
'[MACHWEIS Auth] Anmeldung fehlgeschlagen: ${value.runtimeType}',
);
}
} finally {
notifyListeners();
}
}
Future<void> logout() async {
final token = session?.refreshToken;
await _storage.deleteTokens();
session = null;
error = null;
status = AuthStatus.signedOut;
notifyListeners();
try {
if (kIsWeb) {
await _client.webLogout();
} else if (token != null) {
await _client.logout(token);
}
} on Object catch (value) {
if (kDebugMode) {
debugPrint(
'[MACHWEIS Auth] Server-Abmeldung fehlgeschlagen: ${value.runtimeType}',
);
}
}
}
Future<void> updateIndustryProfiles(List<String> profiles) async {
final current = session;
if (current == null) return;
final values = await _client.updateIndustryProfiles(
current.accessToken,
profiles,
);
session = AuthSession(
accessToken: current.accessToken,
refreshToken: current.refreshToken,
userId: current.userId,
email: current.email,
tenantId: current.tenantId,
role: current.role,
industryProfiles: values,
);
notifyListeners();
}
Future<void> _persist(AuthSession value) => kIsWeb
? Future<void>.value()
: _storage.writeTokens(
accessToken: value.accessToken,
refreshToken: value.refreshToken,
);
Future<void> _deleteTokensSafely() async {
try {
await _storage.deleteTokens();
} on Object catch (value) {
if (kDebugMode) {
debugPrint(
'[MACHWEIS Auth] Token-Bereinigung fehlgeschlagen: ${value.runtimeType}',
);
}
}
}
}
