| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- // Auth utilities — password hashing (scrypt), tokens, and TOTP MFA.
- // Uses only Node's built-in crypto, no external auth deps.
- const crypto = require('crypto');
-
- // ---- Passwords (scrypt) ----
- function hashPassword(password, salt = crypto.randomBytes(16).toString('hex')) {
- const hash = crypto.scryptSync(password, salt, 64).toString('hex');
- return { hash, salt };
- }
- function verifyPassword(password, salt, expectedHash) {
- const hash = crypto.scryptSync(password, salt, 64).toString('hex');
- return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expectedHash));
- }
-
- // ---- Random tokens ----
- const token = (bytes = 24) => crypto.randomBytes(bytes).toString('hex');
- const id = () => crypto.randomBytes(8).toString('hex');
- const numericCode = (digits = 6) =>
- String(crypto.randomInt(0, 10 ** digits)).padStart(digits, '0');
-
- // ---- TOTP (RFC 6238), SHA-1, 30s, 6 digits ----
- const B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
- function base32Encode(buf) {
- let bits = 0, value = 0, out = '';
- for (const byte of buf) {
- value = (value << 8) | byte; bits += 8;
- while (bits >= 5) { out += B32[(value >>> (bits - 5)) & 31]; bits -= 5; }
- }
- if (bits > 0) out += B32[(value << (5 - bits)) & 31];
- return out;
- }
- function base32Decode(str) {
- const clean = str.replace(/=+$/, '').toUpperCase();
- let bits = 0, value = 0; const out = [];
- for (const ch of clean) {
- const idx = B32.indexOf(ch);
- if (idx === -1) continue;
- value = (value << 5) | idx; bits += 5;
- if (bits >= 8) { out.push((value >>> (bits - 8)) & 0xff); bits -= 8; }
- }
- return Buffer.from(out);
- }
- function newMfaSecret() {
- return base32Encode(crypto.randomBytes(20));
- }
- function totp(secret, timeStep = Math.floor(Date.now() / 30000)) {
- const key = base32Decode(secret);
- const buf = Buffer.alloc(8);
- buf.writeBigUInt64BE(BigInt(timeStep));
- const hmac = crypto.createHmac('sha1', key).update(buf).digest();
- const offset = hmac[hmac.length - 1] & 0xf;
- const code =
- ((hmac[offset] & 0x7f) << 24) |
- ((hmac[offset + 1] & 0xff) << 16) |
- ((hmac[offset + 2] & 0xff) << 8) |
- (hmac[offset + 3] & 0xff);
- return String(code % 1_000_000).padStart(6, '0');
- }
- function verifyTotp(secret, code, window = 1) {
- if (!/^\d{6}$/.test(String(code || ''))) return false;
- const step = Math.floor(Date.now() / 30000);
- for (let i = -window; i <= window; i++) {
- if (totp(secret, step + i) === String(code)) return true;
- }
- return false;
- }
- function otpauthUrl(secret, email, issuer = 'RemoteAccess') {
- return `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(email)}` +
- `?secret=${secret}&issuer=${encodeURIComponent(issuer)}&algorithm=SHA1&digits=6&period=30`;
- }
-
- module.exports = {
- hashPassword, verifyPassword, token, id, numericCode,
- newMfaSecret, totp, verifyTotp, otpauthUrl,
- };
|