xpeditis2.0/apps/backend/scripts/setup/generate-admin-hash.js
David 10b69f3b2b
All checks were successful
Dev CI / Backend — Lint (push) Successful in 10m32s
Dev CI / Frontend — Lint & Type-check (push) Successful in 11m20s
Dev CI / Backend — Unit Tests (push) Successful in 10m17s
Dev CI / Frontend — Unit Tests (push) Successful in 10m45s
Dev CI / Notify Failure (push) Has been skipped
CD Preprod / Backend — Lint (push) Successful in 10m24s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 11m18s
CD Preprod / Backend — Unit Tests (push) Successful in 10m16s
CD Preprod / Frontend — Unit Tests (push) Successful in 10m45s
CD Preprod / Backend — Integration Tests (push) Successful in 9m56s
CD Preprod / Build Frontend (push) Successful in 56s
CD Preprod / Build Log Exporter (push) Successful in 32s
CD Preprod / Build Backend (push) Successful in 6m48s
CD Preprod / Deploy to Preprod (push) Successful in 23s
CD Preprod / Notify Failure (push) Has been skipped
CD Preprod / Notify Success (push) Successful in 2s
fix admin monter
2026-09-15 15:21:05 +02:00

133 lines
4.6 KiB
JavaScript
Raw Permalink 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.
*
* Le hash est appliqué à CHAQUE lancement du backend (AdminBootstrapService) :
* - au compte BOOTSTRAP_ADMIN_EMAIL s'il ne s'est encore jamais connecté ;
* - à tout compte si BOOTSTRAP_ADMIN_RESET_PASSWORD=true (retirez ensuite
* les deux variables, sinon un mot de passe changé depuis l'interface
* serait remplacé au lancement suivant).
*
* Sans hash, le compte est créé sans mot de passe utilisable : il faut alors
* « mot de passe oublié » (SMTP requis) ou, sans SMTP, la commande de secours :
* docker exec -it <conteneur-backend> node admin-password.js <email>
*/
'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);
});