HEX
Server: Apache/2.4.65 (Debian)
System: Linux kubikelcreative 5.10.0-35-amd64 #1 SMP Debian 5.10.237-1 (2025-05-19) x86_64
User: www-data (33)
PHP: 8.4.13
Disabled: NONE
Upload Files
File: /var/www/indoadvisory_new/web2/webapp/middleware/auth.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const { db } = require('../config/database');

// Enterprise Authentication Middleware
// ===================================

/**
 * Require user to be authenticated
 */
const requireAuth = async (req, res, next) => {
  try {
    // Check session-based authentication
    if (!req.session.user) {
      req.flash('error', 'Please log in to access this page');
      return res.redirect('/auth/login');
    }
    
    // Verify user still exists and is active
    const result = await db.query(
      'SELECT id, email, name, role, is_active FROM users WHERE id = $1 AND is_active = true',
      [req.session.user.id]
    );
    
    if (result.rows.length === 0) {
      req.session.destroy();
      req.flash('error', 'Your account is no longer active');
      return res.redirect('/auth/login');
    }
    
    // Update user info in session
    req.session.user = result.rows[0];
    req.user = result.rows[0];
    
    next();
  } catch (error) {
    console.error('Auth middleware error:', error);
    res.status(500).render('error', {
      title: 'Authentication Error',
      message: 'An error occurred during authentication',
      error: {}
    });
  }
};

/**
 * Require user to have admin role
 */
const requireAdmin = async (req, res, next) => {
  try {
    if (!req.user || req.user.role !== 'admin') {
      req.flash('error', 'Admin access required');
      return res.redirect('/');
    }
    next();
  } catch (error) {
    console.error('Admin middleware error:', error);
    res.status(403).render('error', {
      title: 'Access Denied',
      message: 'Admin access required',
      error: { status: 403 }
    });
  }
};

/**
 * Optional authentication (for pages that show different content for logged in users)
 */
const optionalAuth = async (req, res, next) => {
  try {
    if (req.session.user) {
      const result = await db.query(
        'SELECT id, email, name, role, is_active FROM users WHERE id = $1 AND is_active = true',
        [req.session.user.id]
      );
      
      if (result.rows.length > 0) {
        req.user = result.rows[0];
      } else {
        req.session.destroy();
      }
    }
    next();
  } catch (error) {
    console.error('Optional auth middleware error:', error);
    next(); // Continue even if there's an error
  }
};

/**
 * Redirect if already authenticated
 */
const redirectIfAuth = (req, res, next) => {
  if (req.session.user) {
    return res.redirect('/admin');
  }
  next();
};

/**
 * Validate login credentials with enterprise security features
 */
const validateLogin = async (email, password, req) => {
  try {
    // Get user with login attempt tracking
    const result = await db.query(
      `SELECT id, email, password_hash, name, role, is_active, 
              failed_login_attempts, locked_until 
       FROM users 
       WHERE email = $1`,
      [email.toLowerCase()]
    );
    
    if (result.rows.length === 0) {
      // Log failed attempt for audit
      await logAuditEvent(null, 'login_failed', 'user', null, req, { reason: 'user_not_found', email });
      return { success: false, message: 'Invalid email or password' };
    }
    
    const user = result.rows[0];
    
    // Check if user is active
    if (!user.is_active) {
      await logAuditEvent(user.id, 'login_failed', 'user', user.id, req, { reason: 'account_disabled' });
      return { success: false, message: 'Your account has been disabled' };
    }
    
    // Check if account is locked
    if (user.locked_until && new Date(user.locked_until) > new Date()) {
      const lockTime = new Date(user.locked_until).toLocaleString();
      await logAuditEvent(user.id, 'login_failed', 'user', user.id, req, { reason: 'account_locked', locked_until: lockTime });
      return { success: false, message: `Account is locked until ${lockTime}` };
    }
    
    // Verify password
    const isValidPassword = await bcrypt.compare(password, user.password_hash);
    
    if (!isValidPassword) {
      // Increment failed attempts
      const newFailedAttempts = user.failed_login_attempts + 1;
      let lockUntil = null;
      
      // Lock account after 5 failed attempts for 30 minutes
      if (newFailedAttempts >= 5) {
        lockUntil = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes
      }
      
      await db.query(
        'UPDATE users SET failed_login_attempts = $1, locked_until = $2 WHERE id = $3',
        [newFailedAttempts, lockUntil, user.id]
      );
      
      await logAuditEvent(user.id, 'login_failed', 'user', user.id, req, { 
        reason: 'invalid_password', 
        failed_attempts: newFailedAttempts,
        account_locked: !!lockUntil
      });
      
      if (lockUntil) {
        return { success: false, message: 'Too many failed attempts. Account locked for 30 minutes.' };
      }
      
      return { success: false, message: 'Invalid email or password' };
    }
    
    // Successful login - reset failed attempts and update last login
    await db.query(
      `UPDATE users 
       SET failed_login_attempts = 0, locked_until = NULL, last_login = CURRENT_TIMESTAMP 
       WHERE id = $1`,
      [user.id]
    );
    
    await logAuditEvent(user.id, 'login_success', 'user', user.id, req);
    
    return { 
      success: true, 
      user: {
        id: user.id,
        email: user.email,
        name: user.name,
        role: user.role
      }
    };
    
  } catch (error) {
    console.error('Login validation error:', error);
    return { success: false, message: 'An error occurred during login' };
  }
};

/**
 * Generate secure password hash
 */
const hashPassword = async (password) => {
  const rounds = parseInt(process.env.BCRYPT_ROUNDS) || 12;
  return await bcrypt.hash(password, rounds);
};

/**
 * Generate JWT token for API access
 */
const generateJWT = (user) => {
  const payload = {
    id: user.id,
    email: user.email,
    role: user.role
  };
  
  return jwt.sign(payload, process.env.JWT_SECRET, { 
    expiresIn: '24h',
    issuer: 'indo-advisory',
    audience: 'api'
  });
};

/**
 * Verify JWT token
 */
const verifyJWT = (token) => {
  try {
    return jwt.verify(token, process.env.JWT_SECRET);
  } catch (error) {
    return null;
  }
};

/**
 * Log audit events for enterprise security
 */
const logAuditEvent = async (userId, action, resourceType, resourceId, req, details = {}) => {
  try {
    const clientIP = req.ip || req.connection.remoteAddress || req.socket.remoteAddress;
    const userAgent = req.get('User-Agent');
    
    await db.query(
      `INSERT INTO audit_logs (user_id, action, resource_type, resource_id, ip_address, user_agent, details)
       VALUES ($1, $2, $3, $4, $5, $6, $7)`,
      [userId, action, resourceType, resourceId, clientIP, userAgent, JSON.stringify(details)]
    );
  } catch (error) {
    console.error('Audit logging error:', error);
  }
};

/**
 * Middleware to require API authentication
 */
const requireAPIAuth = async (req, res, next) => {
  try {
    const token = req.header('Authorization')?.replace('Bearer ', '');
    
    if (!token) {
      return res.status(401).json({ error: 'Access token required' });
    }
    
    const payload = verifyJWT(token);
    if (!payload) {
      return res.status(401).json({ error: 'Invalid or expired token' });
    }
    
    // Verify user still exists
    const result = await db.query(
      'SELECT id, email, name, role, is_active FROM users WHERE id = $1 AND is_active = true',
      [payload.id]
    );
    
    if (result.rows.length === 0) {
      return res.status(401).json({ error: 'User not found or inactive' });
    }
    
    req.user = result.rows[0];
    next();
  } catch (error) {
    console.error('API auth middleware error:', error);
    res.status(500).json({ error: 'Authentication error' });
  }
};

module.exports = {
  requireAuth,
  requireAdmin,
  optionalAuth,
  redirectIfAuth,
  validateLogin,
  hashPassword,
  generateJWT,
  verifyJWT,
  logAuditEvent,
  requireAPIAuth
};