Repository navigation
Roles
This document describes how role-based access is implemented in the frontend, including provider-specific constraints, abstract-role client requirements, rule-evaluation order, and how to safely extend the system.
The access rules are centralized in:
src/context/auth/userAccessValidation.ts
Routes and pages that consume this logic:
-
src/routing/auth.ts—computeAuthRedirectwrapsgetUserAccessStatusfor the_authenticatedlayout;hasAnyRoleperforms any-of role checks for the tier layouts -
src/routes/_authenticated.tsx— auth + status gate layout -
src/routes/_authenticated/_admin.tsx,src/routes/_authenticated/_client-access.tsx— role-tier layouts src/pages/NoRole/index.tsxsrc/pages/RoleError/index.tsx
Primary role model definitions:
src/context/auth/types.ts
FamLoginUser.roles contains both:
-
Assigned business roles (for example
VIEWER,SUBMITTER,AREA, etc.) -
Provider marker roles (
IDIRorBCeID) appended during token parsing
Provider marker roles are metadata, not assignable business roles. Validation intentionally excludes them from assigned-role checks.
Rules are evaluated in deterministic order. First matching violation wins and drives redirect/message behavior.
GIVEN user has any idpProvider
WHEN user has no assigned roles (after removing provider marker roles)
THEN redirect to /no-role
Implementation detail:
-
getAssignedRoles(user)filters outIDIRandBCeIDmarker roles. - If
assignedRoles.length === 0, status iskind: 'no-role'.
GIVEN user has idpProvider = IDIR
WHEN user has more than one assigned role
THEN redirect to /unauthorized?reason=IDIR_MULTIPLE_ROLES with message:
This account is not permitted to have multiple roles.
GIVEN user has idpProvider = BCEIDBUSINESS
WHEN user has at least one assigned role in { ADMIN, AREA, DISTRICT }
THEN redirect to /unauthorized?reason=BCEID_ASSIGNED_ROLES with message:
This account is not permitted to have assigned roles.
GIVEN user has any idpProvider
WHEN user has an abstract role (currently VIEWER or SUBMITTER) with an empty client list
THEN redirect to /unauthorized?reason=CLIENT_ACCESS_REQUIRES_CLIENT with message:
This account requires at least one client
Important:
- This is an individual role check, not a consolidated client pool check.
- Example that now correctly fails:
SUBMITTER: ['100']VIEWER: []
GIVEN user has any idpProvider
WHEN user has both VIEWER and SUBMITTER AND at least one shared client between those two roles
THEN redirect to /unauthorized?reason=CONFLICTING_CLIENT_ACCESS_ROLES with message:
This account has conflicting client access roles
- The
_authenticatedlayout (src/routes/_authenticated.tsx) runs the purecomputeAuthRedirect(src/routing/auth.ts), which callsgetUserAccessStatus(user)after the authentication check. - For
kind: 'no-role'it redirects to/no-role. - For
kind: 'role-error'it redirects toredirectTo(for example/unauthorized?reason=...), except when already on/unauthorized(loop guard). -
RoleErrorreadsreasonfrom query params and resolves the message viagetAccessViolationMessage(reason).
During JWT parsing, the app appends one provider marker role (IDIR or BCeID) so downstream logic can quickly identify IdP context from role data. Because this marker is not an assigned business role, all business-role validation must use getAssignedRoles(...).
When introducing new roles, update both type definitions and validation behavior.
File: src/context/auth/types.ts
- Add role to
Roleenum. - Classify role in
roleTypeMapasRoleType.ABSTRACTorRoleType.CONCRETE.
Role type classification matters:
-
ABSTRACTroles are expected to have one or more client IDs. -
CONCRETEroles are identity/scoped roles that are not client-list based.
File: src/context/auth/authUtils.ts
- Ensure
extractRoles(...)correctly recognizes and maps the new role from Cognito groups.
File: src/context/auth/userAccessValidation.ts
- Add a new code in
accessViolationMessages. - Add/adjust rule logic in
getUserAccessStatus(...). - Keep evaluation order intentional (more general rules can mask later specific rules).
File: src/context/auth/userAccessValidation.unit.test.ts
- Add positive and negative cases.
- Add precedence tests when a user could match multiple violations.
- Add a key/value in
accessViolationMessagesinuserAccessValidation.ts. - Return that code from
getUserAccessStatus(...). - Redirect using
/unauthorized?reason=<NEW_CODE>. - Add unit tests:
- Validator returns expected code and redirect.
- RoleError page renders expected mapped message.
Before adding a new rule, decide its precedence relative to existing rules:
- Put it earlier if it should supersede others.
- Put it later if it should only run after base eligibility checks.
Current order is designed so provider restrictions are evaluated before client-association constraints.
- Keep all access-rule logic centralized in
userAccessValidation.ts. - Avoid duplicating role checks in route components or pages.
- Use reason codes (not raw text) for redirects so messages stay configurable.
- Always normalize client lists (
trim, remove empty values) before comparing.
At minimum, maintain coverage for:
- provider markers excluded from assigned roles
- no-role redirect
- IDIR multi-role rejection
- BCeID forbidden concrete-role rejection
- strict abstract-role client requirement (per-role)
- overlapping VIEWER/SUBMITTER client rejection
- default/unknown reason behavior for RoleError message mapping
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository