chore(docker): .dockerignore angepasst; lokale Build-Schritte in Rebuild-Skripten; Doku/README zu production vs production-prebuilt aktualisiert
This commit is contained in:
@@ -1,13 +1,83 @@
|
||||
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");
|
||||
export async function assertOwner(sessionId) {
|
||||
// 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");
|
||||
if (new Date(session.expiresAt) < new Date())
|
||||
throw new Error("Session expired");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user