From c994dbf3b58236b360f7dee8e981bf8777494bc1 Mon Sep 17 00:00:00 2001 From: Kenearos Date: Tue, 7 Jul 2026 22:33:14 +0200 Subject: [PATCH] =?UTF-8?q?feat(auth):=20Story=201.4=20=E2=80=94=20seedAdm?= =?UTF-8?q?in=20+=20Fail-Fast=20auf=20ADMIN=5FEMAIL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/auth.js | 23 +++++++++++++++++++++++ server/seed.test.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 server/seed.test.js diff --git a/server/auth.js b/server/auth.js index 05bc055..2596ce1 100644 --- a/server/auth.js +++ b/server/auth.js @@ -95,8 +95,31 @@ function deleteUserSessions(userId) { db.prepare('DELETE FROM sessions WHERE user_id = ?').run(userId); } +/** + * Legt den Admin aus ADMIN_EMAIL an (is_admin=1) bzw. befördert ihn; idempotent. + * Fail-Fast: leere/fehlende/ungültige E-Mail wirft — der Server darf dann nicht starten. + * @returns {number} adminUserId + */ +function seedAdmin(adminEmail) { + const email = normalizeEmail(adminEmail || ''); + if (!email || !email.includes('@')) { + throw new Error('ADMIN_EMAIL fehlt oder ist ungültig — Server-Start abgebrochen (Fail-Fast).'); + } + const now = new Date().toISOString(); + const existing = db.prepare('SELECT id, is_admin FROM users WHERE email = ?').get(email); + if (!existing) { + const info = db.prepare('INSERT INTO users (email, is_admin, created_at) VALUES (?, 1, ?)').run(email, now); + return Number(info.lastInsertRowid); + } + if (!existing.is_admin) { + db.prepare('UPDATE users SET is_admin = 1 WHERE id = ?').run(existing.id); + } + return Number(existing.id); +} + module.exports = { normalizeEmail, hashToken, createLoginToken, consumeLoginToken, createSession, validateSession, deleteSession, deleteUserSessions, + seedAdmin, TOKEN_TTL_MIN, SESSION_TTL_DAYS, SESSION_IDLE_HOURS, }; diff --git a/server/seed.test.js b/server/seed.test.js new file mode 100644 index 0000000..2f8dda1 --- /dev/null +++ b/server/seed.test.js @@ -0,0 +1,32 @@ +const { test } = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'dp-seed-')); +const { db } = require('./db'); +const { seedAdmin } = require('./auth'); + +test('Fail-Fast: fehlende/leere/ungültige ADMIN_EMAIL wirft', () => { + assert.throws(() => seedAdmin(''), /Fail-Fast|ADMIN_EMAIL/); + assert.throws(() => seedAdmin(' '), /Fail-Fast|ADMIN_EMAIL/); + assert.throws(() => seedAdmin(undefined), /Fail-Fast|ADMIN_EMAIL/); + assert.throws(() => seedAdmin('keinklammeraffe'), /Fail-Fast|ADMIN_EMAIL/); +}); + +test('seedAdmin legt Admin an (normalisiert), idempotent, liefert id', () => { + const id1 = seedAdmin(' Admin@Klinik.DE '); + const row = db.prepare('SELECT email, is_admin FROM users WHERE id = ?').get(id1); + assert.strictEqual(row.email, 'admin@klinik.de'); + assert.strictEqual(row.is_admin, 1); + const id2 = seedAdmin('admin@klinik.de'); + assert.strictEqual(id2, id1, 'idempotent — kein zweiter Nutzer'); +}); + +test('bestehender Nicht-Admin wird zum Admin befördert', () => { + db.prepare('INSERT INTO users (email, is_admin, created_at) VALUES (?,0,?)') + .run('later@klinik.de', new Date().toISOString()); + const id = seedAdmin('later@klinik.de'); + assert.strictEqual(db.prepare('SELECT is_admin FROM users WHERE id=?').get(id).is_admin, 1); +});