Add local bearer authentication foundation
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user