- Create auth feature with Clean Architecture (domain/data/presentation) - Add login and register pages with form validation - Implement secure token storage with flutter_secure_storage - Create AuthenticatedClient for automatic Bearer token headers - Add AuthViewModel for global auth state management - Update router with auth guards (redirect to login if not authenticated) - Add logout option to settings page - Update TaskRemoteDataSource to use authenticated client - Add auth-related localization strings (EN/DE)
104 lines
2.8 KiB
Dart
104 lines
2.8 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
import '../models/token_model.dart';
|
|
import '../models/user_model.dart';
|
|
|
|
abstract class AuthLocalDataSource {
|
|
Future<void> saveTokens(TokenModel tokens);
|
|
Future<TokenModel?> getTokens();
|
|
Future<void> deleteTokens();
|
|
Future<void> saveUser(UserModel user);
|
|
Future<UserModel?> getUser();
|
|
Future<void> deleteUser();
|
|
Future<void> clearAll();
|
|
Stream<bool> get authStateChanges;
|
|
void notifyAuthStateChanged(bool isAuthenticated);
|
|
}
|
|
|
|
class AuthLocalDataSourceImpl implements AuthLocalDataSource {
|
|
final FlutterSecureStorage _storage;
|
|
final _authStateController = StreamController<bool>.broadcast();
|
|
|
|
static const String _accessTokenKey = 'access_token';
|
|
static const String _refreshTokenKey = 'refresh_token';
|
|
static const String _tokenTypeKey = 'token_type';
|
|
static const String _userKey = 'current_user';
|
|
|
|
AuthLocalDataSourceImpl({required FlutterSecureStorage storage})
|
|
: _storage = storage;
|
|
|
|
@override
|
|
Future<void> saveTokens(TokenModel tokens) async {
|
|
await Future.wait([
|
|
_storage.write(key: _accessTokenKey, value: tokens.accessToken),
|
|
_storage.write(key: _refreshTokenKey, value: tokens.refreshToken),
|
|
_storage.write(key: _tokenTypeKey, value: tokens.tokenType),
|
|
]);
|
|
}
|
|
|
|
@override
|
|
Future<TokenModel?> getTokens() async {
|
|
final accessToken = await _storage.read(key: _accessTokenKey);
|
|
final refreshToken = await _storage.read(key: _refreshTokenKey);
|
|
final tokenType = await _storage.read(key: _tokenTypeKey);
|
|
|
|
if (accessToken == null || refreshToken == null) {
|
|
return null;
|
|
}
|
|
|
|
return TokenModel(
|
|
accessToken: accessToken,
|
|
refreshToken: refreshToken,
|
|
tokenType: tokenType ?? 'bearer',
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteTokens() async {
|
|
await Future.wait([
|
|
_storage.delete(key: _accessTokenKey),
|
|
_storage.delete(key: _refreshTokenKey),
|
|
_storage.delete(key: _tokenTypeKey),
|
|
]);
|
|
}
|
|
|
|
@override
|
|
Future<void> saveUser(UserModel user) async {
|
|
await _storage.write(key: _userKey, value: json.encode(user.toJson()));
|
|
}
|
|
|
|
@override
|
|
Future<UserModel?> getUser() async {
|
|
final userJson = await _storage.read(key: _userKey);
|
|
if (userJson == null) {
|
|
return null;
|
|
}
|
|
return UserModel.fromJson(json.decode(userJson));
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteUser() async {
|
|
await _storage.delete(key: _userKey);
|
|
}
|
|
|
|
@override
|
|
Future<void> clearAll() async {
|
|
await Future.wait([
|
|
deleteTokens(),
|
|
deleteUser(),
|
|
]);
|
|
notifyAuthStateChanged(false);
|
|
}
|
|
|
|
@override
|
|
Stream<bool> get authStateChanges => _authStateController.stream;
|
|
|
|
@override
|
|
void notifyAuthStateChanged(bool isAuthenticated) {
|
|
_authStateController.add(isAuthenticated);
|
|
}
|
|
}
|