feat(teams): the grant flow, announcements, and the routes behind both guards
Path 3's WRITE half. The resolver landed in phase 2; this is who may hand access
out, to whom, and what stops a leader turning a Team forum into open hosting on
the operator's site.
Two authorities, and not one authority with different reach. Staff may act on any
Team, uncapped, and may revoke anything. A leader may grant and revoke ordinary
access on their own Team, is capped at `teams_max_grants_per_team` (default 50),
is rate-limited, and may NOT revoke a staff-issued grant — which is what stops a
leader undoing a moderation decision. The issuer's role is checked at revoke time
rather than stored, so an account that has since lost its staff role stops
protecting the grants it made.
Nothing on this path writes team_members, in either direction. A grant may name any
account, including one with no linked game identity — that is the point of it — and
that account stays off the roster, out of every count, and ineligible for external
platforms.
Announcements are a degenerate thread rather than their own object, so phase 5 adds
no migration. Moderation records WHICH authority was exercised: a staff action also
writes activity_log, a leader's writes only the Team's own ledger. Merging the two
would make a guild leader locking a thread an appealable Discord sanction.
Every forum route answers 404 while the switch is off, and 404 — never 403 — to a
caller with no access: in a private room the contents and the existence are the
same secret. The grant routes deliberately answer even while the forum is OFF,
because a toggle-off revokes no grant and the access list has to stay manageable.
Under /player rather than /admin: a leader is a player, and the /admin tier gate is
requireRole('admin','editor','moderator') — putting a leader endpoint behind it
would mean widening that gate.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ const noindex = require('../../../middleware/noindex')
|
||||
const accountRouter = require('./account.router')
|
||||
const appealsRouter = require('./appeals.router')
|
||||
const teamsRouter = require('./teams.router')
|
||||
const teamForumRouter = require('./teamForum.router')
|
||||
|
||||
const playerRouter = express.Router()
|
||||
|
||||
@@ -41,5 +42,9 @@ playerRouter.use(noindex, requireAuth)
|
||||
playerRouter.use('/account', accountRouter)
|
||||
playerRouter.use('/appeals', appealsRouter)
|
||||
playerRouter.use('/teams', teamsRouter)
|
||||
// Same prefix, second router. The forum and the leader-exercised grant flow are a
|
||||
// different capability from "the caller's own Teams", and splitting them keeps
|
||||
// each file about one thing; no path in the two collides.
|
||||
playerRouter.use('/teams', teamForumRouter)
|
||||
|
||||
module.exports = playerRouter
|
||||
|
||||
Reference in New Issue
Block a user