high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

signIn() return type is expanded to AuthResult — a sealed class with AuthSuccess(session, role), AuthFailure(exception), and NoRoleResult variants
After successful Supabase sign-in, the implementation queries a RoleRepository.getRoleForUser(userId) method
getRoleForUser() returns one of: UserRole.admin, UserRole.coordinator, UserRole.peerMentor, or null
If getRoleForUser() returns null, signIn() returns NoRoleResult — the BLoC maps this to the no-access screen
UserRole enum is defined in the domain layer with values: admin, coordinator, peerMentor
RoleRepository interface is defined in the domain layer; concrete implementation queries the Supabase 'user_roles' table
Role query uses the authenticated user's JWT (not a service role key) — least-privilege principle
If the role query fails due to network error, rethrow as NetworkFailureException (do not silently default to a role)
LoginFormBLoC receives role in LoginSuccess state and delegates routing to the UI layer
flutter analyze passes with zero warnings

Technical Requirements

frameworks
Flutter
Dart
Supabase Flutter SDK
Riverpod
apis
Supabase Database — user_roles table SELECT
Supabase Auth — current user JWT
data models
UserRole
RoleRepository
AuthResult (sealed)
AuthSuccess
NoRoleResult
performance requirements
Role query must complete within 2 seconds — it runs serially after sign-in on the login critical path
Cache the role in the Riverpod session provider to avoid repeated DB queries on tab switches
security requirements
Role query must use Row Level Security (RLS) — users may only read their own role row
Never trust a role value stored client-side for authorization decisions — always re-validate server-side on sensitive operations
Do not expose the user_roles table structure in error messages

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The Supabase query for role: `supabase.from('user_roles').select('role').eq('user_id', userId).maybeSingle()` — use maybeSingle() to get null instead of an exception when no row exists. Map the string role value ('admin', 'coordinator', 'peer_mentor') to the UserRole enum in the RoleRepository implementation, not in AuthService. Keep AuthService focused on orchestration. For GoRouter integration: define a redirect function in the router that reads sessionStateProvider; when AuthSuccess is emitted, navigate to the role-appropriate initial route.

This avoids hard-coding routing logic in the BLoC. Document the no-role scenario in a code comment: it applies to global admins who manage organizations but don't have an org-level role.

Testing Requirements

Unit tests with mocked RoleRepository and MockSupabaseClient: (1) signIn() with valid credentials + getRoleForUser returning coordinator → returns AuthSuccess with role: coordinator. (2) signIn() with valid credentials + getRoleForUser returning null → returns NoRoleResult. (3) signIn() with valid credentials + getRoleForUser throwing NetworkFailureException → returns AuthFailure(NetworkFailureException). (4) signIn() with invalid credentials → returns AuthFailure(InvalidCredentialsException) without calling getRoleForUser.

Widget test: NavigationRouter navigates to CoordinatorHome when role is coordinator, PeerMentorHome when peerMentor, NoAccessScreen when NoRoleResult.

Component
Authentication Service
service medium
Epic Risks (2)
high impact medium prob integration

Supabase GoTrue returns HTTP error codes and string messages that may change between SDK versions. Incorrect or incomplete mapping could cause the wrong user-facing message to be shown (e.g., showing a generic error instead of a specific credential error), violating the plain-language feedback acceptance criteria and potentially exposing security-sensitive information.

Mitigation & Contingency

Mitigation: Pin the supabase_flutter SDK to a specific minor version in pubspec.yaml. Write integration tests that mock the Supabase HTTP layer and assert each error code maps to the correct domain exception. Document the mapping table as a constant in AuthService.

Contingency: If an unrecognized error code is received at runtime, catch it as an UnknownAuthException and display a generic safe message. Alert via crash reporting for triage and SDK update.

medium impact medium prob technical

If the user taps the sign-in button multiple times rapidly, concurrent authentication requests could result in race conditions: duplicate network calls, out-of-order state emissions, or multiple session tokens being written to secure storage.

Mitigation & Contingency

Mitigation: Use bloc concurrency transformer (droppable or restartable) to ensure only one authentication event is processed at a time. The BLoC should guard against submission while in LoginLoading state.

Contingency: Add a UI-level disable on the submit button when loading state is active as a secondary guard independent of BLoC concurrency control.