Frontend first draft
This commit is contained in:
89
frontend/pshared/lib/service/authorization/service.dart
Normal file
89
frontend/pshared/lib/service/authorization/service.dart
Normal file
@@ -0,0 +1,89 @@
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/api/errors/upload_failed.dart';
|
||||
import 'package:pshared/api/requests/login.dart';
|
||||
import 'package:pshared/api/responses/account.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
import 'package:pshared/service/authorization/token.dart';
|
||||
import 'package:pshared/service/device_id.dart';
|
||||
import 'package:pshared/utils/http/requests.dart' as httpr;
|
||||
|
||||
|
||||
class AuthorizationService {
|
||||
static final _logger = Logger('service.authorization');
|
||||
|
||||
static Future<void> _updateAccessToken(AccountResponse response) async {
|
||||
await AuthorizationStorage.updateToken(response.accessToken);
|
||||
}
|
||||
|
||||
static Future<void> _updateTokens(LoginResponse response) async {
|
||||
await _updateAccessToken(response);
|
||||
return AuthorizationStorage.updateRefreshToken(response.refreshToken);
|
||||
}
|
||||
|
||||
static Future<LoginResponse> _completeLogin(Map<String, dynamic> response) async {
|
||||
final LoginResponse lr = LoginResponse.fromJson(response);
|
||||
await _updateTokens(lr);
|
||||
return lr;
|
||||
}
|
||||
|
||||
static Future<Account> login(String service, String email, String password, String locale) async {
|
||||
_logger.fine('Logging in $email with $locale locale');
|
||||
final deviceId = await DeviceIdManager.getDeviceId();
|
||||
final response = await httpr.getPOSTResponse(
|
||||
service,
|
||||
'/login',
|
||||
LoginRequest(
|
||||
login: email.toLowerCase(),
|
||||
password: password,
|
||||
locale: locale,
|
||||
deviceId: deviceId,
|
||||
clientId: Constants.clientId,
|
||||
).toJson());
|
||||
|
||||
return (await _completeLogin(response)).account.toDomain();
|
||||
}
|
||||
|
||||
static Future<Account> restore() async {
|
||||
return (await TokenService.rotateRefreshToken()).account.toDomain();
|
||||
}
|
||||
|
||||
static Future<void> logout() async {
|
||||
return AuthorizationStorage.removeTokens();
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> _authenticatedRequest(
|
||||
String service,
|
||||
String url,
|
||||
Future<Map<String, dynamic>> Function(String, String, Map<String, dynamic>, {String? authToken}) requestType,
|
||||
{Map<String, dynamic>? body}) async {
|
||||
final accessToken = await TokenService.getAccessToken();
|
||||
return requestType(service, url, body ?? {}, authToken: accessToken);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getPOSTResponse(String service, String url, Map<String, dynamic> body) async => _authenticatedRequest(service, url, httpr.getPOSTResponse, body: body);
|
||||
|
||||
static Future<Map<String, dynamic>> getGETResponse(String service, String url) async {
|
||||
final accessToken = await TokenService.getAccessToken();
|
||||
return httpr.getGETResponse(service, url, authToken: accessToken);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getPUTResponse(String service, String url, Map<String, dynamic> body) async => _authenticatedRequest(service, url, httpr.getPUTResponse, body: body);
|
||||
|
||||
static Future<Map<String, dynamic>> getPATCHResponse(String service, String url, Map<String, dynamic> body) async => _authenticatedRequest(service, url, httpr.getPATCHResponse, body: body);
|
||||
|
||||
static Future<Map<String, dynamic>> getDELETEResponse(String service, String url, Map<String, dynamic> body) async => _authenticatedRequest(service, url, httpr.getDELETEResponse, body: body);
|
||||
|
||||
static Future<String> getFileUploadResponseAuth(String service, String url, String fileName, String fileType, String mediaType, List<int> bytes) async {
|
||||
final accessToken = await TokenService.getAccessToken();
|
||||
final res = await httpr.getFileUploadResponse(service, url, fileName, fileType, mediaType, bytes, authToken: accessToken);
|
||||
if (res == null) {
|
||||
throw ErrorUploadFailed();
|
||||
}
|
||||
return res.url;
|
||||
}
|
||||
}
|
||||
53
frontend/pshared/lib/service/authorization/storage.dart
Normal file
53
frontend/pshared/lib/service/authorization/storage.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/api/errors/unauthorized.dart';
|
||||
import 'package:pshared/api/responses/token.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
import 'package:pshared/service/secure_storage.dart';
|
||||
|
||||
|
||||
class AuthorizationStorage {
|
||||
static final _logger = Logger('service.authorization.storage');
|
||||
|
||||
static Future<TokenData> _getTokenData(String tokenKey) async {
|
||||
_logger.fine('Getting token data');
|
||||
final String? tokenJson = await SecureStorageService.get(tokenKey);
|
||||
if (tokenJson == null || tokenJson.isEmpty) {
|
||||
throw ErrorUnauthorized();
|
||||
}
|
||||
return TokenData.fromJson(jsonDecode(tokenJson));
|
||||
}
|
||||
|
||||
static Future<TokenData> getAccessToken() async {
|
||||
_logger.fine('Getting access token');
|
||||
return _getTokenData(Constants.accessTokenStorageKey);
|
||||
}
|
||||
|
||||
static Future<TokenData> getRefreshToken() async {
|
||||
_logger.fine('Getting refresh token');
|
||||
return _getTokenData(Constants.refreshTokenStorageKey);
|
||||
}
|
||||
|
||||
static Future<void> updateToken(TokenData tokenData) async {
|
||||
_logger.fine('Storing access token...');
|
||||
final tokenJson = jsonEncode(tokenData.toJson());
|
||||
await SecureStorageService.set(Constants.accessTokenStorageKey, tokenJson);
|
||||
}
|
||||
|
||||
static Future<void> updateRefreshToken(TokenData tokenData) async {
|
||||
_logger.fine('Storing refresh token...');
|
||||
final refreshTokenJson = jsonEncode(tokenData.toJson());
|
||||
await SecureStorageService.set(Constants.refreshTokenStorageKey, refreshTokenJson);
|
||||
}
|
||||
|
||||
static Future<void> removeTokens() {
|
||||
return Future.wait([
|
||||
SecureStorageService.delete(Constants.refreshTokenStorageKey),
|
||||
SecureStorageService.delete(Constants.accessTokenStorageKey),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
frontend/pshared/lib/service/authorization/token.dart
Normal file
85
frontend/pshared/lib/service/authorization/token.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/api/errors/unauthorized.dart';
|
||||
import 'package:pshared/api/requests/tokens/access_refresh.dart';
|
||||
import 'package:pshared/api/requests/tokens/refresh_rotate.dart';
|
||||
import 'package:pshared/api/responses/account.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/api/responses/token.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
import 'package:pshared/service/device_id.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/http/requests.dart';
|
||||
|
||||
|
||||
class TokenService {
|
||||
static final _logger = Logger('service.authorization.token');
|
||||
static const String _objectType = Services.account;
|
||||
|
||||
static bool _isTokenExpiringSoon(TokenData token, Duration duration) {
|
||||
return token.expiration.isBefore(DateTime.now().add(duration));
|
||||
}
|
||||
|
||||
static Future<String> getAccessToken() async {
|
||||
TokenData token = await AuthorizationStorage.getAccessToken();
|
||||
if (_isTokenExpiringSoon(token, const Duration(hours: 4))) {
|
||||
token = (await _refreshAccessToken()).accessToken;
|
||||
}
|
||||
return token.token;
|
||||
}
|
||||
|
||||
static Future<void> _updateTokens(LoginResponse response) async {
|
||||
await AuthorizationStorage.updateToken(response.accessToken);
|
||||
await AuthorizationStorage.updateRefreshToken(response.refreshToken);
|
||||
}
|
||||
|
||||
static Future<AccountResponse> _refreshAccessToken() async {
|
||||
_logger.fine('Refreshing access token...');
|
||||
final deviceId = await DeviceIdManager.getDeviceId();
|
||||
final refresh = await AuthorizationStorage.getRefreshToken();
|
||||
|
||||
if (_isTokenExpiringSoon(refresh, const Duration(days: 7))) {
|
||||
return await rotateRefreshToken();
|
||||
}
|
||||
|
||||
final response = await getPOSTResponse(
|
||||
_objectType,
|
||||
'/refresh',
|
||||
AccessTokenRefreshRequest(
|
||||
deviceId: deviceId,
|
||||
clientId: Constants.clientId,
|
||||
token: refresh.token,
|
||||
).toJson(),
|
||||
);
|
||||
|
||||
final accountResp = AccountResponse.fromJson(response);
|
||||
await AuthorizationStorage.updateToken(accountResp.accessToken);
|
||||
return accountResp;
|
||||
}
|
||||
|
||||
static Future<LoginResponse> rotateRefreshToken() async {
|
||||
_logger.fine('Rotating refresh token...');
|
||||
final refresh = await AuthorizationStorage.getRefreshToken();
|
||||
|
||||
if (refresh.expiration.isBefore(DateTime.now())) throw ErrorUnauthorized();
|
||||
|
||||
final deviceId = await DeviceIdManager.getDeviceId();
|
||||
final response = await getPOSTResponse(
|
||||
_objectType,
|
||||
'/rotate',
|
||||
RotateRefreshTokenRequest(
|
||||
token: refresh.token,
|
||||
clientId: Constants.clientId,
|
||||
deviceId: deviceId,
|
||||
).toJson(),
|
||||
);
|
||||
|
||||
final loginResponse = LoginResponse.fromJson(response);
|
||||
await _updateTokens(loginResponse);
|
||||
return loginResponse;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user