Erste Version: HCRB Spieltag-Kalender und Fahrtplaner

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
guyg1474andClaude Opus 5.5 committed 2026-10-10 13:54:33 +02:00
commit 10400eee59
30 files changed
+3771

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
// Benutzerverwaltung für den Login
//
// npm run user -- list
// npm run user -- add <name> Benutzer anlegen (Passwort wird abgefragt)
// npm run user -- passwd <name> Passwort ändern (meldet alle Sitzungen des Benutzers ab)
// npm run user -- remove <name> 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 <name> | passwd <name> | remove <name>');
console.log(`Benutzerdatei: ${USERS_FILE}`);
process.exitCode = cmd ? 1 : 0;
}
} catch (err) {
console.error(err.message);
process.exitCode = 1;
}