refactor: session handler

This commit is contained in:
Huskydog9988
2025-04-03 19:15:33 -04:00
parent 97043d6366
commit a9d1a442f6
13 changed files with 189 additions and 168 deletions

View File

@ -1,8 +1,7 @@
import { H3Event, Session } from "h3";
import { H3Event } from "h3";
import createMemorySessionProvider from "./memory";
import { SessionProvider } from "./types";
import prisma from "../db/database";
import { v4 as uuidv4 } from "uuid";
import { Session, SessionProvider } from "./types";
import { randomUUID } from "node:crypto";
import moment from "moment";
import { parse as parseCookies } from "cookie-es";
import { MinimumRequestObject } from "~/server/h3";
@ -14,9 +13,7 @@ This implementation may need work.
It exposes an API that should stay static, but there are plenty of opportunities for optimisation/organisation under the hood
*/
const userSessionKey = "_userSession";
const userIdKey = "_userId";
const dropTokenCookie = "drop-token";
const dropTokenCookieName = "drop-token";
const normalSessionLength = [31, "days"];
const extendedSessionLength = [1, "year"];
@ -29,86 +26,94 @@ export class SessionHandler {
// this.sessionProvider = createMemorySessionProvider();
}
private getSessionToken(request: MinimumRequestObject | undefined) {
async signin(h3: H3Event, userId: string, rememberMe: boolean = false) {
const expiresAt = this.createExipreAt(rememberMe);
const token = this.createSessionCookie(h3, expiresAt);
return await this.sessionProvider.setSession(token, {
userId,
expiresAt,
data: {},
});
}
/**
* Get a session associated with a request
* @returns session
*/
async getSession<T extends Session>(request: MinimumRequestObject) {
const token = this.getSessionToken(request);
if (!token) return undefined;
// TODO: should validate if session is expired or not here, not in application code
const data = await this.sessionProvider.getSession<T>(token);
return data;
}
/**
* Signout session associated with request and deauthenticates it
* @param request
* @returns
*/
async signout(h3: H3Event) {
const token = this.getSessionToken(h3);
if (!token) return false;
const res = await this.sessionProvider.removeSession(token);
if (!res) return false;
deleteCookie(h3, dropTokenCookieName);
return true;
}
async cleanupSessions() {
await this.sessionProvider.cleanupSessions();
}
/**
* Update session info
* @param token session token
* @param data new session data
* @returns success or not
*/
private async updateSession(token: string, data: Session) {
return await this.sessionProvider.updateSession(token, data);
}
// ---------------------- Private API Below ------------------------
/**
* Get session token on a request
* @param request
* @returns session token
*/
private getSessionToken(
request: MinimumRequestObject | undefined
): string | undefined {
if (!request) throw new Error("Native web request not available");
const cookieHeader = request.headers.get("Cookie");
if (!cookieHeader) return undefined;
const cookies = parseCookies(cookieHeader);
const cookie = cookies[dropTokenCookie];
const cookie = cookies[dropTokenCookieName];
return cookie;
}
private async createSession(h3: H3Event, extend = false) {
const token = uuidv4();
const expiry = moment().add(
...(extend ? extendedSessionLength : normalSessionLength)
);
setCookie(h3, dropTokenCookie, token, { expires: expiry.toDate() });
this.sessionProvider.setSession(dropTokenCookie, {});
private createExipreAt(rememberMe: boolean) {
return moment()
.add(...(rememberMe ? extendedSessionLength : normalSessionLength))
.toDate();
}
/**
* Creates cookie that represents user session
* @param h3
* @param extend
* @returns
*/
private createSessionCookie(h3: H3Event, expiresAt: Date) {
const token = randomUUID();
// TODO: we should probably switch to jwts to minimize possibility of someone
// trying to guess a session id (jwts let us sign + encrypt stuff in a std way)
setCookie(h3, dropTokenCookieName, token, { expires: expiresAt });
return token;
}
getDropTokenCookie() {
return dropTokenCookie;
}
async getSession<T extends Session>(request: MinimumRequestObject) {
const token = this.getSessionToken(request);
if (!token) return undefined;
const data = await this.sessionProvider.getSession<{ [userSessionKey]: T }>(
token
);
if (!data) return undefined;
return data[userSessionKey];
}
async setSession(h3: H3Event, data: any, extend = false) {
const token =
this.getSessionToken(h3) ?? (await this.createSession(h3, extend));
const result = await this.sessionProvider.updateSession(
token,
userSessionKey,
data
);
return result;
}
async clearSession(request: MinimumRequestObject) {
const token = this.getSessionToken(request);
if (!token) return false;
await this.sessionProvider.clearSession(token);
return true;
}
async getUserId(h3: MinimumRequestObject) {
const token = this.getSessionToken(h3);
if (!token) return undefined;
return await this.getUserIdRaw(token);
}
async getUserIdRaw(token: string) {
const session = await this.sessionProvider.getSession<{
[userIdKey]: string | undefined;
}>(token);
if (!session) return undefined;
return session[userIdKey];
}
async setUserId(h3: H3Event, userId: string, extend = false) {
const token =
this.getSessionToken(h3) ?? (await this.createSession(h3, extend));
const result = await this.sessionProvider.updateSession(
token,
userIdKey,
userId
);
}
}
export const sessionHandler = new SessionHandler();