189 lines · 8.7 KB
import { env } from "cloudflare:workers";
import { cookieValue } from "../auth";
import { decryptSecret, hmacSha256Hex, randomToken, sha256Hex, timingSafeEqualStrings } from "../crypto";
import { enforceRateLimit, requestIp } from "../limits";
import { codeKey, cookieSecret, SITE_LIMITS } from "./config";
import { safeNext, signAccess, verifyAccess } from "./naming";
import { verifySitePassword } from "./password";
import { finishOidc, startOidc } from "./oidc";
import { html, plain } from "./static";
import type { SiteMeta } from "./config";

export const LOGIN_PATH = "/__gitfor/login";
export const CALLBACK_PATH = "/__gitfor/callback";
const LOGOUT_PATH = "/__gitfor/logout";
const STATE_SECONDS = 600;

type CodeRecord = { siteId: string; host: string; subject: string };

function secure(url: URL): boolean {
  return url.protocol === "https:";
}

function name(url: URL, base: string): string {
  return secure(url) ? `__Host-${base}` : base;
}

/** Host-only (no Domain), so the cookie is never shared with the forge or sibling sites. */
function setCookie(url: URL, base: string, value: string, maxAge: number): string {
  return `${name(url, base)}=${encodeURIComponent(value)}; Path=/; HttpOnly;${secure(url) ? " Secure;" : ""} SameSite=Lax; Max-Age=${maxAge}`;
}

function redirect(location: string, cookies: string[] = []): Response {
  const headers = new Headers({ location, "cache-control": "no-store" });
  for (const cookie of cookies) headers.append("set-cookie", cookie);
  return new Response(null, { status: 303, headers });
}

/** The verified visitor for this site and host, or null. Public sites never need a cookie. */
export async function accessSubject(request: Request, meta: SiteMeta): Promise<string | null> {
  const url = new URL(request.url);