Problem
The JWT carries a role claim baked in at signing time (src/modules/auth/auth.service.ts, lines 160–168), and the comment admits the model: roles refresh only when the client calls POST /auth/refresh. PATCH /users/me/role (src/modules/users/users.controller.ts, lines 80–108) explicitly instructs clients to refresh afterward. This means:
- A user who sets themselves
vendor (or any privileged role available in SetRoleDto) and later becomes malicious keeps that role in every outstanding access token until expiry — role revocation is advisory, not enforced.
- Combined with the blocked-status gap (separate issue), there is currently NO server-side authorization checkpoint between a valid signature and a role-gated action.
- The one-time role selection itself (
setRole) has no recovery path: a misclick permanently binds sponsor|vendor|mentor, and admin tooling to reset it does not exist — support incidents become permanent.
findOrCreateUser() auto-upserts ANY wallet presenting a valid token (line 144–158), meaning role assignment and profile creation happen implicitly outside any vetted flow.
Ground Rules
- Read context/architecture-context.md, context/code-standards.md, context/progress-tracker.md in full
- Read
src/auth/guards/roles.guard.ts, jwt.strategy.ts, and the users module in full
- Coordinate with the admin-guard issue if both land — shared status-resolution helper preferred
What To Build
- Centralize authorization on server truth: guards resolve the user's CURRENT role/status from the datastore (short-TTL cached, consistent with the staleness bound chosen in the companion issues) and treat the JWT claim as a hint only.
- Add an admin-only role-management endpoint (or extend the admin module) allowing role reset/removal with full audit logging via the existing audit interceptor.
- Tighten
setRole transition rules: define allowed transitions (e.g. none after first set, except admin reset) and enforce them in one place.
- Tests: downgraded user loses access within the documented bound; admin reset works end-to-end; audit events emitted; stale-token attacks rejected despite valid signature.
Files To Touch
src/auth/guards/roles.guard.ts
src/modules/users/users.controller.ts
src/modules/users/users.service.ts
src/modules/admin/ (new role-management surface)
- tests
- relevant docs/progress tracker
Acceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.
Problem
The JWT carries a
roleclaim baked in at signing time (src/modules/auth/auth.service.ts, lines 160–168), and the comment admits the model: roles refresh only when the client callsPOST /auth/refresh.PATCH /users/me/role(src/modules/users/users.controller.ts, lines 80–108) explicitly instructs clients to refresh afterward. This means:vendor(or any privileged role available inSetRoleDto) and later becomes malicious keeps that role in every outstanding access token until expiry — role revocation is advisory, not enforced.setRole) has no recovery path: a misclick permanently binds sponsor|vendor|mentor, and admin tooling to reset it does not exist — support incidents become permanent.findOrCreateUser()auto-upserts ANY wallet presenting a valid token (line 144–158), meaning role assignment and profile creation happen implicitly outside any vetted flow.Ground Rules
src/auth/guards/roles.guard.ts,jwt.strategy.ts, and the users module in fullWhat To Build
setRoletransition rules: define allowed transitions (e.g. none after first set, except admin reset) and enforce them in one place.Files To Touch
src/auth/guards/roles.guard.tssrc/modules/users/users.controller.tssrc/modules/users/users.service.tssrc/modules/admin/(new role-management surface)Acceptance Criteria
Mandatory Checks Before Opening PR
Standard checklist applies. PRs failing any check will be closed without review.