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} Session Token and the Refresh Token */ async create(data: SessionCreationData): Promise { 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} 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. */