@light-auth/express
Version:
light auth framework for express, using arctic
297 lines (289 loc) • 12.9 kB
JavaScript
/*! @light-auth/express v0.3.8 2026-04-03 */
;
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