import { env } from "cloudflare:workers";
import { z } from "zod";
import { all, chunks, first, placeholders, run } from "../db";
import { randomId } from "../crypto";
import { enforceRateLimit, requestIp } from "../limits";
import { assertResolvesPublic } from "../net/resolve-guard";
const MAX_REDIRECT_URIS = 10;
const CIMD_FETCH_TIMEOUT_MS = 5000;
const CIMD_MAX_BYTES = 16 * 1024;
const CIMD_REFRESH_MS = 24 * 60 * 60 * 1000;
/** Unused RFC 7591 registrations are dropped after this; used ones after this long without a token issued (AUTH-A9). */
const DCR_UNUSED_TTL_MS = 24 * 60 * 60 * 1000;
const DCR_IDLE_TTL_MS = 90 * 24 * 60 * 60 * 1000;
const LOOPBACK = new Set(["localhost", "127.0.0.1", "[::1]"]);
const BLOCKED_SCHEMES = new Set(["javascript:", "data:", "file:", "vbscript:", "blob:", "about:", "ws:", "wss:"]);
export type OAuthClient = { id: string; name: string; redirectUris: string[] };
type ClientRow = { id: string; name: string; redirect_uris: string; kind: "dcr" | "cimd"; created_at: number };
export class OAuthError extends Error {
constructor(
readonly error: string,
message: string,
readonly status = 400,
) {
super(message);
}
}
/** https anywhere, plain http only on loopback (RFC 8252), or a private-use scheme for native apps. */
function validRedirectUri(raw: string): boolean {
let url: URL;
try {
url = new URL(raw);
} catch {
return false;
}
if (url.hash || BLOCKED_SCHEMES.has(url.protocol)) return false;