Problem
Managers need to operate the passcode and clear the review queue. Without these endpoints the feature is unmanageable: no way to rotate the sticky-note code, no way to recover from a cooldown, no way to mark an account reviewed.
Desired end state
Endpoints in apps/auth/, mounted beside the other admin routes:
- Reveal — returns the current plaintext code. Readable storage exists precisely so a manager can read the code to a stranded DJ by phone without rotating (rotating under the two-row cap can invalidate the note the rest of the room is using).
- Rotate — generates a new code, returns the plaintext, refuses beyond two active rows.
- Revoke — manual kill switch, sets
revoked_reason = 'manual'.
- Clear cooldown — writes a
cooldown_cleared row that acts as a floor on the failure window. This is the escape hatch that keeps the cooldown from being a lockout: a sustained attacker can otherwise hold the endpoint in cooldown with nothing in the product able to clear it. Cheap, same auth gate, and it turns "wait for a manager who is not on site" into one phone call.
- Status — active code state (expired / revoked with reason,
last_used_at, use_count / max_uses), cooldown state, recent failure counts.
- Approve account — sets
self_signup_reviewed_at / _reviewed_by.
Every reveal writes a passcode_revealed row carrying actor_user_id. Readable storage removes the structural guarantee show-once storage had for free — that only whoever rotated ever saw the code — so the audit log is what replaces it, and it makes "who could have seen this?" answerable after a suspected leak.
Auth gate — state it precisely
Use the admin-flag check the sibling endpoint uses: session.user.role !== 'admin' -> 403 (apps/auth/app.ts:253-256). requirePermissions lives in the backend app, not here.
Be explicit that this is not "stationManager only": via grantsAdminFlag it also admits stray admin/owner membership rows. That is the right trade — /auth/admin/provision-user, which creates accounts at any role including stationManager, already sits behind exactly this gate, so passcode rotation is not a wider grant than what it protects. A true stationManager-only gate would need a membership-role read that nothing in apps/auth does today; if that is wanted it is its own change with its own test.
The approve action must not go through public POST /update-user. The three review columns carry input: false, which blocks that route by design (that is the point — it is what stops a DJ approving themselves). Use the admin plugin route or a dedicated endpoint. The BS#2297 comment confirms both internalAdapter writes and the admin plugin's adminUpdateUser bypass input filtering, so either works.
Acceptance criteria
Context
Plan: ~/Downloads/wxyc-station-passcode-signup-plan.md.
Related
Blocked by the lifecycle module. Blocks the dj-site passcode panel.
Blocked by
Problem
Managers need to operate the passcode and clear the review queue. Without these endpoints the feature is unmanageable: no way to rotate the sticky-note code, no way to recover from a cooldown, no way to mark an account reviewed.
Desired end state
Endpoints in
apps/auth/, mounted beside the other admin routes:revoked_reason = 'manual'.cooldown_clearedrow that acts as a floor on the failure window. This is the escape hatch that keeps the cooldown from being a lockout: a sustained attacker can otherwise hold the endpoint in cooldown with nothing in the product able to clear it. Cheap, same auth gate, and it turns "wait for a manager who is not on site" into one phone call.last_used_at,use_count/max_uses), cooldown state, recent failure counts.self_signup_reviewed_at/_reviewed_by.Every reveal writes a
passcode_revealedrow carryingactor_user_id. Readable storage removes the structural guarantee show-once storage had for free — that only whoever rotated ever saw the code — so the audit log is what replaces it, and it makes "who could have seen this?" answerable after a suspected leak.Auth gate — state it precisely
Use the admin-flag check the sibling endpoint uses:
session.user.role !== 'admin'-> 403 (apps/auth/app.ts:253-256).requirePermissionslives in the backend app, not here.Be explicit that this is not "stationManager only": via
grantsAdminFlagit also admits strayadmin/ownermembership rows. That is the right trade —/auth/admin/provision-user, which creates accounts at any role includingstationManager, already sits behind exactly this gate, so passcode rotation is not a wider grant than what it protects. A true stationManager-only gate would need a membership-role read that nothing inapps/authdoes today; if that is wanted it is its own change with its own test.The approve action must not go through public
POST /update-user. The three review columns carryinput: false, which blocks that route by design (that is the point — it is what stops a DJ approving themselves). Use the admin plugin route or a dedicated endpoint. The BS#2297 comment confirms bothinternalAdapterwrites and the admin plugin'sadminUpdateUserbypass input filtering, so either works.Acceptance criteria
passcode_revealedaudit row withactor_user_id/update-usertests/integration/*.spec.js(plain JS — seejest.config.jsontestMatch)Context
Plan:
~/Downloads/wxyc-station-passcode-signup-plan.md.Related
Blocked by the lifecycle module. Blocks the dj-site passcode panel.
Blocked by