Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /**
* Service TOTP — Authentification a deux facteurs.
*
* Utilise le protocole TOTP (RFC 6238) compatible avec :
* - Google Authenticator
* - Authy
* - 1Password
* - Bitwarden
*
* Le secret TOTP est chiffre en base via AES-256-GCM (meme service que les cles SSH).
* Les codes de secours sont hashes avec SHA-256 (usage unique).
*
* @module services/totp
*/
const { TOTP, Secret } = require('otpauth');
const QRCode = require('qrcode');
const crypto = require('node:crypto');
const { encrypt, decrypt } = require('./crypto');
const ISSUER = 'IliaCloud';
const DIGITS = 6;
const PERIOD = 30; // secondes
const ALGORITHM = 'SHA1'; // Standard TOTP (compatibilite max)
const BACKUP_CODES_COUNT = 8;
/**
* Genere un nouveau secret TOTP.
*
* @returns {{ secret: string, uri: string }} — Secret base32 + URI otpauth://
*/
function generateSecret(email) {
const totp = new TOTP({
issuer: ISSUER,
label: email,
algorithm: ALGORITHM,
digits: DIGITS,
period: PERIOD,
secret: new Secret({ size: 20 }), // 160 bits
});
return {
secret: totp.secret.base32,
uri: totp.toString(),
};
}
/**
* Genere un QR code en data URL a partir d'une URI otpauth://.
*
* @param {string} uri — URI otpauth://totp/...
* @returns {Promise<string>} — Data URL PNG du QR code
*/
async function generateQRCode(uri) {
return QRCode.toDataURL(uri, {
width: 256,
margin: 2,
color: { dark: '#000000', light: '#ffffff' },
});
}
/**
* Verifie un code TOTP.
* Accepte une fenetre de ±1 periode (30s) pour compenser les decalages d'horloge.
*
* @param {string} secret — Secret TOTP en base32
* @param {string} code — Code a 6 chiffres saisi par l'utilisateur
* @returns {boolean}
*/
function verifyCode(secret, code) {
const totp = new TOTP({
issuer: ISSUER,
algorithm: ALGORITHM,
digits: DIGITS,
period: PERIOD,
secret: Secret.fromBase32(secret),
});
// delta = null si invalide, sinon l'offset de la fenetre
const delta = totp.validate({ token: code, window: 1 });
return delta !== null;
}
/**
* Genere des codes de secours (usage unique).
* Format : 8 codes de 8 caracteres alphanumeriques.
*
* @returns {string[]} — Codes en clair (a afficher une seule fois a l'utilisateur)
*/
function generateBackupCodes() {
const codes = [];
for (let i = 0; i < BACKUP_CODES_COUNT; i++) {
// 4 bytes = 8 caracteres hex
codes.push(crypto.randomBytes(4).toString('hex'));
}
return codes;
}
/**
* Hash un code de secours (pour stockage en base).
*
* @param {string} code — Code en clair
* @returns {string} — Hash SHA-256 hex
*/
function hashBackupCode(code) {
return crypto.createHash('sha256').update(code.toLowerCase()).digest('hex');
}
/**
* Chiffre le secret TOTP pour stockage en base.
*
* @param {string} secret — Secret base32
* @returns {string} — Secret chiffre AES-256-GCM
*/
function encryptSecret(secret) {
return encrypt(secret);
}
/**
* Dechiffre le secret TOTP depuis la base.
*
* @param {string} encrypted — Secret chiffre
* @returns {string} — Secret base32
*/
function decryptSecret(encrypted) {
return decrypt(encrypted);
}
module.exports = {
generateSecret,
generateQRCode,
verifyCode,
generateBackupCodes,
hashBackupCode,
encryptSecret,
decryptSecret,
};
|