import type { AstroCookies, AstroCookieSetOptions } from 'astro'; import { ActionError } from 'astro:actions'; import crypto from 'node:crypto'; import { Permissions } from './permissions.ts'; import { ADMIN_COOKIE } from 'astro:env/server'; export class Session { static readonly #cookieOptions: AstroCookieSetOptions = { httpOnly: true, path: '/', sameSite: 'lax' }; static #sessions: Session[] = []; readonly sessionId: string; readonly adminId: number; readonly permissions: Permissions; private constructor(sessionId: string, adminId: number, permissions: Permissions) { this.sessionId = sessionId; this.adminId = adminId; this.permissions = permissions; Session.#sessions.push(this); } invalidate(cookies?: AstroCookies) { for (let i = 0; i < Session.#sessions.length; i++) { if (Session.#sessions[i] == this) { Session.#sessions = Session.#sessions.splice(i, 1); if (cookies) cookies.delete(ADMIN_COOKIE, Session.#cookieOptions); break; } } } static newSession(adminId: number, permissions: Permissions, cookies: AstroCookies) { const session = new Session(crypto.randomBytes(16).toString('hex'), adminId, permissions); Session.#sessions.push(session); cookies.set(ADMIN_COOKIE, session.sessionId, Session.#cookieOptions); return session; } static sessionFromCookies(cookies: AstroCookies, neededPermissions?: Permissions) { const sessionId = cookies.get(ADMIN_COOKIE); if (!sessionId) return null; for (const session of Session.#sessions) { if (session.sessionId == sessionId.value) { if (neededPermissions && !session.permissions.hasPermissions(neededPermissions)) { break; } return session; } } return null; } static actionSessionFromCookies(cookies: AstroCookies, neededPermissions?: Permissions) { const sessionId = cookies.get(ADMIN_COOKIE); if (!sessionId) throw new ActionError({ code: 'UNAUTHORIZED' }); for (const session of Session.#sessions) { if (session.sessionId == sessionId.value) { if (neededPermissions && !session.permissions.hasPermissions(neededPermissions)) { throw new ActionError({ code: 'UNAUTHORIZED' }); } return session; } } throw new ActionError({ code: 'UNAUTHORIZED' }); } }