Files
shd.build/scripts/default-users.mjs
T

94 lines
3.0 KiB
JavaScript

import { argon2id, hash } from 'argon2';
import { randomBytes, randomUUID } from 'node:crypto';
export const defaultUsers = [
{
username: 'Aewyn',
role: 'user',
passwordEnvironmentKey: 'DEFAULT_ADMIN_PASSWORD'
},
{
username: 'Quartermaster',
role: 'user',
passwordEnvironmentKey: 'DEFAULT_USER_PASSWORD'
}
];
function temporaryPassword() {
// 120 bits of entropy encoded without punctuation that is awkward to paste.
return randomBytes(15).toString('base64url');
}
async function secureHash(password) {
return hash(password, {
type: argon2id,
memoryCost: 19_456,
timeCost: 2,
parallelism: 1
});
}
/**
* Creates missing default users. With resetPasswords enabled, existing default
* users keep their IDs and related records but receive new password hashes.
*/
export async function initializeDefaultUsers(database, options = {}) {
const resetPasswords = options.resetPasswords === true;
const findUser = await database.prepare(
'SELECT id, username, role FROM users WHERE login_username = ? COLLATE NOCASE OR username = ? COLLATE NOCASE'
);
const insertUser = await database.prepare(
'INSERT INTO users (id, username, login_username, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?, ?)'
);
const updateCredential = await database.prepare(
'UPDATE users SET login_username = ?, password_hash = ?, role = ? WHERE id = ?'
);
const credentials = [];
for (const definition of defaultUsers) {
const existing = await findUser.get(definition.username, definition.username);
if (existing && !resetPasswords) {
console.log(`${definition.username}: already exists (${existing.role}); left unchanged`);
continue;
}
const configuredPassword = process.env[definition.passwordEnvironmentKey]?.trim();
const password = configuredPassword || temporaryPassword();
if (password.length < 12 || password.length > 128) {
throw new Error(`${definition.passwordEnvironmentKey} must be between 12 and 128 characters.`);
}
const passwordHash = await secureHash(password);
if (existing) {
await updateCredential.run(definition.username, passwordHash, String(existing.role), existing.id);
console.log(`${definition.username}: credentials reset; role and related data preserved`);
} else {
await insertUser.run(
randomUUID(),
definition.username,
definition.username,
passwordHash,
definition.role,
new Date().toISOString()
);
console.log(`${definition.username}: default account created`);
}
credentials.push({
username: definition.username,
role: existing ? String(existing.role) : definition.role,
password,
generated: !configuredPassword
});
}
if (credentials.length) {
console.log('\nDefault user credentials (shown once):');
for (const credential of credentials) {
console.log(`${credential.username} (${credential.role}): ${credential.password}${credential.generated ? ' [generated]' : ' [from environment]'}`);
}
console.log('Change generated passwords after the first sign-in.\n');
}
return credentials;
}