AgendaTasks/lib/features/auth/data/datasources/auth_remote_datasource.dart
m3mo d8164be49a Add user authentication to Flutter frontend
- 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)
2026-02-02 22:58:07 +01:00

180 lines
4.7 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import '../../../../core/errors/exceptions.dart';
import '../../../../core/logging/app_logger.dart';
import '../models/token_model.dart';
import '../models/user_model.dart';
abstract class AuthRemoteDataSource {
Future<UserModel> register({
required String email,
required String password,
required String name,
});
Future<TokenModel> login({
required String email,
required String password,
});
Future<UserModel> getCurrentUser(String accessToken);
Future<TokenModel> refreshToken(String refreshToken);
}
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final AppLogger logger;
final http.Client _client;
static const String _baseUrl = String.fromEnvironment(
'API_URL',
defaultValue: 'http://localhost:8000',
);
AuthRemoteDataSourceImpl({
required this.logger,
http.Client? client,
}) : _client = client ?? http.Client();
Map<String, String> get _headers => {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
Map<String, String> _authHeaders(String token) => {
..._headers,
'Authorization': 'Bearer $token',
};
@override
Future<UserModel> register({
required String email,
required String password,
required String name,
}) async {
final uri = Uri.parse('$_baseUrl/auth/register');
logger.info('POST $uri');
try {
final response = await _client.post(
uri,
headers: _headers,
body: json.encode({
'email': email,
'password': password,
'name': name,
}),
);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 201 || response.statusCode == 200) {
return UserModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 400) {
final body = json.decode(response.body);
throw AuthException(
message: body['detail'] ?? 'Registration failed',
);
} else {
throw ServerException(
message: 'Registration failed',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TokenModel> login({
required String email,
required String password,
}) async {
final uri = Uri.parse('$_baseUrl/auth/login');
logger.info('POST $uri');
try {
final response = await _client.post(
uri,
headers: _headers,
body: json.encode({
'email': email,
'password': password,
}),
);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 200) {
return TokenModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 401) {
throw const AuthException(message: 'Invalid email or password');
} else {
throw ServerException(
message: 'Login failed',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<UserModel> getCurrentUser(String accessToken) async {
final uri = Uri.parse('$_baseUrl/auth/me');
logger.info('GET $uri');
try {
final response = await _client.get(
uri,
headers: _authHeaders(accessToken),
);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 200) {
return UserModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 401) {
throw const UnauthorizedException();
} else {
throw ServerException(
message: 'Failed to get user info',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
@override
Future<TokenModel> refreshToken(String refreshToken) async {
final uri = Uri.parse('$_baseUrl/auth/refresh');
logger.info('POST $uri');
try {
final response = await _client.post(
uri,
headers: _headers,
body: json.encode({'refresh_token': refreshToken}),
);
logger.debug('Response: ${response.statusCode}');
if (response.statusCode == 200) {
return TokenModel.fromJson(json.decode(response.body));
} else if (response.statusCode == 401) {
throw const UnauthorizedException(message: 'Session expired');
} else {
throw ServerException(
message: 'Token refresh failed',
statusCode: response.statusCode,
);
}
} on SocketException {
throw const NetworkException();
}
}
}