UNPKG

@light-auth/core

Version:

light auth core framework agnostic, using arctic

136 lines (129 loc) 5.65 kB
/*! @light-auth/core v0.3.1 2025-06-13 */ 'use strict'; import { EncryptJWT, jwtDecrypt } from 'jose'; import { promises } from 'node:fs'; import { resolve } from 'node:path'; // export async function createJwt(payload: JWTPayload): Promise<JWTPayload> { // const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setExpirationTime("30 days").setIssuedAt().sign(SECRET); // const jwt = await parseJwt(token); // return jwt; // } // export async function stringifyJwt(payload: JWTPayload): Promise<string> { // const token = await new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setExpirationTime("30 days").setIssuedAt().sign(SECRET); // return token; // } // export async function parseJwt(token: string): Promise<JWTPayload> { // const { payload } = await jwtVerify(token, SECRET); // return payload; // } async function encryptJwt(payload, secret) { const token = await new EncryptJWT(payload).setProtectedHeader({ alg: "dir", enc: "A128CBC-HS256" }).encrypt(new TextEncoder().encode(secret)); return token; } async function decryptJwt(token, secret) { const { payload } = await jwtDecrypt(token, new TextEncoder().encode(secret)); return payload; } const INTERNAL_SECRET_VALUE = "_HARD_CODED_SECRET_32_CHARS_LONG_!@#123"; function buildSecret(env) { if (!env) throw new Error("light-auth: config.env is required"); if (!env.LIGHT_AUTH_SECRET_VALUE) throw new Error("light-auth: environment variable LIGHT_AUTH_SECRET_VALUE is required"); const secret = (env.LIGHT_AUTH_SECRET_VALUE + INTERNAL_SECRET_VALUE).slice(0, 32); if (secret.length < 32) throw new Error("light-auth: secret must be at least 32 characters long"); return secret; } /** * A concrete SessionStore implementation for Node.js server-side, * using the node:fs package to set, get, and delete session files. * Supports optional encryption of the session object. */ const createLightAuthUserAdapter = ({ base, isEncrypted = false }) => { const sanitizeKey = (key) => { return key.replace(/[^a-zA-Z0-9-_]/g, "_"); // Only allow alphanumeric, dash, and underscore for file safety }; base = base || "./"; return { async getUser({ providerUserId, env, }) { const safeId = sanitizeKey(providerUserId.toString()); const filePath = resolve(base, safeId + ".json"); const exists = await promises .access(filePath) .then(() => true) .catch(() => false); if (!exists) return null; try { const data = await promises.readFile(filePath, "utf-8"); if (isEncrypted) { // Decrypt the stored JWT string to get the session object const payload = await decryptJwt(data, buildSecret(env)); return payload; } else { // Parse the plain JSON session object return JSON.parse(data); } } catch { return null; } }, async setUser({ env, basePath, user, }) { if (!user?.providerUserId) throw new Error("light-auth: user id is required"); const safeId = sanitizeKey(user.providerUserId.toString()); const filePath = resolve(base, safeId + ".json"); const exists = await promises .access(filePath) .then(() => true) .catch(() => false); // some providers may not return the refresh token or access token, if you did not explicitly logout the last time // using the revokeToken option. if (exists) { const existingUser = await this.getUser({ env, basePath, providerUserId: user.providerUserId }); if (existingUser && existingUser.providerUserId === user.providerUserId) { if (existingUser.refreshToken && !user.refreshToken) user.refreshToken = existingUser.refreshToken; if (existingUser.accessToken && !user.accessToken) { user.accessToken = existingUser.accessToken; user.accessTokenExpiresAt = existingUser.accessTokenExpiresAt; } } } await promises.mkdir(base, { recursive: true }); if (isEncrypted) { // Encrypt the session object and store as a JWT string const jwt = await encryptJwt(user, buildSecret(env)); await promises.writeFile(filePath, jwt, "utf-8"); } else { // Store the session object as plain JSON await promises.writeFile(filePath, JSON.stringify(user), "utf-8"); } return user; }, async deleteUser({ user, }) { if (!user?.providerUserId) return; const safeId = sanitizeKey(user.providerUserId.toString()); const filePath = resolve(base, safeId + ".json"); const exists = await promises .access(filePath) .then(() => true) .catch(() => false); if (!exists) return; try { await promises.unlink(filePath); } catch { // Ignore if file does not exist } }, }; }; export { createLightAuthUserAdapter }; //# sourceMappingURL=index.mjs.map