Files
beauty-bookings/server-dist/lib/auth.js

84 lines
3.0 KiB
JavaScript

import { createKV } from "./create-kv.js";
import { getCookie } from "hono/cookie";
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
export const sessionsKV = createKV("sessions");
export const usersKV = createKV("users");
// Cookie configuration constants
export const SESSION_COOKIE_NAME = 'sessionId';
export const CSRF_COOKIE_NAME = 'csrf-token';
export const COOKIE_OPTIONS = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'Lax',
path: '/',
maxAge: 86400 // 24 hours
};
// CSRF token generation
export function generateCSRFToken() {
return randomBytes(32).toString('hex');
}
// Session extraction from cookies
export async function getSessionFromCookies(c) {
const sessionId = getCookie(c, SESSION_COOKIE_NAME);
if (!sessionId)
return null;
const session = await sessionsKV.getItem(sessionId);
if (!session)
return null;
// Check expiration
if (new Date(session.expiresAt) < new Date()) {
// Clean up expired session
await sessionsKV.removeItem(sessionId);
return null;
}
return session;
}
// CSRF token validation
export async function validateCSRFToken(c, sessionId) {
const headerToken = c.req.header('X-CSRF-Token');
if (!headerToken)
throw new Error("CSRF token missing");
const session = await sessionsKV.getItem(sessionId);
if (!session?.csrfToken)
throw new Error("Invalid session");
// Use timing-safe comparison to prevent timing attacks
const sessionTokenBuffer = Buffer.from(session.csrfToken, 'hex');
const headerTokenBuffer = Buffer.from(headerToken, 'hex');
if (sessionTokenBuffer.length !== headerTokenBuffer.length || !timingSafeEqual(sessionTokenBuffer, headerTokenBuffer)) {
throw new Error("CSRF token mismatch");
}
}
// Session rotation helper
export async function rotateSession(oldSessionId, userId) {
// Delete old session
await sessionsKV.removeItem(oldSessionId);
// Create new session with CSRF token
const newSessionId = randomUUID();
const csrfToken = generateCSRFToken();
const now = new Date();
const expiresAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours
const newSession = {
id: newSessionId,
userId,
expiresAt: expiresAt.toISOString(),
createdAt: now.toISOString(),
csrfToken
};
await sessionsKV.setItem(newSessionId, newSession);
return newSession;
}
// Updated assertOwner function with CSRF validation
export async function assertOwner(c) {
const session = await getSessionFromCookies(c);
if (!session)
throw new Error("Invalid session");
const user = await usersKV.getItem(session.userId);
if (!user || user.role !== "owner")
throw new Error("Forbidden");
// Validate CSRF token for non-GET requests
const method = c.req.method;
if (method !== 'GET' && method !== 'HEAD') {
await validateCSRFToken(c, session.id);
}
}