← 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)
    };
  }
}