mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-14 14:23:14 -06:00
Least-privilege way to read GET /api/billing/usage without requiring platform admin. Additive + isolated: reuses the existing api_tokens scope system (the off-ladder 'agency' scope is the precedent) and does NOT touch the shared role/permission checks other endpoints rely on. - New off-ladder scope 'billing:read' (routes/tokens.js SCOPES). Like 'agency' it is NOT on the read<write<full ladder, so tokenScopeGate rejects a billing token on every PUBLIC_ROUTER and JWT-only routers reject any st_ token -> the scope grants billing-read and NOTHING else. - DUAL-PATH gate requireBillingRead (middleware/apiToken.js), written as an EXPLICIT OR: authorize if (billing:read token) OR (platform-admin session). Admins keep read access but are NOT required to; the token path doesn't lock out admins or vice versa. Billing route now mounted with bearerAuth (token OR JWT front door) + requireBillingRead (was requireAuth + requirePlatformAdmin). - MINTING is platform-admin only (stricter than read/write/full/agency, which any workspace member may mint) since a billing:read token grants GLOBAL billing-read. Note: no finer "owner" tier exists here (#14 collapsed superadmin->platform_admin), so PLATFORM_ROLES is the top level required. Tests (5, test/billing-authz.test.js): dual-path positive (token AND admin session both 200) + negative (user 403 / anon 401); scope isolation (billing token 403 on /api/devices, 401 on /api/admin; read token 200 on devices but 403 on billing); minting owner-only (user + ordinary-admin 403, platform-admin 201); revocation -> 401. Existing token firewall/partition suite (api.test.js) + billing-endpoint tests unchanged & green. Reused the exact SHA-256 token-verification path (no bcrypt/new mechanism). Suite 306/306. NOTE: spec described bcrypt + JSON `scopes` + an analytics:read precedent; this codebase actually uses SHA-256 + a single `scope` TEXT column + 'agency' as the off-ladder precedent. Implemented faithfully to the real system. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
26 lines
1.1 KiB
JavaScript
26 lines
1.1 KiB
JavaScript
'use strict';
|
||
|
||
// #146 BILLING — admin-gated Usage Report (ByteTinker–Bold agreement §4.1 system-of-record
|
||
// / §4.2 verification). DELIBERATELY a standalone route, separate from routes/status.js:
|
||
// /api/status is the hot, constantly-polled path; this is a heavier admin-only aggregate
|
||
// and must not touch it. Reads the daily rollup ONLY (cheap — no raw-log scans).
|
||
|
||
const express = require('express');
|
||
const router = express.Router();
|
||
const { requireBillingRead } = require('../middleware/apiToken');
|
||
const billing = require('../lib/billing');
|
||
|
||
// GET /api/billing/usage?month=YYYY-MM (default: current month)
|
||
// #146 Option C — DUAL PATH: authorized by a 'billing:read' scoped API token OR a
|
||
// platform-admin session (requireBillingRead, explicit OR). Admins keep read access but a
|
||
// least-privilege token is the intended consumer (tooling / invoice-time pulls / §4.2).
|
||
router.get('/usage', requireBillingRead, (req, res) => {
|
||
try {
|
||
res.json(billing.buildUsageReport(req.query.month));
|
||
} catch (e) {
|
||
res.status(400).json({ error: e.message });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|