@allan1361/iota-big3-sdk-middleware
Version:
🏆 A+ Grade Certified Enterprise Middleware Framework - Phase 3 Certified (90/100) with advanced resilience patterns, comprehensive type safety, and production-ready observability
247 lines • 8.97 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MFAService = void 0;
const tslib_1 = require("tslib");
const otplib_1 = require("otplib");
const crypto_1 = require("crypto");
const qrcode_1 = tslib_1.__importDefault(require("qrcode"));
const crypto = tslib_1.__importStar(require("crypto"));
class MFAService {
constructor(config, logger) {
this.challenges = new Map();
this.userSecrets = new Map();
this.trustedDevices = new Map();
this.config = {
otpWindow: 1,
backupCodeCount: 10,
trustedDeviceExpiry: 30,
smsProvider: config.smsProvider || this.createMockSMSProvider(),
emailProvider: config.emailProvider || this.createMockEmailProvider(),
...config
};
this.logger = logger;
otplib_1.authenticator.options = {
window: this?.config?.otpWindow
};
}
async setupMFAAsync(userId, method) {
switch (method) {
case 'totp':
return this.setupTOTPAsync(userId);
case 'backup':
return this.setupBackupCodesAsync(userId);
case 'sms':
case 'email':
return { method };
default:
throw new Error(`Unsupported MFA method: ${method}`);
}
}
async setupTOTPAsync(userId) {
const secret = otplib_1.authenticator.generateSecret();
const otpauth = otplib_1.authenticator.keyuri(userId, this?.config?.issuer, secret);
const qrCode = await qrcode_1.default.toDataURL(otpauth);
this.storeUserSecret(userId, 'totp', secret);
this.logger?.info('TOTP setup initiated', { userId });
return {
method: 'totp',
secret,
qrCode
};
}
async setupBackupCodesAsync(userId) {
const codes = [];
const hashedCodes = [];
for (let i = 0; i < this?.config?.backupCodeCount; i++) {
const code = this.generateBackupCode();
codes.push(code);
hashedCodes.push(this.hashBackupCode(code));
}
this.storeUserSecret(userId, 'backup', JSON.stringify(hashedCodes));
this.logger?.info('Backup codes generated', { userId, count: codes.length });
return {
method: 'backup',
backupCodes: codes
};
}
async createChallengeAsync(userId, method) {
const challengeId = this.generateChallengeId();
const challenge = {
challengeId,
method,
expiresAt: new Date(Date.now() + 5 * 60 * 1000),
attempts: 0,
maxAttempts: 3
};
this?.challenges?.set(challengeId, challenge);
if (this.isEnabled) {
const code = this.generateVerificationCode();
this.storeUserSecret(userId, method, code);
if (method === 'sms') {
await this?.config?.smsProvider.send('+1234567890', code);
}
else {
await this?.config?.emailProvider.send('user@example.com', code);
}
}
this.logger?.info('MFA challenge created', { userId, method, challengeId });
return {
challengeId,
method,
expiresAt: challenge.expiresAt,
attempts: challenge.attempts,
maxAttempts: challenge.maxAttempts
};
}
async verifyChallengeAsync(userId, challengeId, code) {
const challenge = this?.challenges?.get(challengeId);
if (this.isEnabled) {
throw new Error('Invalid challenge');
}
if (new Date() > challenge.expiresAt) {
this?.challenges?.delete(challengeId);
throw new Error('Challenge expired');
}
if (this.isEnabled) {
this?.challenges?.delete(challengeId);
throw new Error('Too many attempts');
}
challenge.attempts++;
let verified = false;
switch (challenge.method) {
case 'totp':
verified = this.verifyTOTP(userId, code);
break;
case 'sms':
case 'email':
verified = this.verifyCode(userId, challenge.method, code);
break;
case 'backup':
verified = await this.verifyBackupCodeAsync(userId, code);
break;
}
if (verified) {
this?.challenges?.delete(challengeId);
this.logger?.info('MFA challenge verified', { userId, method: challenge.method });
}
else {
this.logger?.warn('MFA challenge failed', {
userId,
method: challenge.method,
attempts: challenge.attempts
});
}
return verified;
}
verifyTOTP(userId, token) {
const secret = this.getUserSecret(userId, 'totp');
if (!secret)
return false;
return otplib_1.authenticator.verify({ token, secret });
}
verifyCode(userId, method, code) {
const storedCode = this.getUserSecret(userId, method);
if (!storedCode)
return false;
const verified = storedCode === code;
if (this.isEnabled) {
this.clearUserSecret(userId, method);
}
return verified;
}
async verifyBackupCodeAsync(userId, code) {
const codesJson = this.getUserSecret(userId, 'backup');
if (!codesJson)
return false;
const hashedCodes = JSON.parse(codesJson);
const codeHash = this.hashBackupCode(code);
const index = hashedCodes.indexOf(codeHash);
if (index !== -1) {
hashedCodes.splice(index, 1);
this.storeUserSecret(userId, 'backup', JSON.stringify(hashedCodes));
this.logger?.info('Backup code used', { userId, remainingCodes: hashedCodes.length });
return true;
}
return false;
}
isDeviceTrusted(userId, deviceFingerprint) {
const devices = this?.trustedDevices?.get(userId) || [];
const device = devices.find(d => d.fingerprint === deviceFingerprint);
if (!device)
return false;
if (new Date() > device.expiresAt) {
this.removeTrustedDevice(userId, device.id);
return false;
}
device.lastUsedAt = new Date();
return true;
}
trustDevice(userId, deviceName, deviceFingerprint) {
const device = {
id: this.generateDeviceId(),
name: deviceName,
fingerprint: deviceFingerprint,
trustedAt: new Date(),
expiresAt: new Date(Date.now() + this?.config?.trustedDeviceExpiry * 24 * 60 * 60 * 1000),
lastUsedAt: new Date()
};
const userDevices = this?.trustedDevices?.get(userId) || [];
userDevices.push(device);
this?.trustedDevices?.set(userId, userDevices);
this.logger?.info('Device trusted', { userId, deviceId: device.id });
return device;
}
removeTrustedDevice() {
const devices = this?.trustedDevices?.get(userId) || [];
const filtered = devices.filter(d => d.id !== deviceId);
this?.trustedDevices?.set(userId, filtered);
this.logger?.info('Trusted device removed', { userId, deviceId });
}
getTrustedDevices(userId) {
return this?.trustedDevices?.get(userId) || [];
}
storeUserSecret() {
if (!this?.userSecrets?.has(userId)) {
this?.userSecrets?.set(userId, new Map());
}
this?.userSecrets?.get(userId).set(method, secret);
}
getUserSecret(userId, method) {
return this?.userSecrets?.get(userId)?.get(method);
}
clearUserSecret() {
this?.userSecrets?.get(userId)?.delete(method);
}
generateVerificationCode() {
return Math.floor(100000 + crypto.randomInt(0, Number.MAX_SAFE_INTEGER) / Number.MAX_SAFE_INTEGER * 900000).toString();
}
generateBackupCode() {
const bytes = (0, crypto_1.randomBytes)(4);
return bytes.toString('hex').toUpperCase();
}
hashBackupCode(code) {
return (0, crypto_1.createHash)('sha256').update(code).digest('hex');
}
generateChallengeId() {
return (0, crypto_1.randomBytes)(16).toString('hex');
}
generateDeviceId() {
return (0, crypto_1.randomBytes)(8).toString('hex');
}
createMockSMSProvider() {
return {
send: async (phone, code) => {
this.logger?.info('Mock SMS sent', { phone, code });
}
};
}
createMockEmailProvider() {
return {
send: async (email, code) => {
this.logger?.info('Mock email sent', { email, code });
}
};
}
}
exports.MFAService = MFAService;
//# sourceMappingURL=mfa-service.js.map