This commit is contained in:
commit
421043c9b5
67 changed files with 3455 additions and 0 deletions
167
src/lib/auth/session.ts
Normal file
167
src/lib/auth/session.ts
Normal file
|
@ -0,0 +1,167 @@
|
|||
import { jwt } from "#environment/jwt";
|
||||
import { cookieService } from "#lib/auth/cookies";
|
||||
import { jwtService } from "#lib/auth/jwt";
|
||||
|
||||
import { redis } from "bun";
|
||||
|
||||
import type { CookieOptions, SessionData, UserSession } from "#types/config";
|
||||
|
||||
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): 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,
|
||||
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,
|
||||
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): 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 helper methods
|
||||
private getSessionKey(userId: string, token: string): string {
|
||||
return `session:${userId}:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
const sessionManager = new SessionManager();
|
||||
export { SessionManager, sessionManager };
|
Loading…
Add table
Add a link
Reference in a new issue