- 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)
69 lines
2.0 KiB
Dart
69 lines
2.0 KiB
Dart
import 'package:http/http.dart' as http;
|
|
|
|
import '../../features/auth/data/datasources/auth_local_datasource.dart';
|
|
import '../../features/auth/domain/repositories/auth_repository.dart';
|
|
import '../errors/exceptions.dart';
|
|
|
|
class AuthenticatedClient extends http.BaseClient {
|
|
final http.Client _inner;
|
|
final AuthLocalDataSource _localDataSource;
|
|
final AuthRepository _authRepository;
|
|
|
|
AuthenticatedClient({
|
|
required AuthLocalDataSource localDataSource,
|
|
required AuthRepository authRepository,
|
|
http.Client? inner,
|
|
}) : _localDataSource = localDataSource,
|
|
_authRepository = authRepository,
|
|
_inner = inner ?? http.Client();
|
|
|
|
@override
|
|
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
|
final tokens = await _localDataSource.getTokens();
|
|
if (tokens != null) {
|
|
request.headers['Authorization'] = 'Bearer ${tokens.accessToken}';
|
|
}
|
|
|
|
var response = await _inner.send(request);
|
|
|
|
if (response.statusCode == 401 && tokens != null) {
|
|
final refreshResult = await _authRepository.refreshToken();
|
|
final refreshed = refreshResult.when(
|
|
success: (_) => true,
|
|
error: (_) => false,
|
|
);
|
|
|
|
if (refreshed) {
|
|
final newTokens = await _localDataSource.getTokens();
|
|
if (newTokens != null) {
|
|
final newRequest = _copyRequest(request, newTokens.accessToken);
|
|
response = await _inner.send(newRequest);
|
|
}
|
|
} else {
|
|
await _localDataSource.clearAll();
|
|
throw const UnauthorizedException(message: 'Session expired');
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
http.BaseRequest _copyRequest(http.BaseRequest original, String token) {
|
|
final http.Request newRequest = http.Request(original.method, original.url)
|
|
..headers.addAll(original.headers)
|
|
..headers['Authorization'] = 'Bearer $token';
|
|
|
|
if (original is http.Request && original.body.isNotEmpty) {
|
|
newRequest.body = original.body;
|
|
}
|
|
|
|
return newRequest;
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
_inner.close();
|
|
super.close();
|
|
}
|
|
}
|