78 lines · 2.9 KB
import { tokenCredential } from "../token-scope";
import type { RequestCredential } from "../token-scope";
import { agentForToken } from "./grants";
import { protectedResourceMetadataUrl } from "./oauth";
import { RPC } from "./protocol";
import type { AgentIdentity } from "./protocol";
import type { Viewer } from "~/lib/types";

const MAX_AGENT_NAME = 80;

type AgentRow = Awaited<ReturnType<typeof agentForToken>>;

export type Authenticated = {
  credential: RequestCredential;
  viewer: Viewer;
  /** Stable for an OAuth grant's whole life: refreshes rotate the secret, not the row. */
  tokenId: string;
  agentRow: AgentRow;
};

function tokenIdOf(credential: RequestCredential): string | null {
  const grant = credential.grant;
  return grant && grant.kind !== "deploy" ? grant.tokenId : null;
}

/** Bearer personal tokens only (OAuth-issued or PAT); deploy tokens have no user to act for. */
export async function authenticate(request: Request): Promise<Authenticated | null> {
  const credential = await tokenCredential(request);
  const tokenId = credential ? tokenIdOf(credential) : null;
  if (!credential?.viewer || !tokenId) return null;
  const agentRow = await agentForToken(tokenId);
  return { credential, viewer: credential.viewer, tokenId, agentRow };
}

export function unauthorizedResponse(origin: string, error?: string): Response {
  const challenge = [
    `Bearer resource_metadata="${protectedResourceMetadataUrl(origin)}"`,
    ...(error ? [`error="${error}"`] : []),
  ].join(", ");
  return Response.json(