144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
import type { AstroIntegration } from "astro";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export type EnvString = {
|
|
env: string;
|
|
fallback?: string;
|
|
};
|
|
|
|
export type RequiredSecret = {
|
|
env: string;
|
|
};
|
|
|
|
export type OidcIntegrationOptions = {
|
|
issuer: EnvString;
|
|
clientId: EnvString;
|
|
scopes?: string; // default "openid email profile"
|
|
routes?: {
|
|
login?: string;
|
|
callback?: string;
|
|
logout?: string;
|
|
logoutCallback?: string;
|
|
}; // defaults: /login, /oidc/callback, /logout, /logout/callback
|
|
redirectUri?: { mode: "infer-from-request" } | { absolute: string };
|
|
cookie: {
|
|
name?: string;
|
|
sameSite?: "Lax" | "Strict" | "None";
|
|
secure?: boolean;
|
|
domain?: string;
|
|
path?: string;
|
|
signingSecret: RequiredSecret;
|
|
maxAgeSec?: number;
|
|
};
|
|
protected?: string[]; // patterns to guard, e.g., ["/app/*", "/me"]
|
|
};
|
|
|
|
export type OidcInjectedOptions = {
|
|
issuerEnv: string;
|
|
issuerFallback?: string;
|
|
clientIdEnv: string;
|
|
clientIdFallback?: string;
|
|
scopes: string;
|
|
routes: { login: string; callback: string; logout: string; logoutCallback: string };
|
|
redirectUri: { mode: "infer-from-request" } | { absolute: string };
|
|
cookie: {
|
|
name: string;
|
|
sameSite: "Lax" | "Strict" | "None";
|
|
secure: boolean;
|
|
path: string;
|
|
signingSecretEnv: string;
|
|
domain?: string;
|
|
maxAgeSec?: number;
|
|
};
|
|
protected: string[];
|
|
};
|
|
|
|
function requireEnvName(name: string, label: string): string {
|
|
if (typeof name !== "string" || name.trim().length === 0)
|
|
throw new Error(`@resuely/astro-oidc-rp: ${label} must be a non-empty string`);
|
|
return name;
|
|
}
|
|
|
|
function resolveOptions(user: OidcIntegrationOptions): OidcInjectedOptions {
|
|
const routes = {
|
|
login: user.routes?.login ?? "/login",
|
|
callback: user.routes?.callback ?? "/oidc/callback",
|
|
logout: user.routes?.logout ?? "/logout",
|
|
logoutCallback:
|
|
user.routes?.logoutCallback ?? `${user.routes?.logout ?? "/logout"}/callback`,
|
|
};
|
|
const cookie = {
|
|
name: user.cookie?.name ?? "oidc_session",
|
|
sameSite: user.cookie?.sameSite ?? "Lax",
|
|
secure: user.cookie?.secure ?? true,
|
|
path: user.cookie?.path ?? "/",
|
|
signingSecretEnv: requireEnvName(
|
|
user.cookie?.signingSecret?.env,
|
|
"cookie.signingSecret.env",
|
|
),
|
|
domain: user.cookie?.domain,
|
|
maxAgeSec: user.cookie?.maxAgeSec,
|
|
};
|
|
|
|
return {
|
|
issuerEnv: requireEnvName(user.issuer.env, "issuer.env"),
|
|
issuerFallback:
|
|
typeof user.issuer.fallback === "string" ? user.issuer.fallback : undefined,
|
|
clientIdEnv: requireEnvName(user.clientId.env, "clientId.env"),
|
|
clientIdFallback:
|
|
typeof user.clientId.fallback === "string"
|
|
? user.clientId.fallback
|
|
: undefined,
|
|
scopes: user.scopes ?? "openid email profile",
|
|
routes,
|
|
redirectUri: user.redirectUri ?? { mode: "infer-from-request" },
|
|
cookie,
|
|
protected: user.protected ?? [],
|
|
};
|
|
}
|
|
|
|
export default function resuelyOidc(
|
|
options: OidcIntegrationOptions,
|
|
): AstroIntegration {
|
|
const resolved = resolveOptions(options);
|
|
return {
|
|
name: "@resuely/astro-oidc-rp",
|
|
hooks: {
|
|
"astro:config:setup": ({ injectRoute, addMiddleware, updateConfig }) => {
|
|
// Provide runtime options to middleware/routes via Vite define replacement
|
|
updateConfig({
|
|
vite: {
|
|
define: {
|
|
__RESUELY_OIDC_OPTIONS: JSON.stringify(resolved),
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add middleware (run early to populate locals and guard protected patterns)
|
|
addMiddleware({
|
|
entrypoint: fileURLToPath(new URL("./middleware.js", import.meta.url)),
|
|
order: "pre",
|
|
});
|
|
|
|
// Inject routes
|
|
injectRoute({
|
|
pattern: resolved.routes.login,
|
|
entrypoint: fileURLToPath(new URL("./routes/login.js", import.meta.url)),
|
|
});
|
|
injectRoute({
|
|
pattern: resolved.routes.callback,
|
|
entrypoint: fileURLToPath(new URL("./routes/callback.js", import.meta.url)),
|
|
});
|
|
injectRoute({
|
|
pattern: resolved.routes.logout,
|
|
entrypoint: fileURLToPath(new URL("./routes/logout.js", import.meta.url)),
|
|
});
|
|
injectRoute({
|
|
pattern: resolved.routes.logoutCallback,
|
|
entrypoint: fileURLToPath(new URL("./routes/logout.js", import.meta.url)),
|
|
});
|
|
},
|
|
},
|
|
};
|
|
}
|