Login Notifier
presentation/providers · Notifier<LoginState>
Overview
Riverpod Notifier orchestrating email/password, Google and Apple sign-in. On password login it issues a 'dual login' (customer + seller endpoints) to discover whether the same credentials also resolve a seller account, then persists both tokens and forces the user role accordingly.
User Flow
- Screen calls login(email, password); state flips to isLoading + isPasswordLoading.
- AuthApiService.dualLogin posts /auth/customer/emailpass-verified, then /auth/seller/emailpass-verified.
- Seller 401 is swallowed and returned as null — the user is treated as customer-only.
- _persistAuthenticatedSessions saves customer + (optional) seller tokens via AuthStorageService.
- userRoleProvider.notifier.setRole(freelancer|client) forces the active role based on which tokens exist.
- State updates to isAuthenticated = true; the GoRouter redirect then bounces /login → /home.
Cases & Edge Cases
- Seller endpoint returns 401 → treat as 'not a seller', proceed as customer-only (loginSeller swallows the 401).
- Google/Apple cancellation throws GoogleSignInExceptionCode.canceled / AuthorizationErrorCode.canceled — clear loading flags but do not surface as an error.
- Apple only sends name/email on the first authorization — subsequent logins must rely on the backend mapping by Apple subject ID.
- skipTermsConsent flag short-circuits the (currently commented-out) ToS pre-check; the router still enforces ToS post-login.
- Token may be expired when restored from storage — JwtHelper.isTokenExpired is logged but the UI does not yet trigger a refresh.
Code References
- lib/src/features/shared/auth/login/presentation/providers/auth/login_provider.dart:16 // LoginNotifier class
- lib/src/features/shared/auth/login/presentation/providers/auth/login_provider.dart:47 // login(email,password)
- lib/src/features/shared/auth/login/presentation/providers/auth/login_provider.dart:154 // loginWithGoogle
- lib/src/features/shared/auth/login/presentation/providers/auth/login_provider.dart:271 // loginWithApple
- lib/src/features/shared/auth/login/presentation/providers/auth/login_provider.dart:370 // _persistAuthenticatedSessions
- lib/src/features/shared/auth/login/presentation/providers/auth/login_state.dart
API Calls
- POST
/auth/customer/emailpass-verifiedno statusAuthenticate as a customer with email + password and receive a session token (mandatory leg of the dual-login).
Request{ email: string, password: string }Response{ token: string }lib/src/features/shared/auth/login/presentation/login.dartauth: none. First leg of dualLogin; any non-401 customer error throws.
- POST
/auth/seller/emailpass-verifiedno statusBest-effort seller login using the same credentials — used to detect multi-role users.
Request{ email: string, password: string }Response{ token: string }Errors- ·401 treated as 'not a seller' — swallowed and returned as null
lib/src/features/shared/auth/login/presentation/login.dartauth: none. Second leg of dualLogin; 401 is swallowed.
- POST
/auth/customer/google-mobileno statusExchange a Google OAuth payload (id_token + access_token) for a customer session token.
Request{ id_token: string, access_token: string, expires_in: number, token_type: string, scope: string, refresh_token: string }Response{ token: string }lib/src/features/shared/auth/login/presentation/login.dartauth: none. No seller path yet — sellerToken is null.
- POST
/auth/apple/callbackno statusExchange an Apple Sign-In identity payload for a customer session token.
Request{ id_token: string, access_token: string, email?: string, first_name?: string, last_name?: string }Response{ token: string }lib/src/features/shared/auth/login/presentation/login.dartauth: none. Apple only sends name/email on first authorization; later logins rely on backend mapping by Apple subject ID.
Notes
The commented-out termsConsentService.getConsentRequirement block is the previous design — ToS is now enforced exclusively by the GoRouter redirect (see auth-routes-guard). Calls are delegated to AuthApiService — see the auth-api-service node for the wire-level inventory.