3.5 KiB
3.5 KiB
League Actor Model & Permissions (Canonical)
This document defines the canonical backend actor model and the permission rules for league admin/owner operations.
It is the source of truth for Subtask 0A in plans/league-admin-mvp-plan.md.
Session identity (source of truth)
What the authenticated session contains
- The API authentication layer attaches
request.user.userIdbased on the session cookie (gp_session). - The backend uses an async request context (
AsyncLocalStorage) to make the current request available to services.- See
requestContextMiddleware(). - Wired globally for the API via
AppModule.configure().
- See
Mapping: userId → driverId
Current canonical mapping (for MVP):
- The “actor” is derived from session, and
driverId === userId. - This is implemented by
getActorFromRequestContext().
Rationale:
- The current system uses the session user identity as the same identifier used by racing/league membership repositories (e.g. seeded admin user is
driver-1in session). - If/when we introduce a real user ↔ driver relationship (1:N), this function becomes the single authoritative mapping point.
Canonical actor model
The API’s canonical “actor” is:
type Actor = { userId: string; driverId: string };
Returned by getActorFromRequestContext().
Rules:
- All auth/permissions decisions use the actor derived from the authenticated session.
- Controllers and services must never use request-body “performer/admin IDs” for authorization decisions.
League permissions: admin/owner
Meaning of “league admin/owner”
A driver is authorized as a league admin if:
- They have an active membership in the league, and
- Their membership role is either
owneroradmin.
Authoritative check:
- Implemented in the core use case
GetLeagueAdminPermissionsUseCase.execute()by loading membership and validatingstatus+role.
How it is validated server-side
Canonical enforcement entrypoint (API layer):
This helper:
- Derives the actor from session via
getActorFromRequestContext() - Invokes the core use case with
performerDriverId: actor.driverId
Contract rule (non-negotiable)
No league write operation may accept performer/admin IDs for auth decisions.
Concretely:
- Request DTOs may still temporarily contain IDs for “target entities” (e.g.
targetDriverId), but never the acting user/admin/performer ID. - Any endpoint/service that needs “who is performing this” MUST obtain it from session-derived actor, not from request payload, params, or hardcoded values.
Tests:
- Actor derives from session, not payload:
ActorFromSession. - Permission helper uses session-derived actor consistently:
ActorFromSession. - Example application in a league write-like operation (
joinLeague) ignores payload driverId and uses session actor:LeagueService.