CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
agent-auth.ts
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| e75eddc | 1 | /** |
| 2 | * Agent multiplayer — Bearer-token auth middleware. | |
| 3 | * | |
| 4 | * Sits in front of the v2 API. When the caller presents | |
| 5 | * `Authorization: Bearer agt_<hex>`, we resolve the row from | |
| 6 | * `agent_sessions` and stash it on the request context as | |
| 7 | * `c.set("agent", session)`. Downstream handlers can then: | |
| 8 | * - charge the session's budget, | |
| 9 | * - enforce the branch namespace on ref updates, | |
| 10 | * - rate-limit by agent rather than by user. | |
| 11 | * | |
| 12 | * If the header isn't an `agt_` token (or it doesn't validate), we | |
| 13 | * call next() unchanged so the regular `apiAuth` middleware can pick | |
| 14 | * up the PAT/OAuth/session flow. | |
| 15 | * | |
| 16 | * The middleware never rejects on a bad agent token — it just falls | |
| 17 | * through and lets the canonical auth middleware decide. This keeps | |
| 18 | * the matrix of "what the caller can do" centralised in `apiAuth`. | |
| 19 | */ | |
| 20 | ||
| 21 | import { createMiddleware } from "hono/factory"; | |
| 22 | import { | |
| 23 | AGENT_TOKEN_PREFIX, | |
| 24 | authenticateAgent, | |
| 25 | isAgentToken, | |
| 26 | refIsInNamespace, | |
| 27 | } from "../lib/agent-multiplayer"; | |
| 28 | import type { AgentSession } from "../db/schema"; | |
| 29 | ||
| 30 | export type AgentAuthEnv = { | |
| 31 | Variables: { | |
| 32 | agent?: AgentSession | null; | |
| 33 | }; | |
| 34 | }; | |
| 35 | ||
| 36 | /** | |
| 37 | * Detect and resolve an agent Bearer token. Always calls next(); | |
| 38 | * downstream auth handles the non-agent paths. | |
| 39 | */ | |
| 40 | export const agentAuth = createMiddleware<AgentAuthEnv>(async (c, next) => { | |
| 41 | const authHeader = c.req.header("Authorization"); | |
| 42 | if (!authHeader?.startsWith("Bearer ")) { | |
| 43 | return next(); | |
| 44 | } | |
| 45 | const token = authHeader.slice(7).trim(); | |
| 46 | if (!token.startsWith(AGENT_TOKEN_PREFIX) || !isAgentToken(token)) { | |
| 47 | return next(); | |
| 48 | } | |
| 49 | ||
| 50 | const session = await authenticateAgent(token); | |
| 51 | if (session) { | |
| 52 | c.set("agent", session); | |
| 53 | } | |
| 54 | return next(); | |
| 55 | }); | |
| 56 | ||
| 57 | /** | |
| 58 | * Guard: require an authenticated agent on this route. Returns 401 | |
| 59 | * when no agent is on the context. | |
| 60 | */ | |
| 61 | export const requireAgentAuth = createMiddleware<AgentAuthEnv>( | |
| 62 | async (c, next) => { | |
| 63 | const agent = c.get("agent"); | |
| 64 | if (!agent) { | |
| 65 | return c.json( | |
| 66 | { | |
| 67 | error: "Agent authentication required", | |
| 68 | hint: "Use Authorization: Bearer agt_<token>", | |
| 69 | }, | |
| 70 | 401 | |
| 71 | ); | |
| 72 | } | |
| 73 | return next(); | |
| 74 | } | |
| 75 | ); | |
| 76 | ||
| 77 | /** | |
| 78 | * Branch-namespace guard for PATCH /repos/:owner/:repo/git/refs/heads/:branch. | |
| 79 | * When the caller authenticated as an agent, the target branch must sit | |
| 80 | * under the agent's `branch_namespace` prefix. Non-agent callers | |
| 81 | * (sessions, PATs) are passed through unchanged. | |
| 82 | * | |
| 83 | * This is mounted on the v2 git-refs path; it never blocks regular | |
| 84 | * humans. | |
| 85 | */ | |
| 86 | export const enforceAgentBranchNamespace = createMiddleware<AgentAuthEnv>( | |
| 87 | async (c, next) => { | |
| 88 | const agent = c.get("agent"); | |
| 89 | if (!agent) return next(); | |
| 90 | const branch = c.req.param("branch"); | |
| 91 | if (!branch) return next(); | |
| 92 | if (!refIsInNamespace(branch, agent.branchNamespace)) { | |
| 93 | return c.json( | |
| 94 | { | |
| 95 | error: "Branch is outside the agent's namespace", | |
| 96 | namespace: agent.branchNamespace, | |
| 97 | branch, | |
| 98 | }, | |
| 99 | 403 | |
| 100 | ); | |
| 101 | } | |
| 102 | return next(); | |
| 103 | } | |
| 104 | ); |