Files
hcrb/scripts/user.js
T
2026-10-10 13:54:33 +02:00

90 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}