first commit

This commit is contained in:
sriram
2026-06-05 17:29:09 +05:30
commit b984b55bc0
31 changed files with 5008 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
// 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,
};