import { createMiddleware } from 'hono/factory' import { type DatabaseInput, resolveDatabase } from '../db/binding.js' import type { Database } from '../db/types.js' import { type AccessGrant, authenticateAccessToken } from './access-token.js' import { requireBrokerApiKey, resolveBrokerConfig } from './config.js' import { safeEqual } from './crypto.js' import { BrokerError } from './errors.js' export type BrokerContext = { Bindings: Bindings Variables: { db: Database accessGrant: AccessGrant } } /** * Resolves the database for the request. * * The binding is supplied when Hookfish is constructed. It may be a ready * Hookfish database or a runtime-aware binding that resolves from `c.env`. */ export function withDatabase( database: DatabaseInput, ) { return createMiddleware>(async (c, next) => { c.set('db', await resolveDatabase(database, c.env)) await next() }) } /** * Guards every endpoint that can mint an authorization or read a token. * The OAuth callback is deliberately exempt: it is hit by the user's browser * and is authenticated instead by the single-use `state` value. */ export function requireApiKey() { return createMiddleware>(async (c, next) => { const expected = requireBrokerApiKey(resolveBrokerConfig(c.env)) const header = c.req.header('Authorization') ?? '' const presented = header.startsWith('Bearer ') ? header.slice(7) : '' if (!presented) { throw new BrokerError( 401, 'unauthorized', 'Missing Authorization header. Send: Authorization: Bearer ', ) } const accessGrant: AccessGrant = safeEqual(presented, expected) ? { kind: 'root', scopes: ['**'] } : await authenticateAccessToken(c.get('db'), expected, presented) c.set('accessGrant', accessGrant) await next() }) }