Files
astro-oidc-rp/src/middleware.ts

59 lines
1.9 KiB
TypeScript

import type { MiddlewareHandler } from "astro";
import { getOptions } from "./runtime.js";
import { verifyAndDecode } from "./lib/sign.js";
function parseCookies(cookieHeader: string | null): Record<string, string> {
const out: Record<string, string> = {};
if (!cookieHeader) return out;
const parts = cookieHeader.split(/;\s*/);
for (const part of parts) {
const idx = part.indexOf("=");
if (idx === -1) continue;
const name = decodeURIComponent(part.slice(0, idx));
const val = decodeURIComponent(part.slice(idx + 1));
out[name] = val;
}
return out;
}
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function globToRegExp(pattern: string): RegExp {
const escaped = pattern.split("*").map(escapeRegex).join(".*");
return new RegExp(`^${escaped}$`);
}
function isProtected(pathname: string, patterns: string[]): boolean {
return patterns.some((p) => globToRegExp(p).test(pathname));
}
export const onRequest: MiddlewareHandler = async (context, next) => {
const { request, locals, url } = context;
const options = getOptions();
const cookieName = options.cookie.name;
const cookies = parseCookies(request.headers.get("cookie"));
const token = cookies[cookieName];
locals.user = null;
if (token) {
const res = await verifyAndDecode<{ sub: string; email?: string }>(
token,
options.cookie.signingSecret,
);
if (res.valid && res.payload && typeof res.payload.sub === "string") {
locals.user = { sub: res.payload.sub, email: res.payload.email };
}
}
if (isProtected(url.pathname, options.protected) && !locals.user) {
const returnTo = url.pathname + (url.search || "");
const loginUrl = new URL(options.routes.login, url);
loginUrl.searchParams.set("return_to", returnTo);
return Response.redirect(loginUrl, 302);
}
return next();
};