← back to Designer Wallcoverings
DW-Agents/dw-agents/global-auth-system/sms-verifier.ts
184 lines
/**
* DW Global Auth - SMS Verification System
* Handles SMS 2FA via Twilio
*/
import { SMSVerification } from './types';
import twilio from 'twilio';
import fs from 'fs/promises';
import path from 'path';
const VERIFICATION_FILE = path.join(__dirname, 'data', 'sms-verifications.json');
const CODE_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes
const MAX_ATTEMPTS = 3;
export class SMSVerifier {
private verifications: Map<string, SMSVerification> = new Map();
private twilioClient: twilio.Twilio | null = null;
constructor() {
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
if (accountSid && authToken) {
this.twilioClient = twilio(accountSid, authToken);
console.log('📱 Twilio SMS client initialized');
} else {
console.warn('⚠️ Twilio credentials not found - SMS verification disabled');
}
this.loadVerifications();
}
private async loadVerifications() {
try {
const data = await fs.readFile(VERIFICATION_FILE, 'utf-8');
const verifications: SMSVerification[] = JSON.parse(data);
verifications.forEach(v => {
v.expiresAt = new Date(v.expiresAt);
this.verifications.set(v.phone, v);
});
} catch (error) {
// File doesn't exist yet
}
}
private async saveVerifications() {
try {
await fs.mkdir(path.dirname(VERIFICATION_FILE), { recursive: true });
const verificationsArray = Array.from(this.verifications.values());
await fs.writeFile(VERIFICATION_FILE, JSON.stringify(verificationsArray, null, 2));
} catch (error) {
console.error('Failed to save verifications:', error);
}
}
async sendVerificationCode(phone: string, userName: string): Promise<{ success: boolean; message: string }> {
if (!this.twilioClient) {
// Development mode - return a static code
const code = '123456';
console.log(`📱 [DEV MODE] SMS code for ${phone}: ${code}`);
const verification: SMSVerification = {
phone,
code,
expiresAt: new Date(Date.now() + CODE_EXPIRY_MS),
attempts: 0
};
this.verifications.set(phone, verification);
await this.saveVerifications();
return {
success: true,
message: 'Development mode: Use code 123456'
};
}
try {
// Generate 6-digit code
const code = Math.floor(100000 + Math.random() * 900000).toString();
// Send SMS via Twilio
await this.twilioClient.messages.create({
body: `Your DW-Agents verification code is: ${code}\n\nThis code expires in 10 minutes.\n\n- Designer Wallcoverings Security Team`,
from: process.env.TWILIO_PHONE_NUMBER,
to: phone
});
const verification: SMSVerification = {
phone,
code,
expiresAt: new Date(Date.now() + CODE_EXPIRY_MS),
attempts: 0
};
this.verifications.set(phone, verification);
await this.saveVerifications();
console.log(`📱 SMS code sent to ${phone}`);
return {
success: true,
message: 'Verification code sent via SMS'
};
} catch (error: any) {
console.error('Failed to send SMS:', error);
return {
success: false,
message: `Failed to send SMS: ${error.message}`
};
}
}
verifyCode(phone: string, code: string): { success: boolean; message: string } {
const verification = this.verifications.get(phone);
if (!verification) {
return {
success: false,
message: 'No verification code found for this phone number'
};
}
// Check expiry
if (new Date() > verification.expiresAt) {
this.verifications.delete(phone);
this.saveVerifications();
return {
success: false,
message: 'Verification code expired'
};
}
// Check attempts
if (verification.attempts >= MAX_ATTEMPTS) {
this.verifications.delete(phone);
this.saveVerifications();
return {
success: false,
message: 'Maximum verification attempts exceeded'
};
}
// Verify code
if (verification.code !== code) {
verification.attempts++;
this.saveVerifications();
return {
success: false,
message: `Invalid code (${MAX_ATTEMPTS - verification.attempts} attempts remaining)`
};
}
// Success - remove verification
this.verifications.delete(phone);
this.saveVerifications();
console.log(`✅ SMS verified for ${phone}`);
return {
success: true,
message: 'Phone number verified successfully'
};
}
// Clean up expired verifications
cleanup() {
const now = new Date();
let cleaned = 0;
for (const [phone, verification] of this.verifications.entries()) {
if (now > verification.expiresAt) {
this.verifications.delete(phone);
cleaned++;
}
}
if (cleaned > 0) {
console.log(`🧹 Cleaned up ${cleaned} expired SMS verifications`);
this.saveVerifications();
}
}
}