76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import type { APIContext } from "astro";
|
|
import { getOptions } from "../runtime.js";
|
|
import {
|
|
generateCodeVerifier,
|
|
codeChallengeS256,
|
|
generateState,
|
|
generateNonce,
|
|
} from "../lib/pkce.js";
|
|
import { serializeCookie } from "../lib/cookies.js";
|
|
|
|
async function discover(issuer: string) {
|
|
const res = await fetch(
|
|
new URL("/.well-known/openid-configuration", issuer).toString(),
|
|
);
|
|
if (!res.ok) throw new Error(`OIDC discovery failed: ${res.status}`);
|
|
return (await res.json()) as {
|
|
authorization_endpoint: string;
|
|
};
|
|
}
|
|
|
|
function inferRedirectUri(
|
|
options: ReturnType<typeof getOptions>,
|
|
reqUrl: URL,
|
|
): string {
|
|
if ("absolute" in options.redirectUri) return options.redirectUri.absolute;
|
|
const u = new URL(options.routes.callback, reqUrl);
|
|
return u.toString();
|
|
}
|
|
|
|
export async function GET(ctx: APIContext) {
|
|
const options = getOptions();
|
|
const { url } = ctx;
|
|
const verifier = generateCodeVerifier();
|
|
const challenge = await codeChallengeS256(verifier);
|
|
const state = generateState();
|
|
const nonce = generateNonce();
|
|
|
|
const returnTo = url.searchParams.get("return_to") || undefined;
|
|
|
|
const initPayload = JSON.stringify({
|
|
state,
|
|
nonce,
|
|
verifier,
|
|
returnTo,
|
|
});
|
|
const initCookieName = `${options.cookie.name}_init`;
|
|
const cookie = serializeCookie(initCookieName, initPayload, {
|
|
path: options.cookie.path,
|
|
domain: options.cookie.domain,
|
|
sameSite: "Lax",
|
|
secure: options.cookie.secure,
|
|
httpOnly: true,
|
|
maxAge: 5 * 60, // 5 minutes
|
|
});
|
|
|
|
const disco = await discover(options.issuer);
|
|
const redirectUri = inferRedirectUri(options, url);
|
|
const authorize = new URL(disco.authorization_endpoint);
|
|
authorize.searchParams.set("client_id", options.clientId);
|
|
authorize.searchParams.set("redirect_uri", redirectUri);
|
|
authorize.searchParams.set("response_type", "code");
|
|
authorize.searchParams.set("scope", options.scopes);
|
|
authorize.searchParams.set("code_challenge", challenge);
|
|
authorize.searchParams.set("code_challenge_method", "S256");
|
|
authorize.searchParams.set("state", state);
|
|
authorize.searchParams.set("nonce", nonce);
|
|
|
|
return new Response(null, {
|
|
status: 302,
|
|
headers: {
|
|
Location: authorize.toString(),
|
|
"Set-Cookie": cookie,
|
|
},
|
|
});
|
|
}
|