BillTracker/setup/firstRun.js

151 lines
4.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const readline = require('readline');
const bcrypt = require('bcryptjs');
function line(char = '─', len = 56) {
return char.repeat(len);
}
function prompt(question) {
return new Promise(resolve => {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.question(question, answer => {
rl.close();
resolve(answer.trim());
});
});
}
function promptPassword(label) {
if (!process.stdin.isTTY) {
return prompt(label);
}
return new Promise(resolve => {
process.stdout.write(label);
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
let pw = '';
const handler = (ch) => {
if (ch === '\n' || ch === '\r' || ch === '') {
process.stdout.write('\n');
process.stdin.setRawMode(false);
process.stdin.pause();
process.stdin.removeListener('data', handler);
resolve(pw);
} else if (ch === '') {
process.exit(0);
} else if (ch === '') {
if (pw.length > 0) {
pw = pw.slice(0, -1);
process.stdout.write('\b \b');
}
} else {
pw += ch;
process.stdout.write('*');
}
};
process.stdin.on('data', handler);
});
}
async function createUser(db, username, password, role) {
const hash = await bcrypt.hash(password, 12);
db.prepare(`
INSERT INTO users (username, password_hash, role, first_login, must_change_password)
VALUES (?, ?, ?, 0, 0)
`).run(username, hash, role);
}
async function runFromEnv(db) {
const adminUser = process.env.INIT_ADMIN_USER;
const adminPass = process.env.INIT_ADMIN_PASS;
const errors = [];
if (!adminUser || adminUser.length < 3) errors.push('INIT_ADMIN_USER must be at least 3 characters');
if (!adminPass || adminPass.length < 8) errors.push('INIT_ADMIN_PASS must be at least 8 characters');
if (errors.length) {
console.error('\n[first-run] Environment variable setup failed:');
errors.forEach(e => console.error(' ✗ ' + e));
console.error('\nSet both vars: INIT_ADMIN_USER and INIT_ADMIN_PASS');
console.error('Then open the web UI to create your first user account.\n');
process.exit(1);
}
await createUser(db, adminUser, adminPass, 'admin');
console.log(`[first-run] Admin "${adminUser}" created. Open the web UI to create your first user.`);
}
async function run(db) {
// Non-interactive Docker path: env vars provided
if (process.env.INIT_ADMIN_USER && process.env.INIT_ADMIN_PASS) {
return runFromEnv(db);
}
// No TTY and no env vars
if (!process.stdin.isTTY) {
console.error([
'',
'[first-run] No admin account found and no TTY available for interactive setup.',
'Set these environment variables to create the admin automatically:',
' INIT_ADMIN_USER=admin',
' INIT_ADMIN_PASS=<min 8 chars>',
'',
'Or run interactively: docker run -it ...',
'',
].join('\n'));
process.exit(1);
}
// Interactive terminal wizard (admin only)
console.log('\n' + line('═'));
console.log(' Bill Tracker — First Run Setup');
console.log(line('═'));
console.log(`
No accounts found. Create an admin account to get started.
About the admin account:
✓ Can create user accounts via the web interface
✓ Can reset user passwords
✗ Cannot view, edit, or access any bills, payments,
or financial data — ever
`);
console.log(line());
console.log(' Create Admin Account');
console.log(line());
console.log('');
let username, password, confirm;
while (true) {
username = await prompt(' Username: ');
if (username.length >= 3) break;
console.log(' Username must be at least 3 characters.\n');
}
while (true) {
password = await promptPassword(' Password: ');
if (password.length < 8) { console.log(' Password must be at least 8 characters.\n'); continue; }
confirm = await promptPassword(' Confirm: ');
if (password !== confirm) { console.log(' Passwords do not match.\n'); continue; }
break;
}
await createUser(db, username, password, 'admin');
console.log(`\n ✓ Admin account "${username}" created.\n`);
console.log(line('═'));
console.log(' Setup complete!');
console.log('');
console.log(' Open the app in your browser and log in as admin');
console.log(' to create your first user account.');
console.log(line('═'));
console.log('');
}
module.exports = { run };