← back to Designer Wallcoverings
DW-Agents/dw-agents/global-auth-system/session-manager.ts
208 lines
/**
* DW Global Auth - Session Manager
* Handles session creation, validation, and timeout management
*/
import { Session, User, ErrorLog } from './types';
import fs from 'fs/promises';
import path from 'path';
const SESSIONS_FILE = path.join(__dirname, 'data', 'sessions.json');
const FOUR_HOURS_MS = 4 * 60 * 60 * 1000;
export class SessionManager {
private sessions: Map<string, Session> = new Map();
constructor() {
this.loadSessions();
// Clean up expired sessions every 5 minutes
setInterval(() => this.cleanupExpiredSessions(), 5 * 60 * 1000);
}
private async loadSessions() {
try {
const data = await fs.readFile(SESSIONS_FILE, 'utf-8');
const sessions: Session[] = JSON.parse(data);
sessions.forEach(s => {
s.startTime = new Date(s.startTime);
s.lastActivity = new Date(s.lastActivity);
s.expiresAt = new Date(s.expiresAt);
this.sessions.set(s.sessionId, s);
});
console.log(`📋 Loaded ${this.sessions.size} sessions`);
} catch (error) {
console.log('📋 No existing sessions found, starting fresh');
}
}
private async saveSessions() {
try {
await fs.mkdir(path.dirname(SESSIONS_FILE), { recursive: true });
const sessionsArray = Array.from(this.sessions.values());
await fs.writeFile(SESSIONS_FILE, JSON.stringify(sessionsArray, null, 2));
} catch (error) {
console.error('Failed to save sessions:', error);
}
}
createSession(user: User, ipAddress: string, userAgent: string): Session {
const now = new Date();
const session: Session = {
sessionId: this.generateSessionId(),
userId: user.id,
email: user.email,
startTime: now,
lastActivity: now,
expiresAt: new Date(now.getTime() + FOUR_HOURS_MS),
ipAddress,
userAgent,
requiresSMS: false,
smsVerified: true, // First login doesn't require SMS
agentsAccessed: [],
errorLogs: [],
active: true
};
this.sessions.set(session.sessionId, session);
this.saveSessions();
console.log(`✅ Created session for ${user.email} (expires in 4 hours)`);
return session;
}
getSession(sessionId: string): Session | null {
const session = this.sessions.get(sessionId);
if (!session) return null;
// Check if expired
if (new Date() > session.expiresAt) {
session.active = false;
this.saveSessions();
return null;
}
// Check if requires SMS re-verification (4 hours passed)
const fourHoursPassed = new Date().getTime() - session.startTime.getTime() >= FOUR_HOURS_MS;
if (fourHoursPassed && !session.smsVerified) {
session.requiresSMS = true;
this.saveSessions();
}
return session;
}
updateActivity(sessionId: string, agentPort?: number) {
const session = this.sessions.get(sessionId);
if (!session) return;
session.lastActivity = new Date();
if (agentPort && !session.agentsAccessed.includes(String(agentPort))) {
session.agentsAccessed.push(String(agentPort));
}
this.saveSessions();
}
logError(sessionId: string, error: ErrorLog) {
const session = this.sessions.get(sessionId);
if (!session) return;
session.errorLogs.push(error);
this.saveSessions();
}
requireSMSVerification(sessionId: string) {
const session = this.sessions.get(sessionId);
if (!session) return;
session.requiresSMS = true;
session.smsVerified = false;
this.saveSessions();
}
verifySMS(sessionId: string) {
const session = this.sessions.get(sessionId);
if (!session) return;
session.smsVerified = true;
session.requiresSMS = false;
// Extend session by 4 more hours
session.expiresAt = new Date(Date.now() + FOUR_HOURS_MS);
this.saveSessions();
}
endSession(sessionId: string) {
const session = this.sessions.get(sessionId);
if (!session) return;
session.active = false;
const duration = new Date().getTime() - session.startTime.getTime();
console.log(`🔚 Session ended for ${session.email} (duration: ${this.formatDuration(duration)})`);
this.saveSessions();
return session;
}
getAllSessions(): Session[] {
return Array.from(this.sessions.values());
}
getActiveSessions(): Session[] {
return Array.from(this.sessions.values()).filter(s => s.active && new Date() < s.expiresAt);
}
getUserSessions(userId: string): Session[] {
return Array.from(this.sessions.values())
.filter(s => s.userId === userId)
.sort((a, b) => b.startTime.getTime() - a.startTime.getTime());
}
private cleanupExpiredSessions() {
const now = new Date();
let cleaned = 0;
for (const [sessionId, session] of this.sessions.entries()) {
if (now > session.expiresAt) {
session.active = false;
cleaned++;
}
}
if (cleaned > 0) {
console.log(`🧹 Cleaned up ${cleaned} expired sessions`);
this.saveSessions();
}
}
private generateSessionId(): string {
return `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
private formatDuration(ms: number): string {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) return `${hours}h ${minutes % 60}m`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
}
getSessionStats() {
const all = this.getAllSessions();
const active = this.getActiveSessions();
return {
total: all.length,
active: active.length,
expired: all.length - active.length,
requiresSMS: active.filter(s => s.requiresSMS).length,
totalDuration: all.reduce((sum, s) => {
const end = s.active ? new Date() : s.expiresAt;
return sum + (end.getTime() - s.startTime.getTime());
}, 0)
};
}
}