← back to Designer Wallcoverings
DW-Agents/dw-agents/global-auth-system/auth-server.ts
985 lines
/**
* DW Global Authentication Server
* Google OAuth SSO with SMS 2FA and Session Management
* Port: 9999
*/
import express, { Request, Response, NextFunction } from 'express';
import session from 'express-session';
import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs/promises';
import { SessionManager } from './session-manager';
import { SMSVerifier } from './sms-verifier';
import { User, AccessLog, SecurityAlert } from './types';
import Anthropic from '@anthropic-ai/sdk';
dotenv.config({ path: path.join(__dirname, '..', '.env') });
dotenv.config({ path: path.join(__dirname, '.env') });
const app = express();
const PORT = process.env.AUTH_PORT || 9999;
// Initialize managers
const sessionManager = new SessionManager();
const smsVerifier = new SMSVerifier();
// Users database (in production, use a real database)
const USERS_FILE = path.join(__dirname, 'data', 'users.json');
const ACCESS_LOGS_FILE = path.join(__dirname, 'data', 'access-logs.json');
const SECURITY_ALERTS_FILE = path.join(__dirname, 'data', 'security-alerts.json');
let users: Map<string, User> = new Map();
let accessLogs: AccessLog[] = [];
let securityAlerts: SecurityAlert[] = [];
// Load data
async function loadData() {
try {
const usersData = await fs.readFile(USERS_FILE, 'utf-8');
const usersArray: User[] = JSON.parse(usersData);
usersArray.forEach(u => {
u.createdAt = new Date(u.createdAt);
u.lastLogin = new Date(u.lastLogin);
if (u.termsAcceptedAt) u.termsAcceptedAt = new Date(u.termsAcceptedAt);
users.set(u.id, u);
});
console.log(`👥 Loaded ${users.size} users`);
} catch (error) {
console.log('👥 No existing users found');
}
try {
const logsData = await fs.readFile(ACCESS_LOGS_FILE, 'utf-8');
accessLogs = JSON.parse(logsData);
accessLogs.forEach(log => log.timestamp = new Date(log.timestamp));
console.log(`📊 Loaded ${accessLogs.length} access logs`);
} catch (error) {
console.log('📊 No existing access logs found');
}
try {
const alertsData = await fs.readFile(SECURITY_ALERTS_FILE, 'utf-8');
securityAlerts = JSON.parse(alertsData);
securityAlerts.forEach(alert => alert.timestamp = new Date(alert.timestamp));
console.log(`🚨 Loaded ${securityAlerts.length} security alerts`);
} catch (error) {
console.log('🚨 No existing security alerts found');
}
}
async function saveUsers() {
await fs.mkdir(path.dirname(USERS_FILE), { recursive: true });
const usersArray = Array.from(users.values());
await fs.writeFile(USERS_FILE, JSON.stringify(usersArray, null, 2));
}
async function saveAccessLogs() {
await fs.mkdir(path.dirname(ACCESS_LOGS_FILE), { recursive: true });
await fs.writeFile(ACCESS_LOGS_FILE, JSON.stringify(accessLogs, null, 2));
}
async function saveSecurityAlerts() {
await fs.mkdir(path.dirname(SECURITY_ALERTS_FILE), { recursive: true});
await fs.writeFile(SECURITY_ALERTS_FILE, JSON.stringify(securityAlerts, null, 2));
}
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({
secret: process.env.SESSION_SECRET || 'dw-global-auth-secret-2025',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // Set true if using HTTPS
httpOnly: true,
maxAge: 4 * 60 * 60 * 1000 // 4 hours
}
}));
app.use(passport.initialize());
app.use(passport.session());
// Passport configuration
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
callbackURL: process.env.GOOGLE_CALLBACK_URL || `http://45.61.58.125:${PORT}/auth/google/callback`
},
async (accessToken, refreshToken, profile, done) => {
try {
const email = profile.emails?.[0]?.value;
if (!email) {
return done(new Error('No email found in Google profile'));
}
// Check if user is blocked
const blockedUsers = (process.env.BLOCKED_USERS || '').split(',');
if (blockedUsers.includes(email)) {
logAccess({
timestamp: new Date(),
userId: '',
email,
action: 'blocked',
ipAddress: '',
userAgent: '',
success: false,
details: 'Blocked user attempted login'
});
return done(new Error('Access denied'));
}
// Find or create user
let user = Array.from(users.values()).find(u => u.email === email);
if (!user) {
user = {
id: `user_${Date.now()}`,
email,
name: profile.displayName || email,
picture: profile.photos?.[0]?.value,
googleId: profile.id,
phoneVerified: false,
role: isAdmin(email) ? 'admin' : 'user',
createdAt: new Date(),
lastLogin: new Date(),
isBlocked: false,
termsAccepted: false
};
users.set(user.id, user);
await saveUsers();
console.log(`✨ New user registered: ${email}`);
} else {
user.lastLogin = new Date();
await saveUsers();
}
return done(null, user);
} catch (error) {
return done(error);
}
}
));
passport.serializeUser((user: any, done) => {
done(null, user.id);
});
passport.deserializeUser((id: string, done) => {
const user = users.get(id);
done(null, user || null);
});
// Helper functions
function isAdmin(email: string): boolean {
const adminEmails = (process.env.ADMIN_EMAILS || '').split(',');
return adminEmails.includes(email);
}
function logAccess(log: AccessLog) {
accessLogs.push(log);
saveAccessLogs();
// Check for suspicious activity
checkSuspiciousActivity(log);
}
function checkSuspiciousActivity(log: AccessLog) {
// Multiple failed logins
const recentFailed = accessLogs.filter(l =>
l.email === log.email &&
l.action === 'failed_login' &&
(Date.now() - l.timestamp.getTime()) < 15 * 60 * 1000 // Last 15 minutes
);
if (recentFailed.length >= 3) {
createSecurityAlert({
id: `alert_${Date.now()}`,
timestamp: new Date(),
severity: 'high',
type: 'multiple_failures',
email: log.email,
ipAddress: log.ipAddress,
details: `${recentFailed.length} failed login attempts in 15 minutes`,
resolved: false
});
}
}
function createSecurityAlert(alert: SecurityAlert) {
securityAlerts.push(alert);
saveSecurityAlerts();
console.log(`🚨 Security Alert: ${alert.type} - ${alert.details}`);
}
// Authentication routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login?error=auth_failed' }),
(req: Request, res: Response) => {
const user = req.user as User;
if (!user.termsAccepted) {
return res.redirect('/terms');
}
// Create session
const session = sessionManager.createSession(
user,
req.ip || req.socket.remoteAddress || '',
req.get('user-agent') || ''
);
res.cookie('dw_session', session.sessionId, {
httpOnly: true,
maxAge: 4 * 60 * 60 * 1000
});
logAccess({
timestamp: new Date(),
userId: user.id,
email: user.email,
action: 'login',
ipAddress: req.ip || '',
userAgent: req.get('user-agent') || '',
success: true
});
res.redirect('/dashboard');
}
);
// Terms acceptance
app.get('/terms', (req: Request, res: Response) => {
if (!req.user) return res.redirect('/login');
res.send(getTermsPage(req));
});
app.post('/terms/accept', async (req: Request, res: Response) => {
const user = req.user as User;
if (!user) return res.redirect('/login');
user.termsAccepted = true;
user.termsAcceptedAt = new Date();
await saveUsers();
// Create session
const session = sessionManager.createSession(
user,
req.ip || '',
req.get('user-agent') || ''
);
res.cookie('dw_session', session.sessionId, {
httpOnly: true,
maxAge: 4 * 60 * 60 * 1000
});
res.redirect('/dashboard');
});
// SMS verification routes
app.get('/verify-sms', (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session;
const session = sessionManager.getSession(sessionId);
if (!session) return res.redirect('/login');
res.send(getSMSVerificationPage(session.email));
});
app.post('/verify-sms/send', async (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session;
const session = sessionManager.getSession(sessionId);
if (!session) return res.json({ success: false, message: 'Invalid session' });
const { phone } = req.body;
const user = users.get(session.userId);
if (!user) return res.json({ success: false, message: 'User not found' });
// Save phone number
user.phone = phone;
await saveUsers();
const result = await smsVerifier.sendVerificationCode(phone, user.name);
logAccess({
timestamp: new Date(),
userId: user.id,
email: user.email,
action: 'sms_sent',
ipAddress: req.ip || '',
userAgent: req.get('user-agent') || '',
success: result.success,
details: phone
});
res.json(result);
});
app.post('/verify-sms/verify', async (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session;
const session = sessionManager.getSession(sessionId);
if (!session) return res.json({ success: false, message: 'Invalid session' });
const { code } = req.body;
const user = users.get(session.userId);
if (!user || !user.phone) {
return res.json({ success: false, message: 'Phone number not found' });
}
const result = smsVerifier.verifyCode(user.phone, code);
if (result.success) {
user.phoneVerified = true;
await saveUsers();
sessionManager.verifySMS(sessionId);
logAccess({
timestamp: new Date(),
userId: user.id,
email: user.email,
action: 'sms_verified',
ipAddress: req.ip || '',
userAgent: req.get('user-agent') || '',
success: true
});
}
res.json(result);
});
// Dashboard
app.get('/dashboard', (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session;
const session = sessionManager.getSession(sessionId);
if (!session) return res.redirect('/login');
// Check if SMS verification required
if (session.requiresSMS && !session.smsVerified) {
return res.redirect('/verify-sms');
}
const user = users.get(session.userId);
if (!user) return res.redirect('/login');
res.send(getDashboardPage(user, session));
});
// Login page
app.get('/login', (req: Request, res: Response) => {
res.send(getLoginPage(req.query.error as string));
});
// Logout
app.get('/logout', (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session;
if (sessionId) {
const session = sessionManager.endSession(sessionId);
if (session) {
logAccess({
timestamp: new Date(),
userId: session.userId,
email: session.email,
action: 'logout',
ipAddress: req.ip || '',
userAgent: req.get('user-agent') || '',
success: true
});
}
}
res.clearCookie('dw_session');
req.logout(() => {
res.redirect('/login');
});
});
// Root route - redirect to login
app.get('/', (req: Request, res: Response) => {
res.redirect('/login');
});
// API endpoints
app.get('/api/validate-session', (req: Request, res: Response) => {
const sessionId = req.cookies.dw_session || req.query.session;
const session = sessionManager.getSession(sessionId as string);
if (!session) {
return res.json({ valid: false, requiresAuth: true });
}
if (session.requiresSMS && !session.smsVerified) {
return res.json({ valid: false, requiresSMS: true, redirectUrl: '/verify-sms' });
}
sessionManager.updateActivity(sessionId as string);
res.json({
valid: true,
user: {
email: session.email,
userId: session.userId
}
});
});
// Start server
loadData().then(() => {
app.listen(PORT, () => {
console.log(`
🔐 DW Global Authentication Server
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🌍 External: http://45.61.58.125:${PORT}
🏠 Local: http://localhost:${PORT}
🔑 Google OAuth: ${process.env.GOOGLE_CLIENT_ID ? 'Configured' : 'NOT CONFIGURED'}
📱 SMS 2FA: ${process.env.TWILIO_ACCOUNT_SID ? 'Enabled' : 'Disabled (Dev Mode)'}
✅ Authentication server ready...
`);
});
// Cleanup expired verifications every hour
setInterval(() => smsVerifier.cleanup(), 60 * 60 * 1000);
});
// HTML page generators (continued in next file due to length)
function getLoginPage(error?: string): string {
return `<!DOCTYPE html>
<html>
<head>
<title>DW-Agents - Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.login-box {
background: white;
padding: 60px 50px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 450px;
width: 100%;
text-align: center;
}
.logo { font-size: 4em; margin-bottom: 20px; }
h1 { color: #333; margin-bottom: 10px; font-size: 2em; }
.subtitle { color: #666; margin-bottom: 30px; font-size: 1.1em; }
.error {
background: #fee;
color: #c33;
padding: 15px;
border-radius: 10px;
margin-bottom: 20px;
font-weight: 600;
}
.google-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
background: white;
border: 2px solid #ddd;
padding: 15px 30px;
border-radius: 50px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
color: #333;
}
.google-btn:hover {
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.3);
}
.info {
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid #eee;
color: #666;
font-size: 0.9em;
}
</style>
</head>
<body>
<div class="login-box">
<div class="logo">🔐</div>
<h1>DW-Agents</h1>
<div class="subtitle">Global Authentication Portal</div>
${error ? `<div class="error">Authentication failed. Please try again.</div>` : ''}
<a href="/auth/google" class="google-btn">
<svg width="24" height="24" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Sign in with Google
</a>
<div class="info">
Secure authentication powered by Google OAuth 2.0<br>
Sessions are valid for 4 hours with SMS verification
</div>
</div>
</body>
</html>`;
}
function getTermsPage(req: Request): string {
return `<!DOCTYPE html>
<html>
<head>
<title>Terms of Service - DW-Agents</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f5f5;
padding: 40px 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 50px;
border-radius: 20px;
box-shadow: 0 5px 30px rgba(0,0,0,0.1);
}
h1 { color: #333; margin-bottom: 30px; font-size: 2em; }
h2 { color: #667eea; margin: 30px 0 15px; font-size: 1.3em; }
p { color: #666; line-height: 1.8; margin-bottom: 15px; }
.terms-box {
background: #f9f9f9;
padding: 30px;
border-radius: 15px;
margin: 30px 0;
max-height: 400px;
overflow-y: auto;
border: 2px solid #eee;
}
.accept-section {
margin-top: 30px;
padding: 30px;
background: #f0f7ff;
border-radius: 15px;
text-align: center;
}
.checkbox-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin: 20px 0;
}
.checkbox-wrapper input {
width: 24px;
height: 24px;
cursor: pointer;
}
.checkbox-wrapper label {
font-size: 1.1em;
color: #333;
font-weight: 600;
}
button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px 50px;
border-radius: 50px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>🔐 Terms of Service & Privacy Policy</h1>
<div class="terms-box">
<h2>1. Account Access & Authentication</h2>
<p>By accessing DW-Agents, you agree to authenticate via Google OAuth 2.0. Your email address will be verified and used for account identification. Session tokens are valid for 4 hours, after which SMS verification is required for continued access.</p>
<h2>2. SMS Verification & Phone Number</h2>
<p>After 4 hours of activity, you must verify your identity via SMS to continue your session. By providing your phone number, you consent to receive verification codes from Designer Wallcoverings. Your phone number is stored securely and will not be shared with third parties.</p>
<h2>3. Email Verification & Double Authentication</h2>
<p>Your email address obtained through Google OAuth serves as the first factor of authentication. SMS verification serves as the second factor. Both are required for secure access to DW-Agents systems.</p>
<h2>4. Session Monitoring & Security</h2>
<p>All sessions are monitored for security purposes. We log access times, IP addresses, user agents, and activity patterns. Error logs from your sessions may be reviewed by system administrators and assigned to appropriate agents for resolution.</p>
<h2>5. Data Collection & Usage</h2>
<p>We collect and store: email address, name, profile picture (from Google), phone number, session data, access logs, error logs, and IP addresses. This data is used solely for authentication, security monitoring, and system improvement.</p>
<h2>6. Blocked Users & Access Control</h2>
<p>Designer Wallcoverings reserves the right to block access to any user at any time. Multiple failed authentication attempts, suspicious activity, or policy violations may result in account suspension.</p>
<h2>7. Session Duration & Auto-Logout</h2>
<p>Sessions automatically expire after 4 hours. You will be required to re-authenticate via SMS to extend your session. Maximum continuous session time is 4 hours without SMS re-verification.</p>
<h2>8. Legal Compliance</h2>
<p>By accepting these terms, you agree to comply with all applicable laws and Designer Wallcoverings policies. Unauthorized access attempts, data breaches, or malicious activity will be reported to appropriate authorities.</p>
<h2>9. Changes to Terms</h2>
<p>Designer Wallcoverings may update these terms at any time. Continued use of DW-Agents after changes constitutes acceptance of updated terms.</p>
<p><strong>Last Updated: January 2025</strong></p>
</div>
<div class="accept-section">
<h2>📧 Email Verification & Double Authentication</h2>
<p>By accepting, you confirm that:</p>
<ul style="text-align: left; margin: 20px 0; line-height: 2;">
<li>Your email address (<strong>${(req.user as any)?.email}</strong>) will be used for authentication</li>
<li>You consent to SMS verification codes being sent to your phone</li>
<li>You agree to double authentication (email + SMS)</li>
<li>You accept all terms outlined above</li>
</ul>
<form method="POST" action="/terms/accept" id="acceptForm">
<div class="checkbox-wrapper">
<input type="checkbox" id="acceptCheckbox" name="accept" required>
<label for="acceptCheckbox">I accept the Terms of Service and Privacy Policy</label>
</div>
<button type="submit" id="acceptBtn" disabled>Continue to DW-Agents</button>
</form>
</div>
</div>
<script>
const checkbox = document.getElementById('acceptCheckbox');
const button = document.getElementById('acceptBtn');
checkbox.addEventListener('change', () => {
button.disabled = !checkbox.checked;
});
</script>
</body>
</html>`;
}
function getDashboardPage(user: User, session: any): string {
const agents = [
{ name: 'Master Hub', port: 9893, icon: '🤖' },
{ name: 'CEO Dashboard', port: 7121, icon: '👔' },
{ name: 'CFO Dashboard', port: 7122, icon: '💼' },
{ name: 'COO Dashboard', port: 7124, icon: '⚙️' },
{ name: 'VP Operations', port: 7123, icon: '📊' },
{ name: 'Accounting', port: 9882, icon: '💰' },
{ name: 'Legal Team', port: 9878, icon: '⚖️' },
{ name: 'Marketing', port: 9881, icon: '📢' },
{ name: 'Security', port: 9892, icon: '🔒' }
];
const agentCards = agents.map(agent => `
<a href="http://45.61.58.125:${agent.port}?session=${session.sessionId}" class="agent-card">
<div class="agent-icon">${agent.icon}</div>
<div class="agent-name">${agent.name}</div>
<div class="agent-port">Port ${agent.port}</div>
</a>
`).join('');
return `<!DOCTYPE html>
<html>
<head>
<title>DW-Agents Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f5f5;
padding: 0;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px 40px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.user-info {
display: flex;
align-items: center;
gap: 20px;
}
.user-pic {
width: 50px;
height: 50px;
border-radius: 50%;
border: 3px solid white;
}
.logout-btn {
background: rgba(255,255,255,0.2);
color: white;
border: 2px solid white;
padding: 10px 25px;
border-radius: 50px;
text-decoration: none;
font-weight: 600;
transition: all 0.3s;
}
.logout-btn:hover {
background: white;
color: #667eea;
}
.container {
max-width: 1400px;
margin: 40px auto;
padding: 0 40px;
}
h1 { color: #333; margin-bottom: 30px; font-size: 2.5em; }
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 25px;
margin: 30px 0;
}
.agent-card {
background: white;
padding: 40px 30px;
border-radius: 20px;
text-align: center;
text-decoration: none;
color: #333;
transition: all 0.3s;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
border: 2px solid transparent;
}
.agent-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.3);
border-color: #667eea;
}
.agent-icon {
font-size: 4em;
margin-bottom: 15px;
}
.agent-name {
font-size: 1.3em;
font-weight: 600;
margin-bottom: 8px;
}
.agent-port {
color: #999;
font-size: 0.9em;
}
.session-info {
background: white;
padding: 25px;
border-radius: 15px;
margin-bottom: 30px;
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
}
.session-info strong { color: #667eea; }
</style>
</head>
<body>
<div class="header">
<div class="user-info">
${user.picture ? `<img src="${user.picture}" class="user-pic" alt="Profile">` : '<div class="user-pic" style="background: white; display: flex; align-items: center; justify-content: center; font-size: 24px;">👤</div>'}
<div>
<div style="font-size: 1.3em; font-weight: 600;">${user.name}</div>
<div style="opacity: 0.9;">${user.email}</div>
</div>
</div>
<a href="/logout" class="logout-btn">Logout</a>
</div>
<div class="container">
<div class="session-info">
<strong>Session Active:</strong> Expires at ${new Date(session.expiresAt).toLocaleTimeString()} |
<strong>Session ID:</strong> ${session.sessionId}
</div>
<h1>🔐 Your Agents</h1>
<div class="agents-grid">
${agentCards}
</div>
</div>
</body>
</html>`;
}
function getSMSVerificationPage(email: string): string {
return `<!DOCTYPE html>
<html>
<head>
<title>SMS Verification Required</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.box {
background: white;
padding: 50px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
max-width: 500px;
width: 100%;
text-align: center;
}
h1 { color: #333; margin: 20px 0; font-size: 2em; }
.subtitle { color: #666; margin-bottom: 30px; line-height: 1.6; }
input {
width: 100%;
padding: 15px;
margin: 10px 0;
border: 2px solid #ddd;
border-radius: 10px;
font-size: 1.1em;
}
button {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 15px;
border-radius: 10px;
font-size: 1.1em;
font-weight: 600;
cursor: pointer;
margin-top: 10px;
}
.message {
margin: 15px 0;
padding: 15px;
border-radius: 10px;
display: none;
}
.success { background: #d4edda; color: #155724; display: block; }
.error { background: #f8d7da; color: #721c24; display: block; }
#verifyStep { display: none; }
</style>
</head>
<body>
<div class="box">
<div style="font-size: 4em;">📱</div>
<h1>SMS Verification Required</h1>
<div class="subtitle">
Your session has been active for 4 hours.<br>
Please verify your phone number to continue.
</div>
<div id="phoneStep">
<input type="tel" id="phone" placeholder="+1234567890" required>
<button onclick="sendCode()">Send Verification Code</button>
<div id="phoneMessage" class="message"></div>
</div>
<div id="verifyStep">
<input type="text" id="code" placeholder="Enter 6-digit code" maxlength="6" required>
<button onclick="verifyCode()">Verify & Continue</button>
<div id="verifyMessage" class="message"></div>
</div>
</div>
<script>
async function sendCode() {
const phone = document.getElementById('phone').value;
const messageDiv = document.getElementById('phoneMessage');
if (!phone) {
messageDiv.className = 'message error';
messageDiv.textContent = 'Please enter a phone number';
return;
}
const response = await fetch('/verify-sms/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ phone })
});
const result = await response.json();
if (result.success) {
messageDiv.className = 'message success';
messageDiv.textContent = result.message;
document.getElementById('phoneStep').style.display = 'none';
document.getElementById('verifyStep').style.display = 'block';
} else {
messageDiv.className = 'message error';
messageDiv.textContent = result.message;
}
}
async function verifyCode() {
const code = document.getElementById('code').value;
const messageDiv = document.getElementById('verifyMessage');
if (!code || code.length !== 6) {
messageDiv.className = 'message error';
messageDiv.textContent = 'Please enter a 6-digit code';
return;
}
const response = await fetch('/verify-sms/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const result = await response.json();
if (result.success) {
messageDiv.className = 'message success';
messageDiv.textContent = result.message + ' Redirecting...';
setTimeout(() => window.location.href = '/dashboard', 1500);
} else {
messageDiv.className = 'message error';
messageDiv.textContent = result.message;
}
}
</script>
</body>
</html>`;
}