critical priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

After successful BankID authentication, RoleResolutionService.resolveRoles() is called before the router navigates to the home screen
After successful Vipps authentication, same resolution flow is invoked
After successful email/password authentication, same resolution flow is invoked
If resolution returns GlobalAdminBlocked, navigation routes to the no-access screen instead of home
If resolution returns NoRoleAssigned, navigation routes to a role-pending screen
On logout (Supabase Auth signOut), RoleStateManager.resetOnLogout() is called and all role state is cleared
All Riverpod providers (RoleResolutionService, RoleStateManager, PermissionCheckerService) are registered in the root ProviderScope
Navigation does not occur until role resolution Future completes (no race condition)
Auth state listener handles token refresh events without re-triggering full role resolution
Deep link navigation post-login respects resolved role before redirecting

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase Auth (onAuthStateChange stream)
BankID OIDC callback
Vipps Login API callback
data models
assignment
contact
performance requirements
Total time from auth callback to home screen navigation must be under 3 seconds including role resolution
Supabase onAuthStateChange listener registered once at app start — not per-screen
security requirements
JWTs stored in flutter_secure_storage (iOS Keychain / Android Keystore) — never plain SharedPreferences
PKCE flow used for both BankID and Vipps OAuth redirects to prevent authorization code interception
Role state cleared on every logout — no stale role data persists across sessions
Deep link URLs validated before processing OAuth callbacks to prevent open redirect attacks
ui components
LoadingOverlay (shown during role resolution)
NoAccessScreen
RolePendingScreen

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Listen to Supabase.instance.auth.onAuthStateChange in a top-level Riverpod Provider (authStateProvider). On AuthChangeEvent.signedIn, call ref.read(roleResolutionServiceProvider).resolveRoles(userId). Use a GoRouter redirect that checks RoleStateManager state — if role is not yet resolved, redirect to a /loading route. Once RoleStateManager emits a resolved state, GoRouter's refreshListenable triggers re-evaluation of the redirect, allowing navigation to proceed.

This avoids imperative navigation calls and keeps routing declarative. The no-access screen for globalAdmin is a hard requirement per all three workshop organisations — it must be unreachable by any other navigation path.

Testing Requirements

Widget tests with flutter_test: mock Supabase Auth stream to emit AuthChangeEvent.signedIn with various user types, assert correct navigation outcome (home, no-access, role-pending). Test logout flow: emit signedOut event, assert RoleStateManager state is cleared. Test that navigation does not fire before resolution completes (use a delayed mock). Integration test: full BankID/Vipps PKCE callback simulation using local deep link trigger, assert role resolved and home screen rendered.

Test token refresh event does not clear or re-resolve role.

Component
Role Resolution Service
service medium
Epic Risks (3)
high impact low prob security

A coordinator's permissions could be revoked by an admin while they are actively using the app. If the permission checker relies solely on the cached role state from login, the coordinator could continue performing actions they are no longer authorized for until the next login.

Mitigation & Contingency

Mitigation: The Permission Checker Service must re-validate against the Role Repository (not just in-memory state) before high-impact actions. Implement a configurable staleness window (e.g., 15 minutes) after which role data is refreshed from Supabase in the background.

Contingency: If a revoked permission is detected during a pre-action check, immediately clear the cached role state, force a re-resolution from Supabase, and display an inline error explaining the permission change rather than crashing or silently failing.

medium impact medium prob technical

Using both BLoC and Riverpod in the same state management layer for roles risks state synchronization bugs where one system updates before the other, causing widgets to render with stale role data during the switch transition.

Mitigation & Contingency

Mitigation: Choose a single primary state management approach (Riverpod StateNotifier is recommended) for role state and wrap the BLoC pattern within it if legacy code requires BLoC interfaces. Establish a single source-of-truth provider that all consumers read from.

Contingency: If synchronization bugs appear during integration testing, introduce a RoleStateReady gate widget that delays rendering of role-dependent UI until the state notifier emits a confirmed resolved state, preventing partial renders.

medium impact high prob scope

Hardcoded permission constants per role can become a maintenance burden as new features are added across 61 total features, leading to permission definitions that are scattered, stale, or inconsistent.

Mitigation & Contingency

Mitigation: Centralize all role-permission mappings in a single RolePermissions constants file with named action keys. Enforce that no widget or service directly checks role type strings; all checks must go through the Permission Checker Service.

Contingency: If permission definitions drift out of sync, introduce a validation test suite that cross-references all registered permission constants against their usage sites and fails the CI build if an undefined permission key is referenced.