Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889import { seal, sha256Base64Url } from "$lib/server/sealed";import { internalUrl } from "$lib/auth/returnTo";import type { GitHubEnv } from "./env";
export const CALLBACK_PATH = "/_internal/github/callback";
export interface GitHubState { landing: Landing; did: string; verifier: string; exp: number;}
const STATE_TTL_MS = 10 * 60_000;
export const newVerifier = () => crypto.randomUUID().replaceAll("-", "") + crypto.randomUUID().replaceAll("-", "");
export const pkceChallenge = (verifier: string) => sha256Base64Url(verifier);
export const sealState = (secret: string, state: Omit<GitHubState, "exp">) => seal({ ...state, exp: Date.now() + STATE_TTL_MS }, secret);
export const authorizeUrl = (env: GitHubEnv, origin: string, state: string, challenge: string) => { const target = new URL(`${env.publicOrigin}/login/oauth/authorize`); target.searchParams.set("client_id", env.clientId); target.searchParams.set("redirect_uri", `${origin}${CALLBACK_PATH}`); target.searchParams.set("state", state); target.searchParams.set("code_challenge", challenge); target.searchParams.set("code_challenge_method", "S256"); return target.toString();};
export const installUrl = (env: GitHubEnv, state: string) => `${env.publicOrigin}/apps/${encodeURIComponent(env.appSlug)}/installations/new?state=${encodeURIComponent(state)}`;
export const tokenFromCode = async ( env: GitHubEnv, code: string, verifier: string, redirectUri: string, fetcher: typeof fetch = fetch) => { const response = await fetcher(`${env.publicOrigin}/login/oauth/access_token`, { method: "POST", headers: { accept: "application/json", "content-type": "application/json" }, body: JSON.stringify({ client_id: env.clientId, client_secret: env.clientSecret, code, code_verifier: verifier, redirect_uri: redirectUri }) }); if (!response.ok) throw new Error(`oauth upstream ${response.status}`); const json = (await response.json()) as { access_token?: string }; if (!json.access_token) throw new Error("oauth token missing"); return json.access_token;};
export type Landing = | { readonly kind: "page"; readonly href: string } | { readonly kind: "home" } | { readonly kind: "refused" };
export const HOME_LANDING: Landing = { kind: "home" };
// allowed host pages for github connect round-tripsconst HOST_PAGES = new Set(["/repo/import/github", "/repo/migrate", "/repo/new", "/welcome"]);
const NOTICE_PARAM = "github_error";const REFUSED = "return_unexpected";const INTERNAL_ORIGIN = "https://tangled.invalid";
export const landingFrom = (raw: string | null): Landing => { if (!raw) return HOME_LANDING; const url = internalUrl(raw); if (!url || !HOST_PAGES.has(url.pathname)) return { kind: "refused" }; url.searchParams.delete(NOTICE_PARAM); return { kind: "page", href: url.pathname + url.search };};
export const landingHref = (landing: Landing, notice?: string): string => { const url = new URL(landing.kind === "page" ? landing.href : "/", INTERNAL_ORIGIN); if (landing.kind === "refused") url.searchParams.set(NOTICE_PARAM, REFUSED); if (notice) url.searchParams.set(NOTICE_PARAM, notice); return url.pathname + url.search;};