diff --git a/lib/app/features/auth/views/components/identity_key_name_input/identity_key_name_input.dart b/lib/app/features/auth/views/components/identity_key_name_input/identity_key_name_input.dart index 0162cb0fc..5e4f4a5ad 100644 --- a/lib/app/features/auth/views/components/identity_key_name_input/identity_key_name_input.dart +++ b/lib/app/features/auth/views/components/identity_key_name_input/identity_key_name_input.dart @@ -14,10 +14,11 @@ import 'package:ice/generated/assets.gen.dart'; class IdentityKeyNameInput extends HookWidget { const IdentityKeyNameInput({ this.textInputAction = TextInputAction.done, - super.key, this.controller, this.scrollPadding, this.notShowInfoIcon = false, + this.errorText, + super.key, }); final TextEditingController? controller; @@ -27,6 +28,8 @@ class IdentityKeyNameInput extends HookWidget { final EdgeInsets? scrollPadding; final bool notShowInfoIcon; + final String? errorText; + @override Widget build(BuildContext context) { final hideKeyboardAndCallOnce = useHideKeyboardAndCallOnce(); @@ -67,6 +70,7 @@ class IdentityKeyNameInput extends HookWidget { }, textInputAction: textInputAction, scrollPadding: scrollPadding ?? EdgeInsets.only(bottom: 100.0.s), + errorText: errorText, ); } } diff --git a/lib/app/features/auth/views/pages/get_started/login_form.dart b/lib/app/features/auth/views/pages/get_started/login_form.dart index 5d82b5f80..e566d9840 100644 --- a/lib/app/features/auth/views/pages/get_started/login_form.dart +++ b/lib/app/features/auth/views/pages/get_started/login_form.dart @@ -29,6 +29,7 @@ class LoginForm extends HookConsumerWidget { child: Column( children: [ IdentityKeyNameInput( + errorText: loginActionState.error?.toString(), controller: identityKeyNameController, scrollPadding: EdgeInsets.only(bottom: 190.0.s), ), diff --git a/lib/app/features/auth/views/pages/sign_up_passkey/sign_up_passkey_form.dart b/lib/app/features/auth/views/pages/sign_up_passkey/sign_up_passkey_form.dart index 249faa96b..10db05cfd 100644 --- a/lib/app/features/auth/views/pages/sign_up_passkey/sign_up_passkey_form.dart +++ b/lib/app/features/auth/views/pages/sign_up_passkey/sign_up_passkey_form.dart @@ -27,7 +27,10 @@ class SignUpPasskeyForm extends HookConsumerWidget { key: formKey.value, child: Column( children: [ - IdentityKeyNameInput(controller: identityKeyNameController), + IdentityKeyNameInput( + errorText: registerActionState.error?.toString(), + controller: identityKeyNameController, + ), SizedBox(height: 16.0.s), Button( disabled: registerActionState.isLoading, diff --git a/packages/ion_identity_client/lib/src/auth/data_sources/login_data_source.dart b/packages/ion_identity_client/lib/src/auth/data_sources/login_data_source.dart index 88c93f815..9ab5ab642 100644 --- a/packages/ion_identity_client/lib/src/auth/data_sources/login_data_source.dart +++ b/packages/ion_identity_client/lib/src/auth/data_sources/login_data_source.dart @@ -1,5 +1,6 @@ // SPDX-License-Identifier: ice License 1.0 +import 'package:dio/dio.dart'; import 'package:ion_identity_client/src/auth/dtos/dtos.dart'; import 'package:ion_identity_client/src/auth/result_types/login_user_result.dart'; import 'package:ion_identity_client/src/core/network/network.dart'; @@ -28,7 +29,11 @@ class LoginDataSource { decoder: UserActionChallenge.fromJson, ) .mapLeft( - (l) => const UnknownLoginUserFailure(), + (l) => switch (l) { + RequestExecutionNetworkFailure(:final error) => + _handleRequestExecutionNetworkFailure(error), + _ => UnknownLoginUserFailure(l), + }, ); } @@ -48,7 +53,24 @@ class LoginDataSource { decoder: Authentication.fromJson, ) .mapLeft( - (l) => const UnknownLoginUserFailure(), + (l) => switch (l) { + RequestExecutionNetworkFailure(:final error) => + _handleRequestExecutionNetworkFailure(error), + _ => UnknownLoginUserFailure(l), + }, ); } + + LoginUserFailure _handleRequestExecutionNetworkFailure(Object error) { + switch (error) { + case DioException(:final response) when response?.statusCode == 400: + return const AccountDeactivatedLoginUserFailure(); + case DioException(:final response) when response?.statusCode == 401: + return const NoCredentialsLoginUserFailure(); + case DioException(:final response) when response?.statusCode == 403: + return const InvalidCodeLoginUserFailure(); + default: + return const UnknownLoginUserFailure(); + } + } } diff --git a/packages/ion_identity_client/lib/src/auth/data_sources/register_data_source.dart b/packages/ion_identity_client/lib/src/auth/data_sources/register_data_source.dart index ba3b41d9c..75a13c198 100644 --- a/packages/ion_identity_client/lib/src/auth/data_sources/register_data_source.dart +++ b/packages/ion_identity_client/lib/src/auth/data_sources/register_data_source.dart @@ -1,5 +1,6 @@ // SPDX-License-Identifier: ice License 1.0 +import 'package:dio/dio.dart'; import 'package:ion_identity_client/src/auth/dtos/dtos.dart'; import 'package:ion_identity_client/src/auth/result_types/register_user_result.dart'; import 'package:ion_identity_client/src/core/network/network.dart'; @@ -29,8 +30,9 @@ class RegisterDataSource { ) .mapLeft( (l) => switch (l) { - ResponseFormatNetworkFailure() => UserAlreadyExistsRegisterUserFailure(), - _ => const UnknownRegisterUserFailure(), + RequestExecutionNetworkFailure(:final error) => + _handleRequestExecutionNetworkFailure(error), + _ => UnknownRegisterUserFailure(l), }, ); } @@ -39,19 +41,30 @@ class RegisterDataSource { required Fido2Attestation attestation, required String temporaryAuthenticationToken, }) { - return networkClient.post( - registerCompletePath, - data: SignedChallenge(firstFactorCredential: attestation).toJson(), - decoder: RegistrationCompleteResponse.fromJson, - headers: { - ...RequestHeaders.getAuthorizationHeader(token: temporaryAuthenticationToken), - }, - ).mapLeft( - // TODO: find a complete list of user registration failures - (l) => switch (l) { - ResponseFormatNetworkFailure() => UserAlreadyExistsRegisterUserFailure(), - _ => const UnknownRegisterUserFailure(), - }, - ); + return networkClient + .post( + registerCompletePath, + data: SignedChallenge(firstFactorCredential: attestation).toJson(), + decoder: RegistrationCompleteResponse.fromJson, + headers: RequestHeaders.getAuthorizationHeader(token: temporaryAuthenticationToken), + ) + .mapLeft( + (l) => switch (l) { + RequestExecutionNetworkFailure(:final error) => + _handleRequestExecutionNetworkFailure(error), + _ => UnknownRegisterUserFailure(l), + }, + ); + } + + RegisterUserFailure _handleRequestExecutionNetworkFailure(Object error) { + switch (error) { + case DioException(:final response) when response?.statusCode == 400: + return RegistrationCodeExpiredRegisterUserFailure(); + case DioException(:final response) when response?.statusCode == 401: + return UserAlreadyExistsRegisterUserFailure(); + default: + return const UnknownRegisterUserFailure(); + } } } diff --git a/packages/ion_identity_client/lib/src/auth/result_types/login_user_result.dart b/packages/ion_identity_client/lib/src/auth/result_types/login_user_result.dart index a9a55cffe..2dea8aba2 100644 --- a/packages/ion_identity_client/lib/src/auth/result_types/login_user_result.dart +++ b/packages/ion_identity_client/lib/src/auth/result_types/login_user_result.dart @@ -12,6 +12,9 @@ sealed class LoginUserFailure extends LoginUserResult { final class PasskeyNotAvailableLoginUserFailure extends LoginUserFailure { const PasskeyNotAvailableLoginUserFailure(); + + @override + String toString() => 'Passkey not available'; } final class PasskeyValidationLoginUserFailure extends LoginUserFailure { @@ -24,7 +27,28 @@ final class PasskeyValidationLoginUserFailure extends LoginUserFailure { final StackTrace? stackTrace; @override - String toString() => 'PasskeyValidationLoginUserFailure(error: $error, stackTrace: $stackTrace)'; + String toString() => 'Passkey validation failed: $error'; +} + +final class InvalidCodeLoginUserFailure extends LoginUserFailure { + const InvalidCodeLoginUserFailure(); + + @override + String toString() => 'Invalid code'; +} + +final class NoCredentialsLoginUserFailure extends LoginUserFailure { + const NoCredentialsLoginUserFailure(); + + @override + String toString() => 'No credentials found for this user'; +} + +final class AccountDeactivatedLoginUserFailure extends LoginUserFailure { + const AccountDeactivatedLoginUserFailure(); + + @override + String toString() => 'Account has been deactivated'; } final class UnknownLoginUserFailure extends LoginUserFailure { diff --git a/packages/ion_identity_client/lib/src/auth/result_types/register_user_result.dart b/packages/ion_identity_client/lib/src/auth/result_types/register_user_result.dart index 501367423..f0a35e6f4 100644 --- a/packages/ion_identity_client/lib/src/auth/result_types/register_user_result.dart +++ b/packages/ion_identity_client/lib/src/auth/result_types/register_user_result.dart @@ -10,10 +10,21 @@ sealed class RegisterUserFailure extends RegisterUserResult { const RegisterUserFailure(); } -final class UserAlreadyExistsRegisterUserFailure extends RegisterUserFailure {} +final class UserAlreadyExistsRegisterUserFailure extends RegisterUserFailure { + @override + String toString() => 'User already exists'; +} + +final class RegistrationCodeExpiredRegisterUserFailure extends RegisterUserFailure { + @override + String toString() => 'Registration code expired'; +} final class PasskeyNotAvailableRegisterUserFailure extends RegisterUserFailure { const PasskeyNotAvailableRegisterUserFailure(); + + @override + String toString() => 'Passkey not available'; } final class PasskeyValidationRegisterUserFailure extends RegisterUserFailure { @@ -26,8 +37,7 @@ final class PasskeyValidationRegisterUserFailure extends RegisterUserFailure { final StackTrace? stackTrace; @override - String toString() => - 'PasskeyValidationRegisterUserFailure(error: $error, stackTrace: $stackTrace)'; + String toString() => 'Passkey validation failed: $error'; } final class UnknownRegisterUserFailure extends RegisterUserFailure { @@ -40,5 +50,5 @@ final class UnknownRegisterUserFailure extends RegisterUserFailure { final StackTrace? stackTrace; @override - String toString() => 'UnknownRegisterUserFailure(error: $error, stackTrace: $stackTrace)'; + String toString() => 'Unknown error: $error'; }