Add reusable two-factor verification for login and money operations (#32)
This commit is contained in:
1 parent
b7a58b1a87
commit
fb40f860d2
41 files changed
+2439
-43
No files matched your search
@@ -27,11 +27,11 @@ await login.login(
|
||||
login.dispose();
|
||||
```
|
||||
|
||||
`SignupProvider` accepts a `SignupService` and exposes `signup(name:, email:, password:, locale:)`. Both providers extend ChangeNotifier and can be installed with `ChangeNotifierProvider`, ListenableBuilder, or another Flutter state binding. The package itself depends only on Flutter foundation; it contains no widgets, routes, localization, web-only code, network client, or storage.
|
||||
`SignupProvider` accepts a `SignupService` and exposes `signup(name:, email:, password:, locale:)`. Both providers extend ChangeNotifier and can be installed with `ChangeNotifierProvider`, ListenableBuilder, or another Flutter state binding. The package includes providers and a reusable Material two-factor page. Clients own routes and localization; the package contains no web-only code, network client, or storage.
|
||||
|
||||
## Lifecycle and services
|
||||
|
||||
Providers expose immutable `AuthState`: idle, loading, success, or error. They trim email/name, preserve the password verbatim, pass the locale to services, suppress duplicate submissions while loading, and clear old errors before retrying. Reset invalidates older responses and allows a new request; disposal ignores all later completions. Reset does not cancel remote work. Do not treat a provider reset as undoing a production signup.
|
||||
Providers expose immutable `AuthState`: idle, loading, challenge, success, or error. They trim email/name, preserve the password verbatim, pass the locale to services, suppress duplicate submissions while loading, and clear old errors before retrying. Reset invalidates older responses and allows a new request; disposal ignores all later completions. Reset does not cancel remote work. Do not treat a provider reset as undoing a production signup.
|
||||
|
||||
Services return `AuthResult` or throw `AuthException` with a typed `AuthError`. Unexpected exceptions are reduced to `unexpected`; raw errors and credentials are not retained in provider state. Credentials exist only as ephemeral request input. Localize error codes in each consuming application.
|
||||
|
||||
@@ -50,7 +50,7 @@ Supported failure codes: `invalidCredentials`, `emailInUse`, `unavailable`, and
|
||||
|
||||
Adapted the ChangeNotifier lifecycle, service separation, locale-carrying request, and typed-outcome patterns from Sendico's `pshared/lib/provider/account.dart`, `models/auth/login_outcome.dart`, and `service/authorization/client.dart`. These were patterns rather than directly portable implementations: the originals depend on Sendico account/organization models, device IDs, static API services, token storage, and verification flows. Those dependencies are deliberately absent here. The B2B organization signup contract is not copied into the consumer product.
|
||||
|
||||
When a supported sxchnge BFF contract exists, implement transport DTOs and mappers behind the service interfaces, returning domain results. No dummy DTO/mapping layer is needed without serialized transport. Keep web cookie and mobile credential handling in production platform adapters, not the providers.
|
||||
When a supported sxchnge BFF contract exists, implement transport DTOs and mappers behind the service interfaces, returning domain results. Dummy responses also pass through explicit DTOs and mappers, following the repository architecture. Keep web cookie and mobile credential handling in production platform adapters, not the providers.
|
||||
|
||||
## Validation
|
||||
|
||||
@@ -66,3 +66,36 @@ When a supported sxchnge BFF contract exists, implement transport DTOs and mappe
|
||||
shared `JourneyRecorder` from `sxchnge_analytics`. It preserves results/exceptions
|
||||
for providers while emitting only typed semantic outcomes. `SimulationInfo`
|
||||
allows decorators and UI to identify preview services before the first result.
|
||||
|
||||
## Two-factor verification
|
||||
|
||||
`TwoFactorService` owns settings, challenge creation/resend, verification and
|
||||
one-use approval consumption. DTOs map to `FactorSettings`, `FactorChallenge`
|
||||
and `FactorGrant`; `SecurityProvider` and `TwoFactorProvider` coordinate state.
|
||||
`TwoFactorPage` accepts the provider, client-localized `TwoFactorLabels`, callbacks
|
||||
and an optional transaction summary. Its route owner must dispose the provider;
|
||||
the page disposes its own code/focus controllers. No code or grant is persisted.
|
||||
|
||||
`FactorMethod.email` shows masked delivery and resend controls;
|
||||
`FactorMethod.authenticator` uses app-code instructions without delivery UI.
|
||||
The contract is ready for a future authenticator/TOTP adapter. Enrollment, secret
|
||||
provisioning, method selection and recovery require a backend contract first.
|
||||
|
||||
`GuardedLoginService` wraps a credentials service and exposes a pending challenge
|
||||
instead of successful login. `LoginProvider.complete` completes through
|
||||
`PendingLoginService` only after scope-bound authorization. Production adapters
|
||||
must return backend-owned pending login results and enforce session issuance
|
||||
there; this local wrapper is not a production authorization boundary.
|
||||
|
||||
The initial `DummyTwoFactorService` is disabled by default. Enable it in account
|
||||
settings using fixture code **123456**. It resets on reload, sends no email and
|
||||
does not validate real TOTP. Challenges expire after five minutes; email delivery
|
||||
has a 30-second cooldown; five wrong attempts lock that purpose for five minutes.
|
||||
Resending cannot reset attempts. Tests can inject `now`, `delay`, `method`,
|
||||
`enabled` and `nextError` to exercise states.
|
||||
|
||||
Use the same service instance for login, settings and the dummy wallet. A custom
|
||||
`WalletService` must validate the passed approval at execution and preserve
|
||||
idempotency; attaching the page alone does not protect funds. Production checks
|
||||
must bind approvals to the authenticated account and immutable operation details,
|
||||
validate expiry/policy, and atomically consume approval with execution.
|
||||
Reference in new issue
Block a user