import express from 'express'; import * as UserService from '../calendar/users/users.service.js'; import {sendServerError} from './tickets.errors.js'; /** * Mirrors the Feedback module's feedback.auth.ts: this is the ONLY place in * the tickets module that knows how admin authentication works. No route * handler and no service outside this file may import users.service, read * session headers, or touch bcrypt. * * Today: reuses the existing Calendar users/sessions mechanism. Any * activated @nachklang.art account may administer vouchers - no roles, same * policy as Feedback (see docs/plan-ticket-shop.md). A dedicated * roles/permissions model is explicitly out of scope for v1. * * Explicitly forbidden: accepting sessionId/sessionKey from query * parameters - headers only (see DEFERRED_SECURITY.md item 1). */ export interface AdminIdentity { id: string; email: string; displayName: string; } export type AdminAuthenticator = (req: express.Request) => Promise; export const sessionHeaderAuthenticator: AdminAuthenticator = async (req) => { const sessionId = req.header('X-Session-Id'); const sessionKey = req.header('X-Session-Key'); if (!sessionId || !sessionKey) { return null; } const ip = req.ip || ''; const user = await UserService.checkSession(sessionId, sessionKey, ip); if (!user || !user.isActive) { return null; } return { id: String(user.userId), email: user.email, displayName: user.fullName }; }; export const activeAuthenticator: AdminAuthenticator = sessionHeaderAuthenticator; export const requireAdminAuth: express.RequestHandler = async (req, res, next) => { try { const identity = await activeAuthenticator(req); if (!identity) { res.status(401).send({status: 'UNAUTHORIZED', message: 'Anmeldung erforderlich.'}); return; } res.locals.admin = identity; next(); } catch (e: any) { sendServerError(res, e); } };