Add local bearer authentication foundation

This commit is contained in:
Hamza Ayed
2026-10-03 00:19:21 +03:00
parent 1782ad1af0
commit 1c1f662850
8 changed files with 505 additions and 39 deletions
@@ -21,8 +21,8 @@ class ApiRepository {
ApiRepository({required String baseUrl}) : _baseUrl = baseUrl;
String _baseUrl;
String? _cachedUserId;
Future<String>? _loadingUserId;
String? _accessToken;
Future<String>? _loadingLocalSession;
http.Client? _activeRequestClient;
String get baseUrl => _baseUrl;
@@ -33,8 +33,11 @@ class ApiRepository {
String newId() => _uuid.v4();
void setBaseUrl(String value) =>
_baseUrl = value.replaceFirst(RegExp(r'/+$'), '');
void setBaseUrl(String value) {
_baseUrl = value.replaceFirst(RegExp(r'/+$'), '');
_accessToken = null;
_loadingLocalSession = null;
}
Future<String> getModelName() async {
final response = await http.get(Uri.parse('$_baseUrl/health'));
@@ -60,25 +63,75 @@ class ApiRepository {
}).toList();
}
Future<String> _userId() async {
final cached = _cachedUserId;
Future<String> _localSessionToken() async {
final cached = _accessToken;
if (cached != null) return cached;
return _loadingUserId ??= _loadLocalUserId();
final existing = _loadingLocalSession;
if (existing != null) return existing;
final pending = _loadLocalSessionToken();
_loadingLocalSession = pending;
try {
return await pending;
} finally {
_loadingLocalSession = null;
}
}
Future<String> _loadLocalUserId() async {
final response = await http.get(Uri.parse('$_baseUrl/v1/local-user'));
Future<String> _loadLocalSessionToken() async {
final response = await http.post(
Uri.parse('$_baseUrl/v1/auth/local-session'),
headers: const {'Content-Type': 'application/json'},
body: '{}',
);
_checkStatus(response);
final data = jsonDecode(response.body) as Map<String, dynamic>;
_cachedUserId = data['user_id'] as String;
return _cachedUserId!;
_accessToken = data['access_token'] as String;
return _accessToken!;
}
Future<Map<String, String>> _userHeaders() async => {
'Content-Type': 'application/json',
'X-User-ID': await _userId(),
'Authorization': 'Bearer ${await _localSessionToken()}',
};
Future<Map<String, dynamic>> registerAccount({
required String email,
required String password,
}) => _authenticateAccount('register', email, password);
Future<Map<String, dynamic>> login({
required String email,
required String password,
}) => _authenticateAccount('login', email, password);
Future<Map<String, dynamic>> _authenticateAccount(
String action,
String email,
String password,
) async {
final response = await http.post(
Uri.parse('$_baseUrl/v1/auth/$action'),
headers: const {'Content-Type': 'application/json'},
body: jsonEncode({'email': email, 'password': password}),
);
_checkStatus(response);
final data = jsonDecode(response.body) as Map<String, dynamic>;
_accessToken = data['access_token'] as String;
return data;
}
Future<void> logout() async {
final token = _accessToken;
if (token == null) return;
final response = await http.post(
Uri.parse('$_baseUrl/v1/auth/logout'),
headers: {'Authorization': 'Bearer $token'},
);
_checkStatus(response);
_accessToken = null;
_loadingLocalSession = null;
}
void _checkStatus(http.Response response) {
if (response.statusCode < 200 || response.statusCode >= 300) {
throw Exception(_apiError(response));