diff --git a/src/middleware.ts b/src/middleware.ts index 779141c..5581aa7 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -21,6 +21,11 @@ function escapeRegex(str: string): string { } function globToRegExp(pattern: string): RegExp { + if (pattern.endsWith("/*")) { + const base = pattern.slice(0, -2); + const baseEscaped = escapeRegex(base); + return new RegExp(`^${baseEscaped}(?:/.*)?$`); + } const escaped = pattern.split("*").map(escapeRegex).join(".*"); return new RegExp(`^${escaped}$`); } @@ -38,12 +43,17 @@ export const onRequest: MiddlewareHandler = async (context, next) => { locals.user = null; if (token) { - const res = await verifyAndDecode<{ sub: string; email?: string }>( - token, - options.cookie.signingSecret, - ); + const res = await verifyAndDecode<{ + sub: string; + email?: string; + firstName?: 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 }; + locals.user = { + sub: res.payload.sub, + email: res.payload.email, + firstName: res.payload.firstName, + }; } } diff --git a/src/routes/callback.ts b/src/routes/callback.ts index c91472c..348c064 100644 --- a/src/routes/callback.ts +++ b/src/routes/callback.ts @@ -127,9 +127,13 @@ export async function GET(ctx: APIContext) { audience: options.clientId, }); + const givenName = + typeof payload["given_name"] === "string" ? payload["given_name"] : undefined; + const session = { sub: String(payload.sub), email: typeof payload.email === "string" ? payload.email : undefined, + firstName: givenName, }; const signed = await signPayload(session, options.cookie.signingSecret); diff --git a/src/routes/logout.ts b/src/routes/logout.ts index f3667d1..6fa97e3 100644 --- a/src/routes/logout.ts +++ b/src/routes/logout.ts @@ -42,6 +42,39 @@ function safeReturnTo(reqUrl: URL, returnTo: string | undefined): string { } } +function escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function globToRegExp(pattern: string): RegExp { + if (pattern.endsWith("/*")) { + const base = pattern.slice(0, -2); + const baseEscaped = escapeRegex(base); + return new RegExp(`^${baseEscaped}(?:/.*)?$`); + } + 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)); +} + +function finalizePostLogoutReturnTo( + options: ReturnType, + reqUrl: URL, + returnTo: string | undefined, +): string { + const safe = safeReturnTo(reqUrl, returnTo); + try { + const u = new URL(safe, reqUrl); + if (isProtected(u.pathname, options.protected)) return new URL("/", reqUrl).toString(); + return u.toString(); + } catch { + return new URL("/", reqUrl).toString(); + } +} + function inferPostLogoutRedirectUri( options: ReturnType, reqUrl: URL, @@ -88,7 +121,9 @@ export async function GET(ctx: APIContext) { const parsed = cookie ? parseLogoutCookie(cookie) : null; const ok = parsed && parsed.state === state; - const location = ok ? safeReturnTo(url, parsed.returnTo) : "/"; + const location = ok + ? finalizePostLogoutReturnTo(options, url, parsed.returnTo) + : "/"; const headers = new Headers(); headers.set("Location", location); @@ -115,7 +150,11 @@ export async function GET(ctx: APIContext) { disco.end_session_endpoint || new URL("/logout", options.issuer).toString(); const state = generateState(); - const returnTo = url.searchParams.get("return_to") || undefined; + const returnTo = finalizePostLogoutReturnTo( + options, + url, + url.searchParams.get("return_to") || undefined, + ); const cookieValue = JSON.stringify({ state, returnTo }); const setLogout = serializeCookie(logoutCookieName, cookieValue, { path: options.cookie.path, diff --git a/types/astro.locals.d.ts b/types/astro.locals.d.ts index aab5c1b..058fe64 100644 --- a/types/astro.locals.d.ts +++ b/types/astro.locals.d.ts @@ -1,7 +1,7 @@ declare global { namespace App { interface Locals { - user?: { sub: string; email?: string } | null; + user?: { sub: string; email?: string; firstName?: string } | null; } } }