123 lines · 4.3 KB
import { sha256Hex } from "./crypto";
import { accountDb } from "./scoped-db";
import { forgetLocator, locate, registerLocator } from "./scoped-db/locators";

/** Tokens issued before anyone is signed in (a passkey sign-in challenge) live in this pseudo-account. */
export const ANONYMOUS_AUTH_SCOPE = "_anonymous";

async function tokenScope(tokenHash: string) {
  const scopeId = await locate("auth_token", tokenHash);
  return scopeId ? accountDb(scopeId) : null;
}

export type AuthTokenKind =
  | "email-verify"
  | "email-verify-spent"
  | "password-reset"
  | "webauthn"
  | "pending-login"
  | "editor-session"
  | "editor-preview"
  | "mcp-authz";

type Row = { user_id: string | null; payload: string; created_at: number };
export type ConsumedToken = { userId: string | null; payload: string; createdAt: number };

/** Stores single-use state keyed by the hash of a secret token. */
export async function storeAuthToken(
  kind: AuthTokenKind,
  token: string,
  userId: string | null,
  payload: string,
  ttlSeconds: number,
): Promise<void> {
  const now = Date.now();
  const tokenHash = await sha256Hex(token);
  const scopeId = userId ?? ANONYMOUS_AUTH_SCOPE;
  await accountDb(scopeId).run(
    "INSERT INTO auth_tokens (token_hash, kind, user_id, payload, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)",
    tokenHash,
    kind,