screentinker/server/lib/agency-targets.js
ScreenTinker 6d152a5ccf feat(api): GET /api/agency/playlists - a token's designated targets (#73)
The portal needs to show an agency which playlists it may post to. New read surface on the
security primitive, built with write-path rigor: the confinement query lives in
lib/agency-targets.js (own token + bound workspace only) and is bite-tested four ways -
own targets yes; another token's, outside the allowlist, and cross-workspace all NO;
neutralizing the t.token_id filter makes it go red. Real-path wiring + the portal's
graceful 401 trigger asserted in the integration suite. No :playlistId, so router.param
doesn't apply - the query is the seam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:08:07 -05:00

21 lines
891 B
JavaScript

'use strict';
// #73: the single query behind GET /api/agency/playlists. Returns ONLY this token's
// designated playlists, in its bound workspace. The WHERE clause IS the confinement and is
// the thing to bite-test:
// t.token_id = ? -> this token's targets, never another token's
// (JOIN api_token_targets) -> only allowlisted playlists, never one outside the allowlist
// p.workspace_id = ? -> only the bound workspace, never cross-workspace
// db is passed in (not module-required) so the confinement is unit-testable in isolation.
function listDesignatedPlaylists(db, tokenId, workspaceId) {
return db.prepare(`
SELECT p.id, p.name, p.status
FROM api_token_targets t
JOIN playlists p ON p.id = t.playlist_id
WHERE t.token_id = ? AND p.workspace_id = ?
ORDER BY p.name
`).all(tokenId, workspaceId);
}
module.exports = { listDesignatedPlaylists };