auth-cli.ts45 lines · main
1import { Hono } from 'hono';
2
3import { signCliToken } from '../lib/cli-jwt.js';
4import { userRateLimit } from '../middleware/rate-limit.js';
5import { requireAuth } from '../middleware/session.js';
6import { audit, hashIp } from '../services/audit.js';
7import type { AppEnv } from '../types/app-env.js';
8
9/**
10 * POST /v1/auth/cli-token — mints a short-lived JWT bound to the calling
11 * user that the briven CLI can carry in a `Bearer` header on subsequent
12 * requests. The route accepts either a dashboard session cookie or an
13 * existing CLI bearer (requireAuth() handles both), so the CLI can refresh
14 * its own token without a round-trip through the browser.
15 *
16 * Token semantics live in src/lib/cli-jwt.ts (24h TTL, scope=cli,
17 * issuer=briven-api, audience=briven-cli). Every mint is audited under the
18 * `cli.token.mint` action so an operator can spot unusual issuance patterns.
19 */
20export const authCliRouter = new Hono<AppEnv>();
21
22// Cap mints at 5 per user per 60s window (RATE_LIMIT_WINDOW_MS in
23// services/tiers.ts). A legitimate CLI refresh is a once-a-day event; a
24// burst above 5 is either a script bug or token-grinding worth slowing.
25authCliRouter.post(
26 '/v1/auth/cli-token',
27 requireAuth(),
28 userRateLimit('cli.token.mint', 5),
29 async (c) => {
30 const user = c.get('user');
31 if (!user) {
32 return c.json({ code: 'unauthorized', message: 'authentication required' }, 401);
33 }
34 const token = await signCliToken(user.id);
35 await audit({
36 actorId: user.id,
37 projectId: null,
38 action: 'cli.token.mint',
39 ipHash: hashIp(c.req.raw.headers.get('cf-connecting-ip') ?? null),
40 userAgent: c.req.header('user-agent') ?? null,
41 metadata: { ttlSeconds: 60 * 60 * 24 },
42 });
43 return c.json({ token });
44 },
45);