Analytics: Admin-Routen nur auf internem Listener (R5), Transaktion, Duplikat-Check

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 09:24:57 +00:00
1 parent f7da79e4b3
commit cd9aebe4b1
4 files changed
+71 -12

No files matched your search

+19 -6
View File
@@ -48,11 +48,12 @@ function validateSite(b) {
|| !b.domains.every((d) => typeof d === 'string' && d.length <= 253 && HOSTNAME.test(d))) {
return { error: 'Ungültige Domains (1 bis 5 Hostnamen, klein geschrieben, ohne Port und Pfad)' };
}
if (new Set(b.domains).size !== b.domains.length) return { error: 'Doppelte Domains' };
if (typeof b.active !== 'boolean') return { error: 'Ungültiger Status' };
return { site: { projectId: b.projectId, domains: b.domains, active: b.active } };
}
export function createServer({ config, db }) {
function createHandlerServer({ config, db, role }) {
return http.createServer(async (req, res) => {
try {
const url = new URL(req.url, 'http://x');
@@ -61,17 +62,24 @@ export function createServer({ config, db }) {
if (method === 'GET' && url.pathname === '/a/health') return send(res, 200, { ok: true });
if (parts[0] === 'a' && parts[1] === 'admin' && parts[2] === 'sites' && parts.length === 4
if (role === 'admin' && parts[0] === 'a' && parts[1] === 'admin' && parts[2] === 'sites' && parts.length === 4
&& (method === 'PUT' || method === 'DELETE')) {
if (!tokenOk(req.headers.authorization, config.adminToken)) return send(res, 401, { error: 'Nicht autorisiert' });
const key = parts[3];
if (!SITE_KEY.test(key)) return send(res, 400, { error: 'Ungültiger Website-Schlüssel' });
if (method === 'DELETE') {
db.prepare('DELETE FROM sites WHERE siteKey = ?').run(key);
db.prepare('DELETE FROM events_raw WHERE site = ?').run(key);
db.prepare('DELETE FROM clicks WHERE site = ?').run(key);
db.prepare('DELETE FROM daily WHERE site = ?').run(key);
db.exec('BEGIN');
try {
db.prepare('DELETE FROM sites WHERE siteKey = ?').run(key);
db.prepare('DELETE FROM events_raw WHERE site = ?').run(key);
db.prepare('DELETE FROM clicks WHERE site = ?').run(key);
db.prepare('DELETE FROM daily WHERE site = ?').run(key);
db.exec('COMMIT');
} catch (e) {
db.exec('ROLLBACK');
throw e;
}
return send(res, 200, { ok: true });
}
const v = validateSite(await readJson(req, MAX_ADMIN_BODY));
@@ -93,3 +101,8 @@ export function createServer({ config, db }) {
}
});
}
// Öffentlicher Listener: nur /a/health (Erfassung folgt); Admin-Routen gibt es hier nicht.
export const createPublicServer = ({ config, db }) => createHandlerServer({ config, db, role: 'public' });
// Interner Listener für die Website-Verwaltung.
export const createAdminServer = ({ config, db }) => createHandlerServer({ config, db, role: 'admin' });