160 lines
5.1 KiB
TypeScript
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.
|
|
*/
|