New code verification service
Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
This commit is contained in:
26
frontend/pshared/lib/api/responses/login_pending.dart
Normal file
26
frontend/pshared/lib/api/responses/login_pending.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'package:pshared/api/responses/account.dart';
|
||||
import 'package:pshared/api/responses/token.dart';
|
||||
|
||||
part 'login_pending.g.dart';
|
||||
|
||||
|
||||
@JsonSerializable(explicitToJson: true)
|
||||
class PendingLoginResponse {
|
||||
final AccountResponse account;
|
||||
final TokenData pendingToken;
|
||||
final String destination;
|
||||
final int ttlSeconds;
|
||||
|
||||
const PendingLoginResponse({
|
||||
required this.account,
|
||||
required this.pendingToken,
|
||||
required this.destination,
|
||||
required this.ttlSeconds,
|
||||
});
|
||||
|
||||
factory PendingLoginResponse.fromJson(Map<String, dynamic> json) => _$PendingLoginResponseFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$PendingLoginResponseToJson(this);
|
||||
}
|
||||
17
frontend/pshared/lib/models/auth/login_outcome.dart
Normal file
17
frontend/pshared/lib/models/auth/login_outcome.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
|
||||
|
||||
class LoginOutcome {
|
||||
final Account? account;
|
||||
final PendingLogin? pending;
|
||||
|
||||
const LoginOutcome._({this.account, this.pending});
|
||||
|
||||
factory LoginOutcome.completed(Account account) => LoginOutcome._(account: account);
|
||||
|
||||
factory LoginOutcome.pending(PendingLogin pending) => LoginOutcome._(pending: pending);
|
||||
|
||||
bool get isPending => pending != null;
|
||||
bool get isCompleted => account != null;
|
||||
}
|
||||
33
frontend/pshared/lib/models/auth/pending_login.dart
Normal file
33
frontend/pshared/lib/models/auth/pending_login.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:pshared/api/responses/login_pending.dart';
|
||||
import 'package:pshared/api/responses/token.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/session_identifier.dart';
|
||||
|
||||
|
||||
class PendingLogin {
|
||||
final Account account;
|
||||
final TokenData pendingToken;
|
||||
final String destination;
|
||||
final int ttlSeconds;
|
||||
final SessionIdentifier session;
|
||||
|
||||
const PendingLogin({
|
||||
required this.account,
|
||||
required this.pendingToken,
|
||||
required this.destination,
|
||||
required this.ttlSeconds,
|
||||
required this.session,
|
||||
});
|
||||
|
||||
factory PendingLogin.fromResponse(
|
||||
PendingLoginResponse response, {
|
||||
required SessionIdentifier session,
|
||||
}) => PendingLogin(
|
||||
account: response.account.account.toDomain(),
|
||||
pendingToken: response.pendingToken,
|
||||
destination: response.destination,
|
||||
ttlSeconds: response.ttlSeconds,
|
||||
session: session,
|
||||
);
|
||||
}
|
||||
18
frontend/pshared/lib/models/session_identifier.dart
Normal file
18
frontend/pshared/lib/models/session_identifier.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'session_identifier.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class SessionIdentifier {
|
||||
final String clientId;
|
||||
final String deviceId;
|
||||
|
||||
const SessionIdentifier({
|
||||
required this.clientId,
|
||||
required this.deviceId,
|
||||
});
|
||||
|
||||
factory SessionIdentifier.fromJson(Map<String, dynamic> json) => _$SessionIdentifierFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SessionIdentifierToJson(this);
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import 'package:pshared/api/requests/signup.dart';
|
||||
import 'package:pshared/api/requests/login_data.dart';
|
||||
import 'package:pshared/config/constants.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/auth/login_outcome.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/storable.dart';
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
@@ -23,8 +25,10 @@ class AccountProvider extends ChangeNotifier {
|
||||
Resource<Account?> _resource = Resource(data: null);
|
||||
Resource<Account?> get resource => _resource;
|
||||
late LocaleProvider _localeProvider;
|
||||
PendingLogin? _pendingLogin;
|
||||
|
||||
Account? get account => _resource.data;
|
||||
PendingLogin? get pendingLogin => _pendingLogin;
|
||||
bool get isLoggedIn => account != null;
|
||||
bool get isLoading => _resource.isLoading;
|
||||
Object? get error => _resource.error;
|
||||
@@ -57,27 +61,38 @@ class AccountProvider extends ChangeNotifier {
|
||||
|
||||
void _pickupLocale(String locale) => _localeProvider.setLocale(Locale(locale));
|
||||
|
||||
Future<Account> login({
|
||||
Future<LoginOutcome> login({
|
||||
required String email,
|
||||
required String password,
|
||||
required String locale,
|
||||
}) async {
|
||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||
try {
|
||||
final acc = await AccountService.login(LoginData.build(
|
||||
final outcome = await AccountService.login(LoginData.build(
|
||||
login: email,
|
||||
password: password,
|
||||
locale: locale,
|
||||
));
|
||||
_setResource(Resource(data: acc, isLoading: false));
|
||||
_pickupLocale(acc.locale);
|
||||
return acc;
|
||||
if (outcome.account != null) {
|
||||
_setResource(Resource(data: outcome.account, isLoading: false));
|
||||
_pickupLocale(outcome.account!.locale);
|
||||
} else {
|
||||
_pendingLogin = outcome.pending;
|
||||
_setResource(_resource.copyWith(isLoading: false));
|
||||
}
|
||||
return outcome;
|
||||
} catch (e) {
|
||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
void completePendingLogin(Account account) {
|
||||
_pendingLogin = null;
|
||||
_setResource(Resource(data: account, isLoading: false, error: null));
|
||||
_pickupLocale(account.locale);
|
||||
}
|
||||
|
||||
Future<bool> isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
|
||||
|
||||
Future<Account?> restore() async {
|
||||
|
||||
@@ -8,9 +8,13 @@ import 'package:pshared/api/requests/login_data.dart';
|
||||
import 'package:pshared/api/requests/password/change.dart';
|
||||
import 'package:pshared/api/requests/password/forgot.dart';
|
||||
import 'package:pshared/api/requests/password/reset.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/auth/login_outcome.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
import 'package:pshared/service/files.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/http/requests.dart';
|
||||
@@ -20,11 +24,46 @@ class AccountService {
|
||||
static final _logger = Logger('service.account');
|
||||
static const String _objectType = Services.account;
|
||||
|
||||
static Future<Account> login(LoginData login) async {
|
||||
static Future<LoginOutcome> login(LoginData login) async {
|
||||
_logger.fine('Logging in');
|
||||
return AuthorizationService.login(_objectType, login);
|
||||
}
|
||||
|
||||
static Future<void> resendLoginCode(PendingLogin pending, {String? destination}) async {
|
||||
await getPOSTResponse(
|
||||
_objectType,
|
||||
'confirmations/resend',
|
||||
{
|
||||
'target': 'login',
|
||||
if (destination != null) 'destination': destination,
|
||||
},
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Account> confirmLoginCode({
|
||||
required PendingLogin pending,
|
||||
required String code,
|
||||
String? destination,
|
||||
}) async {
|
||||
final response = await getPOSTResponse(
|
||||
_objectType,
|
||||
'confirmations/verify',
|
||||
{
|
||||
'target': 'login',
|
||||
'code': code,
|
||||
if (destination != null) 'destination': destination,
|
||||
'sessionIdentifier': pending.session.toJson(),
|
||||
},
|
||||
authToken: pending.pendingToken.token,
|
||||
);
|
||||
|
||||
final loginResponse = LoginResponse.fromJson(response);
|
||||
await AuthorizationStorage.updateToken(loginResponse.accessToken);
|
||||
await AuthorizationStorage.updateRefreshToken(loginResponse.refreshToken);
|
||||
return loginResponse.account.toDomain();
|
||||
}
|
||||
|
||||
static Future<Account> restore() async {
|
||||
return AuthorizationService.restore();
|
||||
}
|
||||
|
||||
@@ -5,9 +5,13 @@ import 'package:pshared/api/requests/login.dart';
|
||||
import 'package:pshared/api/requests/login_data.dart';
|
||||
import 'package:pshared/api/responses/account.dart';
|
||||
import 'package:pshared/api/responses/login.dart';
|
||||
import 'package:pshared/api/responses/login_pending.dart';
|
||||
import 'package:pshared/config/web.dart';
|
||||
import 'package:pshared/data/mapper/account/account.dart';
|
||||
import 'package:pshared/models/account/account.dart';
|
||||
import 'package:pshared/models/auth/login_outcome.dart';
|
||||
import 'package:pshared/models/auth/pending_login.dart';
|
||||
import 'package:pshared/models/session_identifier.dart';
|
||||
import 'package:pshared/service/authorization/circuit_breaker.dart';
|
||||
import 'package:pshared/service/authorization/retry_helper.dart';
|
||||
import 'package:pshared/service/authorization/storage.dart';
|
||||
@@ -22,7 +26,7 @@ import 'package:pshared/utils/http/requests.dart' as httpr;
|
||||
class AuthorizationService {
|
||||
static final _logger = Logger('service.authorization.auth_service');
|
||||
|
||||
static Future<Account> login(String service, LoginData login) async {
|
||||
static Future<LoginOutcome> login(String service, LoginData login) async {
|
||||
_logger.fine('Logging in ${login.login} with ${login.locale} locale');
|
||||
final deviceId = await DeviceIdManager.getDeviceId();
|
||||
final response = await httpr.getPOSTResponse(
|
||||
@@ -31,7 +35,17 @@ class AuthorizationService {
|
||||
LoginRequest(login: login, deviceId: deviceId, clientId: Constants.clientId).toJson(),
|
||||
);
|
||||
|
||||
return (await _completeLogin(response)).account.toDomain();
|
||||
if (response.containsKey('refreshToken')) {
|
||||
return LoginOutcome.completed((await completeLogin(response)).account.toDomain());
|
||||
}
|
||||
if (response.containsKey('pendingToken')) {
|
||||
final pending = PendingLogin.fromResponse(
|
||||
PendingLoginResponse.fromJson(response),
|
||||
session: SessionIdentifier(clientId: Constants.clientId, deviceId: deviceId),
|
||||
);
|
||||
return LoginOutcome.pending(pending);
|
||||
}
|
||||
throw AuthenticationFailedException('Unexpected login response', Exception(response.toString()));
|
||||
}
|
||||
|
||||
static Future<void> _updateAccessToken(AccountResponse response) async {
|
||||
@@ -49,6 +63,8 @@ class AuthorizationService {
|
||||
return lr;
|
||||
}
|
||||
|
||||
static Future<LoginResponse> completeLogin(Map<String, dynamic> response) => _completeLogin(response);
|
||||
|
||||
static Future<Account> restore() async {
|
||||
return (await TokenService.refreshAccessToken()).account.toDomain();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user