backend/src/lib/auth/session.ts
creations 1e6003079b
All checks were successful
Code quality checks / biome (push) Successful in 11s
move validationResult to value instead, add create, delete, list, info route for guilds
2025-06-18 18:43:47 -04:00

167 lines
4.4 KiB
TypeScript

import { redis } from "bun";
import { jwt } from "#environment/jwt";
import { cookieService, jwtService } from "#lib/auth";
import type { CookieOptions, SessionData, UserSession } from "#types/config";
import type { ExtendedRequest } from "#types/server";
class SessionManager {
async createSession(
payload: UserSession,
userAgent: string,
cookieOptions?: CookieOptions,
): Promise<string> {
const token = jwtService.sign(payload);
const sessionKey = this.getSessionKey(payload.id, token);
const sessionData: SessionData = { ...payload, userAgent };
await redis.set(sessionKey, JSON.stringify(sessionData));
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async getSession(
request: Request | ExtendedRequest,
): Promise<UserSession | null> {
const token = cookieService.extractToken(request);
if (!token) return null;
return this.getSessionByToken(token);
}
async getSessionByToken(token: string): Promise<UserSession | null> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return null;
const sessionKey = keys[0];
if (!sessionKey) return null;
const raw = await redis.get(sessionKey);
if (!raw) return null;
try {
const sessionData: SessionData = JSON.parse(raw);
const { userAgent, ...userSession } = sessionData;
return userSession;
} catch {
return null;
}
}
async updateSession(
request: Request | ExtendedRequest,
payload: UserSession,
userAgent: string,
cookieOptions?: CookieOptions,
): Promise<string> {
const token = cookieService.extractToken(request);
if (!token) throw new Error("Session token not found");
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) throw new Error("Session not found or expired");
const sessionKey = keys[0];
if (!sessionKey) throw new Error("Session not found or expired");
const sessionData: SessionData = { ...payload, userAgent };
await redis.set(sessionKey, JSON.stringify(sessionData));
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async refreshSession(
request: Request | ExtendedRequest,
cookieOptions?: CookieOptions,
): Promise<string | null> {
const token = cookieService.extractToken(request);
if (!token) return null;
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return null;
const sessionKey = keys[0];
if (!sessionKey) return null;
await redis.expire(sessionKey, jwt.expiration as number);
return cookieService.generateCookie(
token,
jwt.expiration as number,
cookieOptions,
);
}
async verifySession(token: string): Promise<UserSession> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) throw new Error("Session not found or expired");
return jwtService.verify(token);
}
async decodeSession(token: string): Promise<UserSession> {
return jwtService.decode(token);
}
async invalidateSession(request: Request | ExtendedRequest): Promise<void> {
const token = cookieService.extractToken(request);
if (!token) return;
await this.invalidateSessionByToken(token);
}
async invalidateSessionByToken(token: string): Promise<void> {
const keys = await redis.keys(`session:*:${token}`);
if (!keys.length) return;
const sessionKey = keys[0];
if (!sessionKey) return;
await redis.del(sessionKey);
}
async invalidateSessionById(sessionId: string): Promise<boolean> {
const keys = await redis.keys(`session:*:${sessionId}`);
if (!keys.length) return false;
const sessionKey = keys[0];
if (!sessionKey) return false;
await redis.del(sessionKey);
return true;
}
async invalidateAllSessionsForUser(userId: string): Promise<number> {
const keys = await redis.keys(`session:${userId}:*`);
if (keys.length === 0) return 0;
for (const key of keys) {
await redis.del(key);
}
return keys.length;
}
async getActiveSessionsForUser(userId: string): Promise<string[]> {
const keys = await redis.keys(`session:${userId}:*`);
return keys.flatMap((key) => {
const token = key.split(":")[2];
return token ? [token] : [];
});
}
private getSessionKey(userId: string, token: string): string {
return `session:${userId}:${token}`;
}
}
const sessionManager = new SessionManager();
export { SessionManager, sessionManager };