From fad2c8792c5d72dcc3f8f0520456e9fe9cd75da1 Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Fri, 17 Apr 2026 11:12:14 +0200 Subject: [PATCH] refactor(recover_password): type API errors and hide email enumeration Map PUT /auth/reset-password and PUT /auth/recovery-password failures into LegacyRecoverPasswordErrorEvent. Reset-password now treats 404 (email not found) as success and surfaces a generic sent-if-exists flow, closing an account enumeration vector. Recovery-password differentiates 401 (tokenExpired), 404 (tokenNotFound), 403+Property (invalidField) from 403 without Property (weakPassword). The view state splits validation vs API errors with a displayErrorKey extension for the inline error text. --- .../legacy_recover_password_error_event.dart | 54 ++++++++++++++++++ .../new_password/new_password_screen.dart | 5 +- .../request_recovery_screen.dart | 5 +- .../state/recover_password_view_model.dart | 57 ++++++++++++++----- .../state/recover_password_view_state.dart | 30 +++++++++- .../recover_password_view_state.freezed.dart | 49 ++++++++-------- packages/sf_localizations/assets/l10n/de.json | 5 ++ packages/sf_localizations/assets/l10n/en.json | 5 ++ packages/sf_localizations/assets/l10n/es.json | 5 ++ packages/sf_localizations/assets/l10n/fr.json | 5 ++ packages/sf_localizations/assets/l10n/it.json | 5 ++ packages/sf_localizations/assets/l10n/pt.json | 5 ++ .../lib/src/generated/i18n.dart | 10 ++++ 13 files changed, 198 insertions(+), 42 deletions(-) create mode 100644 modules/legacy/modules/legacy_auth/lib/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart new file mode 100644 index 00000000..e16b4f0d --- /dev/null +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart @@ -0,0 +1,54 @@ +import 'package:sf_infrastructure/sf_infrastructure.dart'; + +enum LegacyRecoverPasswordErrorEvent { + invalidEmail, + invalidField, + weakPassword, + tokenExpired, + tokenNotFound, + tooManyAttempts, + network, + generic, +} + +/// For POST /auth/reset-password. +/// +/// Returns `null` when the backend responds with 404 (email not found) to +/// hide account enumeration — callers should treat this as success and show +/// a generic "if the email exists, we sent a link" message. +LegacyRecoverPasswordErrorEvent? mapResetPasswordError(Object error) { + if (error is! ApiException) return LegacyRecoverPasswordErrorEvent.generic; + if (error.isNetworkError) return LegacyRecoverPasswordErrorEvent.network; + + switch (error.statusCode) { + case 404: + return null; + case 403: + return LegacyRecoverPasswordErrorEvent.invalidEmail; + case 429: + return LegacyRecoverPasswordErrorEvent.tooManyAttempts; + default: + return LegacyRecoverPasswordErrorEvent.generic; + } +} + +LegacyRecoverPasswordErrorEvent mapRecoveryPasswordError(Object error) { + if (error is! ApiException) return LegacyRecoverPasswordErrorEvent.generic; + if (error.isNetworkError) return LegacyRecoverPasswordErrorEvent.network; + + switch (error.statusCode) { + case 401: + return LegacyRecoverPasswordErrorEvent.tokenExpired; + case 404: + return LegacyRecoverPasswordErrorEvent.tokenNotFound; + case 403: + if (error.message.contains('Property')) { + return LegacyRecoverPasswordErrorEvent.invalidField; + } + return LegacyRecoverPasswordErrorEvent.weakPassword; + case 429: + return LegacyRecoverPasswordErrorEvent.tooManyAttempts; + default: + return LegacyRecoverPasswordErrorEvent.generic; + } +} diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/new_password/new_password_screen.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/new_password/new_password_screen.dart index e7674f99..a48ae87d 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/new_password/new_password_screen.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/new_password/new_password_screen.dart @@ -1,4 +1,5 @@ import 'package:legacy_auth/src/features/recover_password/presentation/state/recover_password_view_model.dart'; +import 'package:legacy_auth/src/features/recover_password/presentation/state/recover_password_view_state.dart'; import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -175,10 +176,10 @@ class LegacyNewPasswordScreen extends ConsumerWidget { ), ], ), - if (viewState.errorMessage.isNotEmpty) ...[ + if (viewState.displayErrorKey != null) ...[ SizedBox(height: 10), Text( - context.translate(viewState.errorMessage), + context.translate(viewState.displayErrorKey!), textAlign: TextAlign.center, style: const TextStyle( color: Color.fromRGBO(239, 17, 17, 1), diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/request_recovery/request_recovery_screen.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/request_recovery/request_recovery_screen.dart index 69e86f40..8979dba1 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/request_recovery/request_recovery_screen.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/request_recovery/request_recovery_screen.dart @@ -7,6 +7,7 @@ import 'package:sf_localizations/sf_localizations.dart'; import 'package:utils/utils.dart'; import '../state/recover_password_view_model.dart'; +import '../state/recover_password_view_state.dart'; class LegacyRequestRecoveryScreen extends ConsumerWidget { final NavigationContract navigationContract; @@ -58,9 +59,9 @@ class LegacyRequestRecoveryScreen extends ConsumerWidget { SizedBox( height: SizeUtils.getByScreen(small: 40, big: 40, xl: 28), ), - if (viewState.errorMessage.isNotEmpty) ...[ + if (viewState.displayErrorKey != null) ...[ Text( - context.translate(viewState.errorMessage), + context.translate(viewState.displayErrorKey!), textAlign: TextAlign.center, style: const TextStyle( color: Color.fromRGBO(239, 17, 17, 1), diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_model.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_model.dart index 644848a6..218f918f 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_model.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_model.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:legacy_auth/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart'; import 'package:legacy_auth/src/features/recover_password/domain/use_cases/recover_password_use_case.dart'; import 'package:legacy_auth/src/features/recover_password/presentation/state/recover_password_view_state.dart'; -import 'package:sf_localizations/sf_localizations.dart'; import 'package:sf_tracking/sf_tracking.dart'; import '../providers/recover_password_provider.dart'; @@ -46,7 +46,8 @@ class LegacyRecoverPasswordViewModel void _onEmailChanged() { state = state.copyWith( email: emailController.text, - errorMessage: '', + validationErrorKey: '', + apiErrorEvent: null, recoveryRequested: false, ); } @@ -57,7 +58,8 @@ class LegacyRecoverPasswordViewModel state = state.copyWith( password: raw, - errorMessage: '', + validationErrorKey: '', + apiErrorEvent: null, equalPasswords: equalPasswords, securityChecks: { 'min': raw.length >= 8, @@ -72,7 +74,8 @@ class LegacyRecoverPasswordViewModel final raw = repeatedPasswordController.text; state = state.copyWith( repeatedPassword: raw, - errorMessage: '', + validationErrorKey: '', + apiErrorEvent: null, equalPasswords: raw == passwordController.text, ); } @@ -81,17 +84,28 @@ class LegacyRecoverPasswordViewModel state = state.copyWith(passwordVisible: !state.passwordVisible); } + void clearApiError() { + if (state.apiErrorEvent != null) state = state.copyWith(apiErrorEvent: null); + } + + void clearValidationError() { + if (state.validationErrorKey.isNotEmpty) { + state = state.copyWith(validationErrorKey: ''); + } + } + Future requestRecovery() async { final email = state.email.trim(); if (email.isEmpty) { - state = state.copyWith(errorMessage: 'errorMessageContactIsEmpty'); + state = state.copyWith(validationErrorKey: 'errorMessageContactIsEmpty'); return; } state = state.copyWith( isLoading: true, - errorMessage: '', + validationErrorKey: '', + apiErrorEvent: null, recoveryRequested: false, ); @@ -110,9 +124,20 @@ class LegacyRecoverPasswordViewModel ); } catch (e) { if (!ref.mounted) return; + + final mappedError = mapResetPasswordError(e); + if (mappedError == null) { + unawaited(_tracking.legacyAuthPasswordResetEmailSent()); + state = state.copyWith( + isLoading: false, + recoveryRequested: true, + ); + return; + } + state = state.copyWith( isLoading: false, - errorMessage: I18n.errorGeneric, + apiErrorEvent: mappedError, recoveryRequested: false, passwordChanged: false, ); @@ -123,7 +148,7 @@ class LegacyRecoverPasswordViewModel if (!state.equalPasswords) { unawaited(_tracking.legacyAuthPasswordResetFailed('unequal_passwords')); state = state.copyWith( - errorMessage: 'errorMessageUnequalPasswords', + validationErrorKey: 'errorMessageUnequalPasswords', passwordChanged: false, ); return; @@ -132,7 +157,7 @@ class LegacyRecoverPasswordViewModel if (!state.securityChecks['min']!) { unawaited(_tracking.legacyAuthPasswordResetFailed('too_short')); state = state.copyWith( - errorMessage: 'errorMessagePasswordTooShort', + validationErrorKey: 'errorMessagePasswordTooShort', passwordChanged: false, ); return; @@ -141,7 +166,7 @@ class LegacyRecoverPasswordViewModel if (!state.securityChecks['capital']!) { unawaited(_tracking.legacyAuthPasswordResetFailed('no_capitals')); state = state.copyWith( - errorMessage: 'errorMessagePasswordNoCapitals', + validationErrorKey: 'errorMessagePasswordNoCapitals', passwordChanged: false, ); return; @@ -150,7 +175,7 @@ class LegacyRecoverPasswordViewModel if (!state.securityChecks['number']!) { unawaited(_tracking.legacyAuthPasswordResetFailed('no_numbers')); state = state.copyWith( - errorMessage: 'errorMessagePasswordNoNumbers', + validationErrorKey: 'errorMessagePasswordNoNumbers', passwordChanged: false, ); return; @@ -159,13 +184,17 @@ class LegacyRecoverPasswordViewModel if (!state.securityChecks['special']!) { unawaited(_tracking.legacyAuthPasswordResetFailed('no_special_chars')); state = state.copyWith( - errorMessage: 'errorMessagePasswordNoSpecialChars', + validationErrorKey: 'errorMessagePasswordNoSpecialChars', passwordChanged: false, ); return; } - state = state.copyWith(isLoading: true, passwordChanged: false); + state = state.copyWith( + isLoading: true, + apiErrorEvent: null, + passwordChanged: false, + ); try { await _recoverPasswordUseCase.recoverPassword( @@ -177,7 +206,7 @@ class LegacyRecoverPasswordViewModel } catch (error) { unawaited(_tracking.legacyAuthPasswordResetFailed(error.toString())); state = state.copyWith( - errorMessage: I18n.errorGeneric, + apiErrorEvent: mapRecoveryPasswordError(error), isLoading: false, passwordChanged: false, ); diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.dart index 42166c42..a1a60a43 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.dart @@ -1,4 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:legacy_auth/src/features/recover_password/domain/entities/legacy_recover_password_error_event.dart'; +import 'package:sf_localizations/sf_localizations.dart'; part 'recover_password_view_state.freezed.dart'; @@ -7,7 +9,8 @@ abstract class LegacyRecoverPasswordViewState with _$LegacyRecoverPasswordViewState { const factory LegacyRecoverPasswordViewState({ @Default('') String email, - @Default('') String errorMessage, + @Default('') String validationErrorKey, + LegacyRecoverPasswordErrorEvent? apiErrorEvent, @Default(false) bool isLoading, @Default(false) bool recoveryRequested, @Default(false) bool passwordChanged, @@ -25,3 +28,28 @@ abstract class LegacyRecoverPasswordViewState Map securityChecks, }) = _LegacyRecoverPasswordViewState; } + +extension LegacyRecoverPasswordViewStateDisplay + on LegacyRecoverPasswordViewState { + String? get displayErrorKey { + if (validationErrorKey.isNotEmpty) return validationErrorKey; + final event = apiErrorEvent; + if (event == null) return null; + return switch (event) { + LegacyRecoverPasswordErrorEvent.invalidEmail => + I18n.recoverPasswordErrorInvalidEmail, + LegacyRecoverPasswordErrorEvent.invalidField => + I18n.recoverPasswordErrorInvalidField, + LegacyRecoverPasswordErrorEvent.weakPassword => + I18n.recoverPasswordErrorWeakPassword, + LegacyRecoverPasswordErrorEvent.tokenExpired => + I18n.recoverPasswordErrorTokenExpired, + LegacyRecoverPasswordErrorEvent.tokenNotFound => + I18n.recoverPasswordErrorTokenNotFound, + LegacyRecoverPasswordErrorEvent.tooManyAttempts => + I18n.authErrorTooManyAttempts, + LegacyRecoverPasswordErrorEvent.network => I18n.authErrorNetwork, + LegacyRecoverPasswordErrorEvent.generic => I18n.errorGeneric, + }; + } +} diff --git a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.freezed.dart b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.freezed.dart index 0bed9ac8..d4f41b78 100644 --- a/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.freezed.dart +++ b/modules/legacy/modules/legacy_auth/lib/src/features/recover_password/presentation/state/recover_password_view_state.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$LegacyRecoverPasswordViewState { - String get email; String get errorMessage; bool get isLoading; bool get recoveryRequested; bool get passwordChanged; String get token; String get password; String get repeatedPassword; bool get passwordVisible; bool get equalPasswords; Map get securityChecks; + String get email; String get validationErrorKey; LegacyRecoverPasswordErrorEvent? get apiErrorEvent; bool get isLoading; bool get recoveryRequested; bool get passwordChanged; String get token; String get password; String get repeatedPassword; bool get passwordVisible; bool get equalPasswords; Map get securityChecks; /// Create a copy of LegacyRecoverPasswordViewState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $LegacyRecoverPasswordViewStateCopyWith get copy @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacyRecoverPasswordViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.recoveryRequested, recoveryRequested) || other.recoveryRequested == recoveryRequested)&&(identical(other.passwordChanged, passwordChanged) || other.passwordChanged == passwordChanged)&&(identical(other.token, token) || other.token == token)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatedPassword, repeatedPassword) || other.repeatedPassword == repeatedPassword)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.equalPasswords, equalPasswords) || other.equalPasswords == equalPasswords)&&const DeepCollectionEquality().equals(other.securityChecks, securityChecks)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is LegacyRecoverPasswordViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.recoveryRequested, recoveryRequested) || other.recoveryRequested == recoveryRequested)&&(identical(other.passwordChanged, passwordChanged) || other.passwordChanged == passwordChanged)&&(identical(other.token, token) || other.token == token)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatedPassword, repeatedPassword) || other.repeatedPassword == repeatedPassword)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.equalPasswords, equalPasswords) || other.equalPasswords == equalPasswords)&&const DeepCollectionEquality().equals(other.securityChecks, securityChecks)); } @override -int get hashCode => Object.hash(runtimeType,email,errorMessage,isLoading,recoveryRequested,passwordChanged,token,password,repeatedPassword,passwordVisible,equalPasswords,const DeepCollectionEquality().hash(securityChecks)); +int get hashCode => Object.hash(runtimeType,email,validationErrorKey,apiErrorEvent,isLoading,recoveryRequested,passwordChanged,token,password,repeatedPassword,passwordVisible,equalPasswords,const DeepCollectionEquality().hash(securityChecks)); @override String toString() { - return 'LegacyRecoverPasswordViewState(email: $email, errorMessage: $errorMessage, isLoading: $isLoading, recoveryRequested: $recoveryRequested, passwordChanged: $passwordChanged, token: $token, password: $password, repeatedPassword: $repeatedPassword, passwordVisible: $passwordVisible, equalPasswords: $equalPasswords, securityChecks: $securityChecks)'; + return 'LegacyRecoverPasswordViewState(email: $email, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, recoveryRequested: $recoveryRequested, passwordChanged: $passwordChanged, token: $token, password: $password, repeatedPassword: $repeatedPassword, passwordVisible: $passwordVisible, equalPasswords: $equalPasswords, securityChecks: $securityChecks)'; } @@ -45,7 +45,7 @@ abstract mixin class $LegacyRecoverPasswordViewStateCopyWith<$Res> { factory $LegacyRecoverPasswordViewStateCopyWith(LegacyRecoverPasswordViewState value, $Res Function(LegacyRecoverPasswordViewState) _then) = _$LegacyRecoverPasswordViewStateCopyWithImpl; @useResult $Res call({ - String email, String errorMessage, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks + String email, String validationErrorKey, LegacyRecoverPasswordErrorEvent? apiErrorEvent, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks }); @@ -62,11 +62,12 @@ class _$LegacyRecoverPasswordViewStateCopyWithImpl<$Res> /// Create a copy of LegacyRecoverPasswordViewState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? email = null,Object? errorMessage = null,Object? isLoading = null,Object? recoveryRequested = null,Object? passwordChanged = null,Object? token = null,Object? password = null,Object? repeatedPassword = null,Object? passwordVisible = null,Object? equalPasswords = null,Object? securityChecks = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? email = null,Object? validationErrorKey = null,Object? apiErrorEvent = freezed,Object? isLoading = null,Object? recoveryRequested = null,Object? passwordChanged = null,Object? token = null,Object? password = null,Object? repeatedPassword = null,Object? passwordVisible = null,Object? equalPasswords = null,Object? securityChecks = null,}) { return _then(_self.copyWith( email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable -as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable -as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as String,validationErrorKey: null == validationErrorKey ? _self.validationErrorKey : validationErrorKey // ignore: cast_nullable_to_non_nullable +as String,apiErrorEvent: freezed == apiErrorEvent ? _self.apiErrorEvent : apiErrorEvent // ignore: cast_nullable_to_non_nullable +as LegacyRecoverPasswordErrorEvent?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool,recoveryRequested: null == recoveryRequested ? _self.recoveryRequested : recoveryRequested // ignore: cast_nullable_to_non_nullable as bool,passwordChanged: null == passwordChanged ? _self.passwordChanged : passwordChanged // ignore: cast_nullable_to_non_nullable as bool,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable @@ -160,10 +161,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String email, String errorMessage, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String email, String validationErrorKey, LegacyRecoverPasswordErrorEvent? apiErrorEvent, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _LegacyRecoverPasswordViewState() when $default != null: -return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: +return $default(_that.email,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: return orElse(); } @@ -181,10 +182,10 @@ return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryReq /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String email, String errorMessage, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String email, String validationErrorKey, LegacyRecoverPasswordErrorEvent? apiErrorEvent, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks) $default,) {final _that = this; switch (_that) { case _LegacyRecoverPasswordViewState(): -return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: +return $default(_that.email,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: throw StateError('Unexpected subclass'); } @@ -201,10 +202,10 @@ return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryReq /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String email, String errorMessage, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String email, String validationErrorKey, LegacyRecoverPasswordErrorEvent? apiErrorEvent, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks)? $default,) {final _that = this; switch (_that) { case _LegacyRecoverPasswordViewState() when $default != null: -return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: +return $default(_that.email,_that.validationErrorKey,_that.apiErrorEvent,_that.isLoading,_that.recoveryRequested,_that.passwordChanged,_that.token,_that.password,_that.repeatedPassword,_that.passwordVisible,_that.equalPasswords,_that.securityChecks);case _: return null; } @@ -216,11 +217,12 @@ return $default(_that.email,_that.errorMessage,_that.isLoading,_that.recoveryReq class _LegacyRecoverPasswordViewState implements LegacyRecoverPasswordViewState { - const _LegacyRecoverPasswordViewState({this.email = '', this.errorMessage = '', this.isLoading = false, this.recoveryRequested = false, this.passwordChanged = false, this.token = '', this.password = '', this.repeatedPassword = '', this.passwordVisible = false, this.equalPasswords = true, final Map securityChecks = const {'min' : false, 'capital' : false, 'number' : false, 'special' : false}}): _securityChecks = securityChecks; + const _LegacyRecoverPasswordViewState({this.email = '', this.validationErrorKey = '', this.apiErrorEvent, this.isLoading = false, this.recoveryRequested = false, this.passwordChanged = false, this.token = '', this.password = '', this.repeatedPassword = '', this.passwordVisible = false, this.equalPasswords = true, final Map securityChecks = const {'min' : false, 'capital' : false, 'number' : false, 'special' : false}}): _securityChecks = securityChecks; @override@JsonKey() final String email; -@override@JsonKey() final String errorMessage; +@override@JsonKey() final String validationErrorKey; +@override final LegacyRecoverPasswordErrorEvent? apiErrorEvent; @override@JsonKey() final bool isLoading; @override@JsonKey() final bool recoveryRequested; @override@JsonKey() final bool passwordChanged; @@ -247,16 +249,16 @@ _$LegacyRecoverPasswordViewStateCopyWith<_LegacyRecoverPasswordViewState> get co @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacyRecoverPasswordViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.recoveryRequested, recoveryRequested) || other.recoveryRequested == recoveryRequested)&&(identical(other.passwordChanged, passwordChanged) || other.passwordChanged == passwordChanged)&&(identical(other.token, token) || other.token == token)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatedPassword, repeatedPassword) || other.repeatedPassword == repeatedPassword)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.equalPasswords, equalPasswords) || other.equalPasswords == equalPasswords)&&const DeepCollectionEquality().equals(other._securityChecks, _securityChecks)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LegacyRecoverPasswordViewState&&(identical(other.email, email) || other.email == email)&&(identical(other.validationErrorKey, validationErrorKey) || other.validationErrorKey == validationErrorKey)&&(identical(other.apiErrorEvent, apiErrorEvent) || other.apiErrorEvent == apiErrorEvent)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.recoveryRequested, recoveryRequested) || other.recoveryRequested == recoveryRequested)&&(identical(other.passwordChanged, passwordChanged) || other.passwordChanged == passwordChanged)&&(identical(other.token, token) || other.token == token)&&(identical(other.password, password) || other.password == password)&&(identical(other.repeatedPassword, repeatedPassword) || other.repeatedPassword == repeatedPassword)&&(identical(other.passwordVisible, passwordVisible) || other.passwordVisible == passwordVisible)&&(identical(other.equalPasswords, equalPasswords) || other.equalPasswords == equalPasswords)&&const DeepCollectionEquality().equals(other._securityChecks, _securityChecks)); } @override -int get hashCode => Object.hash(runtimeType,email,errorMessage,isLoading,recoveryRequested,passwordChanged,token,password,repeatedPassword,passwordVisible,equalPasswords,const DeepCollectionEquality().hash(_securityChecks)); +int get hashCode => Object.hash(runtimeType,email,validationErrorKey,apiErrorEvent,isLoading,recoveryRequested,passwordChanged,token,password,repeatedPassword,passwordVisible,equalPasswords,const DeepCollectionEquality().hash(_securityChecks)); @override String toString() { - return 'LegacyRecoverPasswordViewState(email: $email, errorMessage: $errorMessage, isLoading: $isLoading, recoveryRequested: $recoveryRequested, passwordChanged: $passwordChanged, token: $token, password: $password, repeatedPassword: $repeatedPassword, passwordVisible: $passwordVisible, equalPasswords: $equalPasswords, securityChecks: $securityChecks)'; + return 'LegacyRecoverPasswordViewState(email: $email, validationErrorKey: $validationErrorKey, apiErrorEvent: $apiErrorEvent, isLoading: $isLoading, recoveryRequested: $recoveryRequested, passwordChanged: $passwordChanged, token: $token, password: $password, repeatedPassword: $repeatedPassword, passwordVisible: $passwordVisible, equalPasswords: $equalPasswords, securityChecks: $securityChecks)'; } @@ -267,7 +269,7 @@ abstract mixin class _$LegacyRecoverPasswordViewStateCopyWith<$Res> implements $ factory _$LegacyRecoverPasswordViewStateCopyWith(_LegacyRecoverPasswordViewState value, $Res Function(_LegacyRecoverPasswordViewState) _then) = __$LegacyRecoverPasswordViewStateCopyWithImpl; @override @useResult $Res call({ - String email, String errorMessage, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks + String email, String validationErrorKey, LegacyRecoverPasswordErrorEvent? apiErrorEvent, bool isLoading, bool recoveryRequested, bool passwordChanged, String token, String password, String repeatedPassword, bool passwordVisible, bool equalPasswords, Map securityChecks }); @@ -284,11 +286,12 @@ class __$LegacyRecoverPasswordViewStateCopyWithImpl<$Res> /// Create a copy of LegacyRecoverPasswordViewState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? email = null,Object? errorMessage = null,Object? isLoading = null,Object? recoveryRequested = null,Object? passwordChanged = null,Object? token = null,Object? password = null,Object? repeatedPassword = null,Object? passwordVisible = null,Object? equalPasswords = null,Object? securityChecks = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? email = null,Object? validationErrorKey = null,Object? apiErrorEvent = freezed,Object? isLoading = null,Object? recoveryRequested = null,Object? passwordChanged = null,Object? token = null,Object? password = null,Object? repeatedPassword = null,Object? passwordVisible = null,Object? equalPasswords = null,Object? securityChecks = null,}) { return _then(_LegacyRecoverPasswordViewState( email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable -as String,errorMessage: null == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable -as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as String,validationErrorKey: null == validationErrorKey ? _self.validationErrorKey : validationErrorKey // ignore: cast_nullable_to_non_nullable +as String,apiErrorEvent: freezed == apiErrorEvent ? _self.apiErrorEvent : apiErrorEvent // ignore: cast_nullable_to_non_nullable +as LegacyRecoverPasswordErrorEvent?,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable as bool,recoveryRequested: null == recoveryRequested ? _self.recoveryRequested : recoveryRequested // ignore: cast_nullable_to_non_nullable as bool,passwordChanged: null == passwordChanged ? _self.passwordChanged : passwordChanged // ignore: cast_nullable_to_non_nullable as bool,token: null == token ? _self.token : token // ignore: cast_nullable_to_non_nullable diff --git a/packages/sf_localizations/assets/l10n/de.json b/packages/sf_localizations/assets/l10n/de.json index bf905473..d3c32227 100644 --- a/packages/sf_localizations/assets/l10n/de.json +++ b/packages/sf_localizations/assets/l10n/de.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Zu viele Versuche. Bitte warten Sie einige Minuten und versuchen Sie es erneut.", "authErrorNetwork": "Keine Verbindung. Überprüfen Sie Ihr Netzwerk und versuchen Sie es erneut.", "signupErrorInvalidField": "Eines der Felder ist ungültig. Bitte überprüfen Sie es und versuchen Sie es erneut.", + "recoverPasswordErrorInvalidEmail": "Das E-Mail-Format ist ungültig.", + "recoverPasswordErrorWeakPassword": "Das Passwort erfüllt die Sicherheitsanforderungen nicht.", + "recoverPasswordErrorInvalidField": "Eines der Felder ist ungültig. Bitte überprüfen Sie es und versuchen Sie es erneut.", + "recoverPasswordErrorTokenExpired": "Der Wiederherstellungslink ist abgelaufen. Bitte fordern Sie einen neuen an.", + "recoverPasswordErrorTokenNotFound": "Der Wiederherstellungslink ist ungültig. Bitte fordern Sie einen neuen an.", "stepUserContactSupertitle": "Benutzer und Kontakt", "stepUserContactTitle": "Erstelle dein Konto", "stepUserContactSubtitle": "Mit deiner E-Mail und deiner Telefonnummer können wir dich jederzeit informieren", diff --git a/packages/sf_localizations/assets/l10n/en.json b/packages/sf_localizations/assets/l10n/en.json index 0901c4d2..0c16925a 100755 --- a/packages/sf_localizations/assets/l10n/en.json +++ b/packages/sf_localizations/assets/l10n/en.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Too many attempts. Please wait a few minutes and try again.", "authErrorNetwork": "No connection. Check your network and try again.", "signupErrorInvalidField": "One of the fields is invalid. Please check and try again.", + "recoverPasswordErrorInvalidEmail": "The email format is not valid.", + "recoverPasswordErrorWeakPassword": "The password does not meet the security requirements.", + "recoverPasswordErrorInvalidField": "One of the fields is invalid. Please check and try again.", + "recoverPasswordErrorTokenExpired": "The recovery link has expired. Please request a new one.", + "recoverPasswordErrorTokenNotFound": "The recovery link is not valid. Please request a new one.", "stepUserContactSupertitle": "User & contact", "stepUserContactTitle": "Create your account", "stepUserContactSubtitle": "With your email and phone number we can keep you informed at all times", diff --git a/packages/sf_localizations/assets/l10n/es.json b/packages/sf_localizations/assets/l10n/es.json index 20c5d306..0856d018 100644 --- a/packages/sf_localizations/assets/l10n/es.json +++ b/packages/sf_localizations/assets/l10n/es.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Demasiados intentos. Espera unos minutos e inténtalo de nuevo.", "authErrorNetwork": "No hay conexión. Comprueba tu red e inténtalo de nuevo.", "signupErrorInvalidField": "Alguno de los campos no es válido. Revísalos e inténtalo de nuevo.", + "recoverPasswordErrorInvalidEmail": "El email no tiene un formato válido.", + "recoverPasswordErrorWeakPassword": "La contraseña no cumple los requisitos de seguridad.", + "recoverPasswordErrorInvalidField": "Alguno de los campos no es válido. Revísalos e inténtalo de nuevo.", + "recoverPasswordErrorTokenExpired": "El enlace de recuperación ha caducado. Solicita uno nuevo.", + "recoverPasswordErrorTokenNotFound": "El enlace de recuperación no es válido. Solicita uno nuevo.", "stepUserContactSupertitle": "Usuario y contacto", "stepUserContactTitle": "Crea tu usuario", "stepUserContactSubtitle": "Con tu email y tu número podremos mantenerte siempre informado", diff --git a/packages/sf_localizations/assets/l10n/fr.json b/packages/sf_localizations/assets/l10n/fr.json index 08b407e8..2600c026 100644 --- a/packages/sf_localizations/assets/l10n/fr.json +++ b/packages/sf_localizations/assets/l10n/fr.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Trop de tentatives. Patientez quelques minutes avant de réessayer.", "authErrorNetwork": "Pas de connexion. Vérifiez votre réseau et réessayez.", "signupErrorInvalidField": "L'un des champs n'est pas valide. Vérifiez et réessayez.", + "recoverPasswordErrorInvalidEmail": "Le format de l'e-mail n'est pas valide.", + "recoverPasswordErrorWeakPassword": "Le mot de passe ne respecte pas les exigences de sécurité.", + "recoverPasswordErrorInvalidField": "L'un des champs n'est pas valide. Vérifiez et réessayez.", + "recoverPasswordErrorTokenExpired": "Le lien de récupération a expiré. Demandez-en un nouveau.", + "recoverPasswordErrorTokenNotFound": "Le lien de récupération n'est pas valide. Demandez-en un nouveau.", "stepUserContactSupertitle": "Utilisateur et contact", "stepUserContactTitle": "Crée ton compte", "stepUserContactSubtitle": "Avec ton e-mail et ton numéro, nous pourrons te tenir informé à tout moment", diff --git a/packages/sf_localizations/assets/l10n/it.json b/packages/sf_localizations/assets/l10n/it.json index 823ae590..d64a4f8b 100644 --- a/packages/sf_localizations/assets/l10n/it.json +++ b/packages/sf_localizations/assets/l10n/it.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Troppi tentativi. Attendi qualche minuto e riprova.", "authErrorNetwork": "Nessuna connessione. Controlla la tua rete e riprova.", "signupErrorInvalidField": "Uno dei campi non è valido. Controlla e riprova.", + "recoverPasswordErrorInvalidEmail": "Il formato dell'email non è valido.", + "recoverPasswordErrorWeakPassword": "La password non soddisfa i requisiti di sicurezza.", + "recoverPasswordErrorInvalidField": "Uno dei campi non è valido. Controlla e riprova.", + "recoverPasswordErrorTokenExpired": "Il link di recupero è scaduto. Richiedine uno nuovo.", + "recoverPasswordErrorTokenNotFound": "Il link di recupero non è valido. Richiedine uno nuovo.", "stepUserContactSupertitle": "Utente e contatti", "stepUserContactTitle": "Crea il tuo account", "stepUserContactSubtitle": "Con la tua email e il tuo numero potremo tenerti sempre informato", diff --git a/packages/sf_localizations/assets/l10n/pt.json b/packages/sf_localizations/assets/l10n/pt.json index ba76c45f..94a52029 100644 --- a/packages/sf_localizations/assets/l10n/pt.json +++ b/packages/sf_localizations/assets/l10n/pt.json @@ -84,6 +84,11 @@ "authErrorTooManyAttempts": "Demasiadas tentativas. Aguarda alguns minutos e tenta novamente.", "authErrorNetwork": "Sem ligação. Verifica a tua rede e tenta novamente.", "signupErrorInvalidField": "Um dos campos não é válido. Verifica e tenta novamente.", + "recoverPasswordErrorInvalidEmail": "O formato do email não é válido.", + "recoverPasswordErrorWeakPassword": "A palavra-passe não cumpre os requisitos de segurança.", + "recoverPasswordErrorInvalidField": "Um dos campos não é válido. Verifica e tenta novamente.", + "recoverPasswordErrorTokenExpired": "O link de recuperação expirou. Solicita um novo.", + "recoverPasswordErrorTokenNotFound": "O link de recuperação não é válido. Solicita um novo.", "stepUserContactSupertitle": "Utilizador e contacto", "stepUserContactTitle": "Cria a tua conta", "stepUserContactSubtitle": "Com o teu email e o teu número poderemos manter-te sempre informado", diff --git a/packages/sf_localizations/lib/src/generated/i18n.dart b/packages/sf_localizations/lib/src/generated/i18n.dart index 65b831cf..a8954c7c 100755 --- a/packages/sf_localizations/lib/src/generated/i18n.dart +++ b/packages/sf_localizations/lib/src/generated/i18n.dart @@ -424,6 +424,16 @@ class I18n { static const String authErrorTooManyAttempts = 'authErrorTooManyAttempts'; static const String authErrorNetwork = 'authErrorNetwork'; static const String signupErrorInvalidField = 'signupErrorInvalidField'; + static const String recoverPasswordErrorInvalidEmail = + 'recoverPasswordErrorInvalidEmail'; + static const String recoverPasswordErrorWeakPassword = + 'recoverPasswordErrorWeakPassword'; + static const String recoverPasswordErrorInvalidField = + 'recoverPasswordErrorInvalidField'; + static const String recoverPasswordErrorTokenExpired = + 'recoverPasswordErrorTokenExpired'; + static const String recoverPasswordErrorTokenNotFound = + 'recoverPasswordErrorTokenNotFound'; static const String errorGeofenceCreate = 'errorGeofenceCreate'; static const String errorGeofenceDelete = 'errorGeofenceDelete'; static const String errorGeofenceUpdate = 'errorGeofenceUpdate';