Files
optima/src/managers/sessions.ts
T
2026-01-24 16:59:50 -06:00

160 lines
5.1 KiB
TypeScript

import {
prisma,
refreshTokenDuration,
sessionDuration,
accessTokenDuration,
accessTokenPrivateKey,
refreshTokenPrivateKey,
} from "../constants";
import UserController from "../controllers/UserController";
import {
SessionController,
DecodedSession,
SessionPayloadObject,
SessionTokensObject,
} from "../controllers/SessionController";
import jwt from "jsonwebtoken";
import SessionError from "../Errors/SessionError";
import SessionTokenError from "../Errors/SessionTokenError";
import ExpiredAccessTokenError from "../Errors/ExpiredAccessTokenError";
import ExpiredRefreshTokenError from "../Errors/ExpiredRefreshTokenError";
import { events } from "../modules/globalEvents";
export interface SessionCreationData {
user: UserController;
}
export const sessions = {
/**
* Create a session
*
* This will create a session instance in the databse that will be linked to both the session token
* and the refresh token.
*
* @param data - The params needed to create the session tokens
* @returns {Promise<SessionTokensObject>} Session Token and the Refresh Token
*/
async create(data: SessionCreationData): Promise<SessionTokensObject> {
const session = await prisma.session.create({
data: {
expires: new Date(Date.now() + sessionDuration),
userId: data.user.id,
},
});
const controller = new SessionController(session);
// Trigger Global Event
events.emit("session:created", {
user: data.user,
session: controller,
});
let tokens: SessionTokensObject = await controller.generateTokens();
return tokens;
},
/**
* Fetch a session
*
* This method is designed to be as versitile as possible, if you are looking for a session, use this method with
* your choice of `accessToken`, `refreshToken`, `id`, or `sessionKey`. The identifier value is a partial type.
*
* @param identifier - An object allowing you to put either session token, sessionKey, or id in to fetch the desired session.
* @returns {Promise<SessionController>} The controller for the desired session.
*/
async fetch(
identifier: Partial<{
refreshToken: string;
accessToken: string;
id: string;
sessionKey: string;
}>
) {
if (identifier.refreshToken || identifier.accessToken) {
const decodedJWT = identifier.refreshToken
? ((await new Promise((res, rej) =>
jwt.verify(
identifier.refreshToken!,
refreshTokenPrivateKey,
{
algorithms: ["RS256"],
},
async (err, decode) => {
if (
err &&
(err.name == "TokenExpiredError" ||
err.message == "invalid signature")
) {
let sessionDat = await prisma.session.findFirst({
where: {
sessionKey: (
jwt.decode(identifier.refreshToken!) as DecodedSession
).sessionKey,
},
});
if (!sessionDat)
return rej(new SessionError("Invalid session."));
let session = new SessionController(sessionDat);
await session.terminate();
if (err.message == "invalid signature")
return rej(new SessionError("Invalid session."));
return rej(new ExpiredRefreshTokenError("It epired."));
}
if (err) return rej(err);
return res(decode as DecodedSession);
}
)
)) as DecodedSession)
: ((await new Promise((res, rej) =>
jwt.verify(
identifier.accessToken!,
accessTokenPrivateKey,
{
algorithms: ["RS256"],
},
(err, decode) => {
if (err && err.name == "TokenExpiredError")
return rej(new ExpiredAccessTokenError());
if (err) return rej(err);
return res(decode as DecodedSession);
}
)
)) as DecodedSession);
const sessionData = await prisma.session.findFirst({
where: { sessionKey: decodedJWT.sessionKey },
});
if (!sessionData) throw new SessionError("Invalid Session");
if (identifier.accessToken && decodedJWT.exp > Date.now())
throw new ExpiredAccessTokenError();
if (identifier.refreshToken && decodedJWT.exp > Date.now()) {
let sess = new SessionController(sessionData);
await sess.terminate();
throw new SessionError("Invalid Session...", "Expired Refresh Token");
}
return new SessionController(sessionData);
}
const sessionData = await prisma.session.findFirst({
where: { OR: [{ sessionKey: identifier.sessionKey, id: identifier.id }] },
});
if (!sessionData) throw new SessionError("Invalid Session");
return new SessionController(sessionData);
},
};
/**
* @TODO As a consequence of the above, need to setup pgBoss for cron and event loop.
*/