Files
FuerDieCuts/server/app.js
T
ferdi2goandClaude Opus 5.5 f7d249f28b Grundgerüst mit Server, PIN-Anmeldung und Docker-Deploy aufbauen
Die alte Clip-Auswahl-Oberfläche entfällt. Neu sind ein Express-Server,
der im Entwicklungsbetrieb Vite einbindet, eine PIN-Anmeldung mit
signiertem Sitzungs-Cookie und ein Docker-Image mit ffmpeg für VCH.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:15:01 +00:00

62 lines
1.7 KiB
JavaScript

import express from 'express';
import {
SESSION_COOKIE,
SESSION_MAX_AGE_MS,
createLoginLimiter,
createToken,
pinMatches,
readCookie,
verifyToken,
} from './auth.js';
export function createApp({ config, secret }) {
const app = express();
app.disable('x-powered-by');
app.set('trust proxy', true);
const limiter = createLoginLimiter();
const isAuthenticated = (req) =>
verifyToken(secret, config.pin, readCookie(req, SESSION_COOKIE));
app.get('/healthz', (req, res) => res.json({ ok: true }));
app.use('/api', express.json({ limit: '100kb' }));
app.get('/api/session', (req, res) => {
res.json({ authenticated: isAuthenticated(req), pinConfigured: Boolean(config.pin) });
});
app.post('/api/login', (req, res) => {
if (limiter.isBlocked()) {
return res.status(429).json({ error: 'Zu viele Fehlversuche. Bitte später erneut versuchen.' });
}
if (!pinMatches(config.pin, req.body?.pin)) {
limiter.recordFailure();
return res.status(401).json({ error: 'PIN falsch.' });
}
limiter.reset();
res.cookie(SESSION_COOKIE, createToken(secret, config.pin), {
httpOnly: true,
sameSite: 'lax',
secure: req.secure,
maxAge: SESSION_MAX_AGE_MS,
path: '/',
});
res.json({ authenticated: true });
});
app.post('/api/logout', (req, res) => {
res.clearCookie(SESSION_COOKIE, { path: '/' });
res.json({ authenticated: false });
});
app.use('/api', (req, res, next) => {
if (!isAuthenticated(req)) return res.status(401).json({ error: 'Nicht angemeldet.' });
next();
});
app.use('/api', (req, res) => res.status(404).json({ error: 'Nicht gefunden.' }));
return app;
}