UNPKG

@light-auth/express

Version:

light auth framework for express, using arctic

290 lines (282 loc) 12.2 kB
/*! @light-auth/express v0.3.1 2025-06-13 */ 'use strict'; import { createSignoutServerFunction, createHttpHandlerFunction, createFetchSessionServerFunction, resolveBasePath, createSigninServerFunction, createSetUserServerFunction, createFetchUserServerFunction, createSetSessionServerFunction, 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) => { const getUser = createFetchUserServerFunction(config); return async (req, res, providerUserId) => await getUser({ req, res, providerUserId }); }; 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 if (!config.userAdapter && typeof window === "undefined") { import('@light-auth/core/adapters').then((module) => { config.userAdapter = module.createLightAuthUserAdapter({ base: "./users_db", isEncrypted: false }); }); } if (!config.sessionStore && typeof window === "undefined") { Promise.resolve().then(function () { return expressLightAuthSessionStore$1; }).then((module) => { config.sessionStore = module.expressLightAuthSessionStore; }); } if (!config.router && typeof window === "undefined") { Promise.resolve().then(function () { return expressLightAuthRouter$1; }).then((module) => { config.router = module.expressLightAuthRouter; }); } // @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"); return { providers: config.providers, handlers: createAuthHandler(config), middleware: createMiddleware(config), basePath: config.basePath, getAuthSession: createGetAuthSession(config), setAuthSession: createSetAuthSession(config), getUser: createGetUser(config), setUser: createSetUser(config), signIn: createSignin(config), signOut: 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, }) { 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 sessionString = parsedCookies[DEFAULT_SESSION_NAME]; 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, }) { 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(DEFAULT_SESSION_NAME, value, { httpOnly: true, secure: true, sameSite: "lax", path: "/", expires: new Date(session.expiresAt), }); return session; }, deleteSession: function ({ res, }) { if (!res) throw new Error("Response is required in deleteSession of expressLightAuthSessionStore"); res.clearCookie(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