@chittyos/core
Version:
ChittyOS Core - Essential package with ID, auth, verification, beacon tracking, and brand components for all ChittyOS applications
150 lines • 4.28 kB
JavaScript
// src/auth/index.ts
import { SignJWT, jwtVerify } from "jose";
import { nanoid } from "nanoid";
import * as crypto from "crypto";
var DEFAULT_CONFIG = {
jwtSecret: process.env.CHITTY_JWT_SECRET || crypto.randomBytes(32).toString("base64"),
issuer: "chittyos",
audience: "chittyos-apps",
expiresIn: "24h"
};
var config = { ...DEFAULT_CONFIG };
var sessions = /* @__PURE__ */ new Map();
function configure(customConfig) {
config = { ...config, ...customConfig };
}
async function createToken(user) {
const secret = new TextEncoder().encode(config.jwtSecret);
const expiresAt = /* @__PURE__ */ new Date();
const hours = parseInt(config.expiresIn?.replace("h", "") || "24");
expiresAt.setHours(expiresAt.getHours() + hours);
const token = await new SignJWT({
sub: user.id,
chittyId: user.chittyId,
email: user.email,
roles: user.roles || [],
permissions: user.permissions || [],
metadata: user.metadata || {}
}).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setIssuer(config.issuer).setAudience(config.audience).setExpirationTime(config.expiresIn).setJti(nanoid()).sign(secret);
const refreshToken2 = nanoid(32);
const session = {
id: nanoid(),
userId: user.id,
token,
refreshToken: refreshToken2,
expiresAt,
createdAt: /* @__PURE__ */ new Date()
};
sessions.set(session.id, session);
sessions.set(refreshToken2, session);
return {
token,
expiresAt,
refreshToken: refreshToken2
};
}
async function verifyToken(token) {
try {
const secret = new TextEncoder().encode(config.jwtSecret);
const { payload } = await jwtVerify(token, secret, {
issuer: config.issuer,
audience: config.audience
});
return {
...payload,
id: payload.sub,
chittyId: payload.chittyId,
email: payload.email,
roles: payload.roles,
permissions: payload.permissions,
metadata: payload.metadata
};
} catch (error) {
throw new Error("Invalid or expired token");
}
}
async function refreshToken(refreshToken2) {
const session = sessions.get(refreshToken2);
if (!session) {
throw new Error("Invalid refresh token");
}
if (session.expiresAt < /* @__PURE__ */ new Date()) {
sessions.delete(session.id);
sessions.delete(session.refreshToken);
throw new Error("Session expired");
}
const user = await verifyToken(session.token).catch(() => null);
if (!user) {
throw new Error("Cannot refresh token");
}
return createToken({
id: user.id,
chittyId: user.chittyId,
email: user.email,
roles: user.roles,
permissions: user.permissions,
metadata: user.metadata
});
}
function revokeSession(sessionId) {
const session = sessions.get(sessionId);
if (session) {
sessions.delete(session.id);
sessions.delete(session.refreshToken);
return true;
}
return false;
}
function hasRoles(user, requiredRoles) {
if (!user.roles) return false;
return requiredRoles.every((role) => user.roles.includes(role));
}
function hasPermissions(user, requiredPermissions) {
if (!user.permissions) return false;
return requiredPermissions.every((perm) => user.permissions.includes(perm));
}
async function hashPassword(password) {
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto.pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
return `${salt}:${hash}`;
}
async function verifyPassword(password, hashedPassword) {
const [salt, hash] = hashedPassword.split(":");
const verifyHash = crypto.pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
return hash === verifyHash;
}
function cleanupSessions() {
const now = /* @__PURE__ */ new Date();
for (const [key, session] of sessions) {
if (session.expiresAt < now) {
sessions.delete(key);
}
}
}
setInterval(cleanupSessions, 36e5).unref();
var auth_default = {
configure,
createToken,
verifyToken,
refreshToken,
revokeSession,
hasRoles,
hasPermissions,
hashPassword,
verifyPassword,
cleanupSessions
};
export {
cleanupSessions,
configure,
createToken,
auth_default as default,
hasPermissions,
hasRoles,
hashPassword,
refreshToken,
revokeSession,
verifyPassword,
verifyToken
};
//# sourceMappingURL=index.mjs.map