UNPKG

@light-auth/express

Version:

light auth framework for express, using arctic

297 lines (289 loc) 12.9 kB
/*! @light-auth/express v0.3.8 2026-04-03 */ 'use strict'; import { createSignoutServerFunction, createHttpHandlerFunction, createFetchSessionServerFunction, resolveBasePath, createSigninServerFunction, createSetUserServerFunction, createSetSessionServerFunction, getUserDirect, buildFullUrl, DEFAULT_SESSION_NAME, encryptJwt, buildSecret, decryptJwt } from '@light-auth/core'; import * as cookieParser from 'cookie'; const createGetAuthSession = (config) => { const getSession = createFetchSessionServerFunction(config); return async (req, res) => await getSession({ req, res }); }; const createSetAuthSession = (config) => { const setSession = createSetSessionServerFunction(config); return async (req, res, session) => await setSession({ req, res, session }); }; const createGetUser = (config) => { return async (req, res, providerUserId) => await getUserDirect({ config, providerUserId, req, res }); }; const createSetUser = (config) => { const setUser = createSetUserServerFunction(config); return async (req, res, user) => await setUser({ req, res, user }); }; function createSignin(config) { const signInFunction = createSigninServerFunction(config); return async (req, res, providerName, callbackUrl = "/") => await signInFunction({ providerName, callbackUrl, req, res }); } function createSignout(config) { const signOut = createSignoutServerFunction(config); return async (req, res, revokeToken = false, callbackUrl = "/") => await signOut({ revokeToken, callbackUrl, req, res }); } const createAuthHandler = (config) => { const lightAuthHandler = createHttpHandlerFunction(config); return async (req, res, next) => { await lightAuthHandler({ req, res, next }); if (!res.headersSent) next(); }; }; const createMiddleware = (config) => { const sessionFunction = createFetchSessionServerFunction(config); return async (req, res, next, ...params) => { const session = await sessionFunction({ req }); res.locals.session = session; return next(); }; }; 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 expressLightAuthSessionStore$1; }).then((module) => { config.sessionStore = module.expressLightAuthSessionStore; })); } if (!config.router && typeof window === "undefined") { pendingImports.push(Promise.resolve().then(function () { return expressLightAuthRouter$1; }).then((module) => { config.router = module.expressLightAuthRouter; })); } const importsReady = Promise.all(pendingImports); // @ts-ignore config.env = config.env || process.env; config.basePath = resolveBasePath(config.basePath, config.env); if (!config.env["LIGHT_AUTH_SECRET_VALUE"]) throw new Error("LIGHT_AUTH_SECRET_VALUE is required in environment variables"); const handlers = createAuthHandler(config); const middleware = createMiddleware(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: ensureReady(handlers), middleware: ensureReady(middleware), basePath: config.basePath, getAuthSession: ensureReady(createGetAuthSession(config)), setAuthSession: ensureReady(createSetAuthSession(config)), getUser: ensureReady(createGetUser(config)), setUser: ensureReady(createSetUser(config)), signIn: ensureReady(createSignin(config)), signOut: ensureReady(createSignout(config)), }; } const expressLightAuthRouter = { returnJson: function ({ res, data, init }) { if (!res) throw new Error("Response is required in writeJson function of expressLightAuthRouter"); const status = init?.status ?? 200; const headers = new Headers(init?.headers); res.status(status); res.setHeaders(headers); res.json(data); return res; }, getRequest: async function (args) { const { req } = args; if (!req) throw new Error("Request is required in getRequest function of expressLightAuthRouter"); const url = await this.getUrl({ ...args }); const headers = await this.getHeaders({ ...args }); return new Request(url, { method: req.method, headers: headers }); }, getUrl: function ({ endpoint, req }) { const url = endpoint ?? req?.url; if (!url) throw new Error("light-auth: No url provided and no request object available in getUrl of expressLightAuthRouter."); if (url.startsWith("http")) return url; const isServerSide = typeof window === "undefined"; if (!isServerSide) return url; if (!req) throw new Error("Request is required in getUrl function of expressLightAuthRouter"); const headers = new Headers(); if (req.headers) { for (const [key, value] of Object.entries(req.headers)) { if (!value) continue; const vals = Array.isArray(value) ? value : [value]; for (const val of vals) { headers.append(key, val); } } } const fullUrl = buildFullUrl({ url, incomingHeaders: headers }); return fullUrl.toString(); }, getCookies: function ({ search, req }) { if (!req) throw new Error("Request is required in getCookies function of expressLightAuthRouter"); const incomingCookies = req.headers?.cookie; if (!incomingCookies) return []; // parse the cookies const parsedCookies = cookieParser.parse(incomingCookies); const cookieArray = Object.entries(parsedCookies).map(([name, value]) => ({ name, value: value || "" })); if (!incomingCookies) return []; const searchRegex = typeof search === "string" ? new RegExp(search, "i") : search; return cookieArray.filter((cookie) => { if (!cookie.name || !cookie.value) return false; if (!search || !searchRegex) return true; return searchRegex.test(cookie.name); }); }, getHeaders: function ({ search, req }) { if (!req) throw new Error("Request is required in getHeaders function of expressLightAuthRouter"); const incomingHeaders = req.headers; if (!incomingHeaders) return new Headers(); const searchRegex = typeof search === "string" ? new RegExp(search, "i") : search; const headers = new Headers(); if (incomingHeaders) { for (const [key, value] of Object.entries(incomingHeaders)) { if (!value) continue; const vals = Array.isArray(value) ? value : [value]; for (const val of vals) { if (!search || !searchRegex) headers.append(key, val); else if (searchRegex.test(key)) { headers.append(key, val); } } } } return headers; }, setCookies: function ({ res, cookies, init }) { if (!res) throw new Error("Response is required in setCookies of expressLightAuthRouter"); const status = init?.status ?? 200; const headers = new Headers(init?.headers); res.status(status); res.setHeaders(headers); if (cookies) { for (const cookie of cookies) { res.cookie(cookie.name, cookie.value, { // unfortunately, express set maxAge in milliseconds (not seconds) maxAge: cookie.maxAge ? cookie.maxAge * 1000 : 1000 * 60 * 10, httpOnly: cookie.httpOnly, secure: cookie.secure, sameSite: cookie.sameSite, path: cookie.path, }); } } return res; }, redirectTo: function ({ req, res, url }) { if (!res) throw new Error("Response is required in redirectTo of expressLightAuthRouter"); if (!req) throw new Error("Request is required in redirectTo of expressLightAuthRouter"); if (url.startsWith("http")) return res.redirect(url); // get headers from the incoming request // and build the full url const incomingHeaders = new Headers(); if (req?.headers) { for (const [key, value] of Object.entries(req.headers)) { if (value === undefined) continue; if (Array.isArray(value)) { value.forEach((v) => incomingHeaders.append(key, v)); } else { incomingHeaders.append(key, value); } } } const fullUrl = buildFullUrl({ url, incomingHeaders }); res.redirect(fullUrl.toString()); }, }; var expressLightAuthRouter$1 = /*#__PURE__*/Object.freeze({ __proto__: null, expressLightAuthRouter: expressLightAuthRouter }); /** * A concrete CookieStore implementation for express, */ const expressLightAuthSessionStore = { getSession: async function ({ env, basePath, req, sessionName, }) { if (!req) throw new Error("Request is required in getSession function of expressLightAuthSessionStore"); const incomingCookies = req.headers?.cookie; if (!incomingCookies) return null; // parse the cookies const parsedCookies = cookieParser.parse(incomingCookies); const cookieName = sessionName ?? DEFAULT_SESSION_NAME; const sessionString = parsedCookies[cookieName]; if (!sessionString) return null; try { const decryptedSession = await decryptJwt(sessionString, buildSecret(env)); return decryptedSession; } catch (error) { console.error("Failed to decrypt session cookie:", error); return null; } }, setSession: async function ({ env, basePath, res, session, sessionName, }) { if (!res) throw new Error("Response is required in setSession of expressLightAuthSessionStore"); 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 res.cookie(sessionName ?? DEFAULT_SESSION_NAME, value, { httpOnly: true, secure: true, sameSite: "lax", path: "/", expires: new Date(session.expiresAt), }); return session; }, deleteSession: function ({ res, sessionName, }) { if (!res) throw new Error("Response is required in deleteSession of expressLightAuthSessionStore"); res.clearCookie(sessionName ?? DEFAULT_SESSION_NAME); }, generateSessionId: function () { return Math.random().toString(36).substring(2, 15); }, }; var expressLightAuthSessionStore$1 = /*#__PURE__*/Object.freeze({ __proto__: null, expressLightAuthSessionStore: expressLightAuthSessionStore }); export { CreateLightAuth, createAuthHandler, createMiddleware, createSignout, expressLightAuthRouter, expressLightAuthSessionStore }; //# sourceMappingURL=index.mjs.map