xpeditis2.0/apps/backend/scripts/setup/generate-admin-hash.js
2026-09-07 21:40:50 +02:00

130 lines
4.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.

#!/usr/bin/env node
/**
* Génère un hash Argon2id pour BOOTSTRAP_ADMIN_PASSWORD_HASH.
*
* cd apps/backend && node scripts/setup/generate-admin-hash.js
*
* Le mot de passe est saisi sans écho et ne quitte jamais votre poste : ni
* argument de ligne de commande (visible dans `ps` et dans l'historique du
* shell), ni variable d'environnement, ni fichier temporaire.
*
* RAPPEL — le mode SANS mot de passe est préférable.
* Si votre chaîne SMTP fonctionne, ne renseignez que BOOTSTRAP_ADMIN_EMAIL :
* le compte est alors créé sans mot de passe utilisable et vous le définissez
* via « mot de passe oublié ». Aucun secret n'existe nulle part, il n'y a donc
* rien à faire fuiter. Ce script n'est utile que si vous devez pouvoir vous
* connecter avant que l'envoi de courriels ne soit opérationnel.
*/
'use strict';
const argon2 = require('argon2');
const readline = require('readline');
// Mêmes paramètres que auth.service.ts : un hash produit ici est vérifiable
// par l'application sans aucune adaptation.
const ARGON2_OPTIONS = {
type: argon2.argon2id,
memoryCost: 65536, // 64 Mo
timeCost: 3,
parallelism: 4,
};
const MIN_LENGTH = 16;
/** Saisie masquée sur un terminal ; lecture directe si l'entrée est redirigée. */
function readSecret(prompt) {
return new Promise((resolve, reject) => {
if (!process.stdin.isTTY) {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => (data += chunk));
process.stdin.on('end', () => resolve(data.replace(/\r?\n$/, '')));
process.stdin.on('error', reject);
return;
}
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const onKeypress = () => {
// Réécrit la ligne sans révéler la longueur de la saisie.
readline.clearLine(process.stdout, 0);
readline.cursorTo(process.stdout, 0);
process.stdout.write(prompt);
};
process.stdout.write(prompt);
process.stdin.on('data', onKeypress);
rl.question('', answer => {
process.stdin.removeListener('data', onKeypress);
rl.close();
process.stdout.write('\n');
resolve(answer);
});
});
}
function checkStrength(password) {
const problems = [];
if (password.length < MIN_LENGTH) {
problems.push(`au moins ${MIN_LENGTH} caractères (${password.length} fournis)`);
}
if (!/[a-z]/.test(password)) problems.push('une minuscule');
if (!/[A-Z]/.test(password)) problems.push('une majuscule');
if (!/[0-9]/.test(password)) problems.push('un chiffre');
if (!/[^A-Za-z0-9]/.test(password)) problems.push('un caractère spécial');
return problems;
}
async function main() {
console.log('');
console.log('Génération du hash Argon2id pour le premier administrateur.');
console.log('La saisie n’est pas affichée.');
console.log('');
const password = await readSecret('Mot de passe : ');
if (!password) {
console.error('Aucun mot de passe saisi.');
process.exit(1);
}
if (process.stdin.isTTY) {
const confirmation = await readSecret('Confirmation : ');
if (confirmation !== password) {
console.error('Les deux saisies diffèrent.');
process.exit(1);
}
}
const problems = checkStrength(password);
if (problems.length > 0) {
console.error('');
console.error('Mot de passe refusé. Il manque : ' + problems.join(', ') + '.');
console.error('Ce compte a tous les droits sur la plateforme : générez plutôt une');
console.error('phrase longue et aléatoire depuis votre gestionnaire de mots de passe.');
process.exit(1);
}
const hash = await argon2.hash(password, ARGON2_OPTIONS);
console.log('');
console.log('Hash à placer dans le Secret Kubernetes (jamais dans le ConfigMap) :');
console.log('');
console.log(' BOOTSTRAP_ADMIN_PASSWORD_HASH: ' + JSON.stringify(hash));
console.log('');
console.log(' cd infra/prod && sops k8s/base/03-secrets.sops.yaml');
console.log('');
console.log('Après votre première connexion :');
console.log(' 1. changez le mot de passe depuis l’interface ;');
console.log(' 2. retirez BOOTSTRAP_ADMIN_PASSWORD_HASH du Secret et réappliquez.');
console.log('');
console.log('Un hash reste attaquable hors ligne : il n’a plus aucune raison');
console.log('de rester stocké une fois le compte opérationnel.');
console.log('');
}
main().catch(error => {
console.error('Échec :', error.message);
process.exit(1);
});