critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

SecureStorageKey enum is defined with exactly these values: sessionToken, sessionExpiry, biometricEnabled, userId, refreshToken — each with a corresponding string key property used as the flutter_secure_storage key (e.g., 'session_token')
BiometricType enum is defined with exactly these values: faceId, touchId, fingerprint, iris, none
StoredSession is implemented as an immutable value object (either with const constructor or using freezed/equatable) with fields: String token, DateTime expiry, String userId
StoredSession includes a factory constructor StoredSession.fromMap(Map<String, String>) for deserialization from flutter_secure_storage string values
StoredSession includes a Map<String, String> toMap() method for serialization to flutter_secure_storage
StoredSession includes a bool get isExpired computed property returning true when expiry.isBefore(DateTime.now())
All models are placed in lib/src/features/auth/domain/models/ (or equivalent domain layer path consistent with the project's layer structure)
A barrel export file (auth_domain_models.dart) exports all three types
flutter analyze passes with zero errors on the new files
Unit tests for StoredSession serialization round-trip (toMap → fromMap) pass

Technical Requirements

frameworks
Dart (core language only — no additional packages required unless project already uses freezed/equatable)
equatable (if already used in the project for value equality) or manually implemented == and hashCode
data models
StoredSession (token: String, expiry: DateTime, userId: String)
SecureStorageKey enum
BiometricType enum
performance requirements
StoredSession serialization/deserialization must complete in under 1ms — no heavy computation
security requirements
SecureStorageKey string values (the actual storage keys) must be stable across app versions — changing them would invalidate all stored sessions for existing users
StoredSession must not contain raw password or PIN — only token and refresh token
Do not log or print StoredSession fields anywhere in the model code — no implicit toString() that exposes tokens

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

DateTime serialization: store expiry as an ISO 8601 string (expiry.toIso8601String()) in the map and parse back with DateTime.parse(map['session_expiry']!). This avoids ambiguity with timezone and is human-readable in debugging. SecureStorageKey enum pattern — add a key getter to avoid hardcoded strings elsewhere: 'enum SecureStorageKey { sessionToken; String get key => switch(this) { sessionToken => "session_token", ... } }'.

Using Dart 3 switch expressions keeps this concise. If the project uses freezed, make StoredSession a @freezed class for free copyWith, equality, and toString (with token redaction). If not, implement == and hashCode manually based on all three fields. Place the BiometricType enum in the same file as StoredSession or in a separate biometric_type.dart — be consistent with the project's file-per-type convention.

Do not make these models depend on any Flutter-specific packages (no flutter imports) — they are pure Dart domain objects usable in unit tests without the full Flutter engine.

Testing Requirements

Write unit tests in test/src/features/auth/domain/models/stored_session_test.dart covering: (1) StoredSession.fromMap correctly parses all fields including ISO 8601 DateTime string, (2) StoredSession.toMap produces the correct map with string-serialized DateTime, (3) round-trip: toMap then fromMap returns an equal StoredSession, (4) isExpired returns true for expiry in the past, (5) isExpired returns false for expiry in the future, (6) fromMap with missing fields throws a descriptive ArgumentError. Run with flutter test test/src/features/auth/domain/models/. No integration tests required for pure domain models.

Component
Secure Session Storage
data low
Epic Risks (3)
high impact medium prob technical

iOS Keychain access requires correct entitlement configuration and provisioning profile setup. Misconfigured entitlements cause silent failures in CI/CD and on physical devices, where the plugin appears to work in the simulator but fails at runtime. This can delay foundation delivery and block all downstream epics.

Mitigation & Contingency

Mitigation: Add a dedicated integration test running on a physical iOS device early in the epic. Document required entitlements and provisioning steps in a developer runbook. Validate Keychain access in the CI pipeline using an iOS simulator with correct entitlements enabled.

Contingency: If Keychain entitlements cannot be resolved quickly, temporarily use in-memory storage behind the SecureSessionStorage interface to unblock downstream epics, then resolve the Keychain issue in a hotfix before release.

medium impact medium prob dependency

The Flutter local_auth plugin has a history of breaking API changes between major versions, and its Android implementation depends on BiometricPrompt which behaves differently across Android API levels (23-34). An incompatible plugin version or unexpected Android API behaviour can cause authentication failures on a significant portion of the target device fleet.

Mitigation & Contingency

Mitigation: Pin local_auth to a specific stable version in pubspec.yaml. Test against Android API levels 23, 28, and 33 in the CI matrix. Review the plugin changelog and migration guide before adopting any version bump.

Contingency: If the pinned version proves incompatible with target devices, evaluate flutter_local_auth_android as a replacement or fork the plugin adapter to isolate the breaking surface.

high impact low prob security

If users upgrade from a version of the app that stored session data in non-encrypted storage (SharedPreferences), a migration path is required. Failing to migrate silently leaves old tokens in plain storage, creating a security gap and potentially causing confusing authentication state on first launch of the new version.

Mitigation & Contingency

Mitigation: Audit existing storage usage across the codebase before writing SecureSessionStorage. If legacy plain storage keys exist, implement a one-time migration routine that reads from SharedPreferences, writes to Keychain/Keystore, and deletes the plain-text entry.

Contingency: If migration is discovered late, ship the migration as a mandatory patch release before the biometric feature is enabled for users, and add a startup check that blocks biometric opt-in until migration is confirmed complete.