Add lib/api/cap.js: requireWrite(entity_type) maps HTTP method to action, runs canAct, and tags req.capTier as allow|suggest|deny→403. Mutating routes (pages, projects, tasks, refs, resources, source_docs) now check req.capTier and either run the repo (allow) or divert to pending_changes returning 202 (suggest). Owner and worker actors stay on the allow path. requireOwner helper added for Task 11. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
32 lines
1.2 KiB
JavaScript
32 lines
1.2 KiB
JavaScript
import { canAct } from '../auth/capability.js';
|
|
import * as pendingChanges from '../db/repos/pending_changes.js';
|
|
import { ForbiddenError } from './errors.js';
|
|
|
|
const METHOD_TO_ACTION = { POST: 'create', PATCH: 'update', PUT: 'update', DELETE: 'delete' };
|
|
|
|
export function requireWrite(entity_type) {
|
|
return (req, _res, next) => {
|
|
const action = METHOD_TO_ACTION[req.method] || 'update';
|
|
const tier = canAct(req.actor, action, entity_type);
|
|
if (tier === 'allow') { req.capTier = 'allow'; return next(); }
|
|
if (tier === 'suggest') { req.capTier = 'suggest'; return next(); }
|
|
return next(new ForbiddenError(`agent not permitted to ${action} ${entity_type}`));
|
|
};
|
|
}
|
|
|
|
export function requireOwner(req, _res, next) {
|
|
if (req.actor?.kind !== 'user') {
|
|
return next(new ForbiddenError('owner-only endpoint'));
|
|
}
|
|
next();
|
|
}
|
|
|
|
export async function divertToPending(req, res, { entity_type, entity_id = null, action, payload, reason = null }) {
|
|
const change = await pendingChanges.create({
|
|
agent_id: req.actor.id,
|
|
entity_type, entity_id, action, payload,
|
|
reason: reason ?? req.headers['x-reason'] ?? null
|
|
});
|
|
res.status(202).json({ pending: true, change_id: change.id });
|
|
}
|