Skip to content

Station signup: manager API for reveal, rotate, revoke, clear-cooldown, status, and approve #2362

Description

@jakebromberg

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

  • All six operations behind the admin-flag gate
  • Reveal writes a passcode_revealed audit row with actor_user_id
  • Rotate refuses beyond two active rows
  • Clear-cooldown writes a floor row and deletes nothing
  • Approve does not use public /update-user
  • Integration tests in tests/integration/*.spec.js (plain JS — see jest.config.json testMatch)

Context

Plan: ~/Downloads/wxyc-station-passcode-signup-plan.md.

Related

Blocked by the lifecycle module. Blocks the dj-site passcode panel.

Blocked by

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions