@light-auth/core
Version:
light auth core framework agnostic, using arctic
136 lines (129 loc) • 5.65 kB
JavaScript
/*! @light-auth/core v0.3.1 2025-06-13 */
;
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