screentinker/server
ScreenTinker 40102b2b41 feat(api): agency portal endpoints + router.param target seam (#73)
The agency capability behind the proven off-ladder/agencyGate primitive:
- agencyGate is now SCOPE-only at the mount; the per-target check is router.param
  ('playlistId') in routes/agency.js - it fires WITH the param before the handler, so no
  :playlistId route can skip it (drift-proof). A mount-level target check was silently
  bypassed (Express populates req.params only at route match); the integration bite-suite
  caught it - this is the fix.
- routes/agency.js: POST /content (shared ingest) + POST /playlists/:id/items (date-bounded
  #74/#75 item; lands as draft so the admin's re-publish is the approval gate).
- tokens.js: issue scope='agency' tokens bound to a non-empty in-workspace playlist
  allowlist (atomic); PUT /:id/targets re-designates (JWT-only -> can't self-widen).
- server.js: AGENCY_ROUTERS mounted bearerAuth + resolveTenancy + agencyGate.

Full bite-suite (test/agency.test.js) GREEN and re-proven to bite on the SHIPPING path:
neutralizing the router.param check makes non-designated->403 go red. Four assertions at
three seams: target (router.param), off-ladder (tokenScopeGate), can't-widen (tokens
JWT-only), issuance cross-workspace (create validation). 139 suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 22:48:42 -05:00
..
config feat(api): agency-token security primitive - off-ladder scope + agencyGate (#73) 2026-06-13 21:30:38 -05:00
db feat(api): agency-token security primitive - off-ladder scope + agencyGate (#73) 2026-06-13 21:30:38 -05:00
lib refactor(content): extract the upload ingest into a shared lib (#73) 2026-06-13 22:48:42 -05:00
middleware feat(api): agency portal endpoints + router.param target seam (#73) 2026-06-13 22:48:42 -05:00
player feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
routes feat(api): agency portal endpoints + router.param target seam (#73) 2026-06-13 22:48:42 -05:00
scripts feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
services feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
test feat(api): agency portal endpoints + router.param target seam (#73) 2026-06-13 22:48:42 -05:00
ws feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00
package-lock.json chore(server): TOTP schema + otplib dep (#100) 2026-06-13 20:48:55 -05:00
package.json chore(server): TOTP schema + otplib dep (#100) 2026-06-13 20:48:55 -05:00
server.js feat(api): agency portal endpoints + router.param target seam (#73) 2026-06-13 22:48:42 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00