feat(api): agent bearer auth middleware

Add lib/api/middleware/agent_auth.js: agentOrOwner accepts the owner
token (kind=user actor) or a hashed agent token (kind=agent actor
carrying capabilities + scopes). /api router now mounts this in place
of ownerOnly so agent tokens become first-class.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
root
2026-05-31 20:59:29 +10:00
parent ba782ed690
commit 7862d22a03
3 changed files with 89 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
import { Router } from 'express';
import { ownerOnly } from '../auth/owner.js';
import { agentOrOwner } from './middleware/agent_auth.js';
import { errorMiddleware, NotFoundError } from './errors.js';
import { router as spacesRouter } from './routes/spaces.js';
import { router as projectsRouter, spacesScopedRouter as projectsBySpaceRouter } from './routes/projects.js';
@@ -15,7 +15,7 @@ import { router as sourceDocsRouter, resourcesScopedRouter as sourceDocsByResour
export function mountApi(app) {
const api = Router();
api.use(ownerOnly);
api.use(agentOrOwner);
api.use('/spaces', spacesRouter);
api.use('/spaces/:space_id/projects', projectsBySpaceRouter);

View File

@@ -0,0 +1,32 @@
import * as agents from '../../db/repos/agents.js';
export async function agentOrOwner(req, res, next) {
const expectedOwner = process.env.OWNER_TOKEN;
if (!expectedOwner) {
return res.status(500).json({
error: { code: 'no_owner_token', message: 'OWNER_TOKEN not configured' }
});
}
const auth = req.headers.authorization || '';
const [scheme, token] = auth.split(' ');
if (scheme !== 'Bearer' || !token) {
return res.status(401).json({ error: { code: 'unauthorized', message: 'missing bearer token' } });
}
if (token === expectedOwner) {
req.actor = { kind: 'user', id: null };
return next();
}
try {
const agent = await agents.verifyToken(token);
if (!agent) {
return res.status(401).json({ error: { code: 'unauthorized', message: 'invalid token' } });
}
req.actor = {
kind: 'agent',
id: agent.id,
capabilities: agent.capabilities || {},
scopes: agent.scopes || {}
};
next();
} catch (e) { next(e); }
}