UNPKG

@light-auth/astro

Version:

light auth framework for astro, using arctic

269 lines (261 loc) 11.7 kB
/*! @light-auth/astro v0.3.8 2026-04-03 */ 'use strict'; import { getUserDirect, createSigninServerFunction, createSignoutServerFunction, createHttpHandlerFunction, resolveBasePath, createSetUserServerFunction, createSetSessionServerFunction, createFetchSessionServerFunction, buildFullUrl, encryptJwt, buildSecret, getSessionExpirationMaxAge, DEFAULT_SESSION_NAME, decryptJwt } from '@light-auth/core'; import * as cookieParser from 'cookie'; const createGetAuthSession = (config) => { const getSession = createFetchSessionServerFunction(config); return async (context) => await getSession({ context }); }; const createSetAuthSession = (config) => { const setSession = createSetSessionServerFunction(config); return async (context, session) => await setSession({ context, session }); }; const createGetAuthUser = (config) => { return async (context, providerUserId) => await getUserDirect({ config, providerUserId, context }); }; const createSetAuthUser = (config) => { const setUser = createSetUserServerFunction(config); return async (context, user) => await setUser({ context, user }); }; function createSignin(config) { const signInFunction = createSigninServerFunction(config); return async (context, providerName, callbackUrl = "/") => { return await signInFunction({ providerName, callbackUrl, context }); }; } function createSignout(config) { const signOutFunction = createSignoutServerFunction(config); return async (context, revokeToken = false, callbackUrl = "/") => { return await signOutFunction({ revokeToken, callbackUrl, context }); }; } const createHandler = (config) => { const lightAuthHandler = createHttpHandlerFunction(config); return { GET: async (context) => { const response = await lightAuthHandler({ context }); return response; }, POST: async (context) => { const response = await lightAuthHandler({ context }); return response; }, }; }; function CreateLightAuth(config) { // check if we are on the server side const isServerSide = typeof window === "undefined"; if (!isServerSide) throw new Error("light-auth-nextjs: DO NOT use this function [CreateLightAuth] on the client side as you may expose sensitive data to the client."); if (!config.providers || config.providers.length === 0) throw new Error("At least one provider is required"); // dynamic imports to avoid error if we are on the client side // Using Promise.all to ensure all imports are resolved before any handler is called const pendingImports = []; if (!config.userAdapter && typeof window === "undefined") { pendingImports.push(import('@light-auth/core/adapters').then((module) => { config.userAdapter = module.createLightAuthUserAdapter({ base: "./users_db", isEncrypted: false }); })); } if (!config.sessionStore && typeof window === "undefined") { pendingImports.push(Promise.resolve().then(function () { return astroLightAuthSessionStore$1; }).then((module) => { config.sessionStore = module.astroLightAuthSessionStore; })); } if (!config.router && typeof window === "undefined") { pendingImports.push(Promise.resolve().then(function () { return astroLightAuthRouter$1; }).then((module) => { config.router = module.astroLightAuthRouter; })); } const importsReady = Promise.all(pendingImports); // @ts-ignore config.env = config.env || import.meta.env; config.basePath = resolveBasePath(config.basePath, config.env); if (!config.env || !config.env["LIGHT_AUTH_SECRET_VALUE"]) throw new Error("LIGHT_AUTH_SECRET_VALUE is required in environment variables"); const handler = createHandler(config); // Wrap all async functions to ensure dynamic imports are resolved before executing const ensureReady = (fn) => (async (...args) => { await importsReady; return fn(...args); }); return { providers: config.providers, handlers: { GET: ensureReady(handler.GET), POST: ensureReady(handler.POST), }, basePath: config.basePath, getAuthSession: ensureReady(createGetAuthSession(config)), setAuthSession: ensureReady(createSetAuthSession(config)), getUser: ensureReady(createGetAuthUser(config)), setUser: ensureReady(createSetAuthUser(config)), signIn: ensureReady(createSignin(config)), signOut: ensureReady(createSignout(config)), }; } const astroLightAuthRouter = { returnJson: function ({ data, context, init }) { const json = data ? JSON.stringify(data) : undefined; const status = init?.status ?? 200; return new Response(json, { ...(init ?? {}), status, }); }, getUrl: function ({ endpoint, context, req }) { const request = context?.request ?? req; let url = endpoint; if (!url) url = context?.request?.url || req?.url; if (!url) throw new Error("light-auth: No url provided and no request object available in getUrl of astroLightAuthRouter."); if (url.startsWith("http")) return url; const isServerSide = typeof window === "undefined"; if (!isServerSide) return url; if (!request) return url; const parsedUrl = buildFullUrl({ url, incomingHeaders: request.headers }); return parsedUrl.toString(); }, redirectTo: function ({ url, context }) { if (!context) throw new Error("AstroSharedContext is required in redirectTo function of astroLightAuthRouter"); return context.redirect(url, 302); }, getHeaders: function ({ search, context, req }) { const request = context?.request ?? req; if (!request) return new Headers(); const incomingHeaders = request.headers; // Convert search to RegExp if it's a string const regex = typeof search === "string" ? new RegExp(search) : search; // Create a new Headers object to hold filtered headers const filteredHeaders = new Headers(); // Iterate and filter headers whose names match the regex for (const [key, value] of incomingHeaders.entries()) { if (!search || !regex) filteredHeaders.append(key, value); else if (regex.test(key)) { filteredHeaders.append(key, value); } } return filteredHeaders; }, setCookies: function ({ cookies, context }) { if (!context) throw new Error("AstroSharedContext is required in setCookies of expressLightAuthRouter"); for (const cookie of cookies ?? []) { context.cookies.set(cookie.name, cookie.value, { httpOnly: cookie.httpOnly, secure: cookie.secure, sameSite: cookie.sameSite, maxAge: cookie.maxAge, path: cookie.path, }); } }, getCookies: function ({ search, context }) { if (!context) throw new Error("AstroSharedContext is required in getCookies of expressLightAuthRouter"); const cookies = []; const regex = typeof search === "string" ? new RegExp(search) : search; for (const cookieString of context.cookies.headers()) { const cookie = cookieParser.parse(cookieString); const name = Object.keys(cookie)[0]; const value = cookie[name]; if (!search || !regex || regex.test(name)) cookies.push({ name: name, value: value || "" }); } const cookieString = context.request?.headers?.get("cookie"); if (cookieString) { const cookie = cookieParser.parse(cookieString); for (const cookieString of Object.entries(cookie)) { const [name, value] = cookieString; if (!search || !regex || regex.test(name)) cookies.push({ name: name, value: value || "" }); } } return cookies; }, getRequest: function ({ context }) { if (!context) throw new Error("AstroSharedContext is required in getRequest of expressLightAuthRouter"); return context.request; }, }; var astroLightAuthRouter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, astroLightAuthRouter: astroLightAuthRouter }); /** * A concrete CookieStore implementation for Node.js server-side, * using the 'cookie' npm package and returning Response objects * with appropriate Set-Cookie headers. */ const astroLightAuthSessionStore = { async getSession(args) { const { env, context, req, sessionName } = args; if (!env) throw new Error("light-auth: Env is required in getSession of astroLightAuthSessionStore"); const request = context?.request || req; if (!request) throw new Error("light-auth: Request is required in getSession of astroLightAuthSessionStore"); const cookieHeader = request.headers.get("cookie"); if (!cookieHeader) return null; const requestCookies = cookieParser.parse(cookieHeader); const sessionCookie = requestCookies[sessionName ?? DEFAULT_SESSION_NAME]; if (!sessionCookie) return null; try { const decryptedSession = await decryptJwt(sessionCookie, buildSecret(env)); return decryptedSession; } catch (error) { console.error("Failed to decrypt session cookie:", error); return null; } }, async deleteSession(args) { const { env, context, sessionName } = args; if (!env) throw new Error("light-auth: Env is required in deleteSession of astroLightAuthSessionStore"); if (!context) throw new Error("light-auth: Context is required in deleteSession of astroLightAuthSessionStore"); context.cookies.set(sessionName ?? DEFAULT_SESSION_NAME, "", { maxAge: 0, path: "/", }); }, async setSession(args) { const { env, session, context, sessionName } = args; if (!context) throw new Error("light-auth: Context is required in setSession of astroLightAuthSessionStore"); const value = await encryptJwt(session, buildSecret(env)); // Check the size of the cookie value in bytes const encoder = new TextEncoder(); const valueBytes = encoder.encode(value); if (valueBytes.length > 4096) throw new Error("light-auth: Cookie value exceeds 4096 bytes, which may not be supported by your browser."); // get the cookie expiration time const maxAge = getSessionExpirationMaxAge(); // 30 days if no env var is set // maxAge: Specifies the number (in seconds) to be the value for the `Max-Age` context.cookies.set(sessionName ?? DEFAULT_SESSION_NAME, value, { httpOnly: true, secure: true, sameSite: "lax", path: "/", maxAge: maxAge, }); return session; }, generateSessionId() { return Math.random().toString(36).slice(2); }, }; var astroLightAuthSessionStore$1 = /*#__PURE__*/Object.freeze({ __proto__: null, astroLightAuthSessionStore: astroLightAuthSessionStore }); export { CreateLightAuth, astroLightAuthRouter, astroLightAuthSessionStore, createGetAuthUser, createHandler, createSignin, createSignout }; //# sourceMappingURL=index.mjs.map