// Benutzerverwaltung für den Login // // npm run user -- list // npm run user -- add Benutzer anlegen (Passwort wird abgefragt) // npm run user -- passwd Passwort ändern (meldet alle Sitzungen des Benutzers ab) // npm run user -- remove Benutzer löschen import { createInterface } from 'node:readline'; import { hashPassword, readUsers, writeUsers, normalizeName, MIN_PASSWORD, USERS_FILE } from '../auth.js'; // Passwort verdeckt einlesen (bei umgeleiteter Eingabe zeilenweise) let pipedLines; function askPassword(prompt) { return new Promise((resolve, reject) => { const { stdin, stdout } = process; stdout.write(prompt); if (!stdin.isTTY) { pipedLines ??= createInterface({ input: stdin })[Symbol.asyncIterator](); pipedLines.next().then(({ value }) => { stdout.write('\n'); resolve(value ?? ''); }, reject); return; } let input = ''; stdin.setRawMode(true); stdin.setEncoding('utf8'); stdin.resume(); const onData = (chunk) => { for (const ch of chunk) { if (ch === '\r' || ch === '\n') { stdin.setRawMode(false); stdin.pause(); stdin.off('data', onData); stdout.write('\n'); return resolve(input); } if (ch === '\u0003') { // Strg+C stdin.setRawMode(false); stdout.write('\n'); return reject(new Error('Abgebrochen.')); } if (ch === '\u007f' || ch === '\b') input = input.slice(0, -1); else input += ch; } }; stdin.on('data', onData); }); } async function newPassword() { const pw = await askPassword('Neues Passwort: '); if (pw.length < MIN_PASSWORD) throw new Error(`Das Passwort muss mindestens ${MIN_PASSWORD} Zeichen haben.`); if (await askPassword('Passwort wiederholen: ') !== pw) throw new Error('Die Passwörter stimmen nicht überein.'); return hashPassword(pw); } const [cmd, rawName] = process.argv.slice(2); const name = normalizeName(rawName); const users = readUsers(); const user = users.find((u) => u.name === name); try { if (cmd === 'list') { console.log(users.length ? users.map((u) => u.name).join('\n') : 'Noch keine Benutzer angelegt.'); } else if (cmd === 'add' && name) { if (!/^[a-z0-9._-]{2,32}$/.test(name)) { throw new Error('Benutzername: 2–32 Zeichen, nur a–z, 0–9, Punkt, Minus, Unterstrich.'); } if (user) throw new Error(`Benutzer „${name}“ gibt es schon.`); users.push({ name, hash: await newPassword(), changedAt: Date.now() }); writeUsers(users); console.log(`Benutzer „${name}“ angelegt.`); } else if (cmd === 'passwd' && name) { if (!user) throw new Error(`Benutzer „${name}“ gibt es nicht.`); user.hash = await newPassword(); user.changedAt = Date.now(); writeUsers(users); console.log(`Passwort von „${name}“ geändert. Bestehende Anmeldungen sind abgemeldet.`); } else if (cmd === 'remove' && name) { if (!user) throw new Error(`Benutzer „${name}“ gibt es nicht.`); writeUsers(users.filter((u) => u !== user)); console.log(`Benutzer „${name}“ gelöscht.`); } else { console.log('Benutzung: npm run user -- list | add | passwd | remove '); console.log(`Benutzerdatei: ${USERS_FILE}`); process.exitCode = cmd ? 1 : 0; } } catch (err) { console.error(err.message); process.exitCode = 1; }