PHP 8.2.30
Preview: advancedAuthController.js Size: 15.34 KB
/home/byroehnu/easepay.easetack.com/controllers/advancedAuthController.js

const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const { prisma } = require('../config/prisma');

// Generate JWT token with extended expiry for auto-login
const generateToken = (id, extended = false) => {
  const expiresIn = extended ? '30d' : (process.env.JWT_EXPIRES_IN || '7d');
  return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn });
};

// Generate session token
const generateSessionToken = () => {
  return crypto.randomBytes(32).toString('hex');
};

// Hash PIN securely
const hashPIN = async (pin) => {
  const salt = await bcrypt.genSalt(12);
  return bcrypt.hash(pin, salt);
};

// Setup PIN for user
const setupPIN = async (req, res) => {
  try {
    const { pin, confirmPin } = req.body;
    const userId = req.user.id;

    // Validation
    if (!pin || !confirmPin) {
      return res.status(400).json({
        success: false,
        message: 'Please provide PIN and confirmation'
      });
    }

    if (pin !== confirmPin) {
      return res.status(400).json({
        success: false,
        message: 'PINs do not match'
      });
    }

    if (!/^\d{4,6}$/.test(pin)) {
      return res.status(400).json({
        success: false,
        message: 'PIN must be 4-6 digits'
      });
    }

    // Hash and save PIN
    const hashedPin = await hashPIN(pin);

    await prisma.user.update({
      where: { id: userId },
      data: {
        pin: hashedPin,
        pinSetAt: new Date()
      }
    });

    console.log(`✅ PIN setup completed for user ${userId}`);

    res.status(200).json({
      success: true,
      message: 'PIN setup successful'
    });

  } catch (error) {
    console.error('Setup PIN error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to setup PIN'
    });
  }
};

// PIN-based login
const loginWithPIN = async (req, res) => {
  try {
    const { phone, pin, deviceId, deviceType } = req.body;

    if (!phone || !pin) {
      return res.status(400).json({
        success: false,
        message: 'Please provide phone number and PIN'
      });
    }

    console.log(`🔐 PIN login attempt for ${phone}`);

    // Find user
    const user = await prisma.user.findUnique({
      where: { phone },
      select: {
        id: true,
        phone: true,
        pin: true,
        firstName: true,
        lastName: true,
        email: true,
        role: true,
        isActive: true,
        phoneVerified: true,
        loginAttempts: true,
        lockedUntil: true
      }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    // Check if account is locked
    if (user.lockedUntil && new Date() < user.lockedUntil) {
      return res.status(423).json({
        success: false,
        message: 'Account temporarily locked. Try again later.'
      });
    }

    // Check if user is active
    if (!user.isActive) {
      return res.status(401).json({
        success: false,
        message: 'Account has been deactivated'
      });
    }

    // Check if PIN is set
    if (!user.pin) {
      return res.status(400).json({
        success: false,
        message: 'PIN not set. Please login with OTP first.'
      });
    }

    // Verify PIN
    const isPinValid = await bcrypt.compare(pin, user.pin);

    if (!isPinValid) {
      // Increment login attempts
      const loginAttempts = (user.loginAttempts || 0) + 1;
      const updateData = { loginAttempts };

      // Lock account after 5 failed attempts
      if (loginAttempts >= 5) {
        updateData.lockedUntil = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes
      }

      await prisma.user.update({
        where: { id: user.id },
        data: updateData
      });

      return res.status(401).json({
        success: false,
        message: 'Invalid PIN',
        attemptsRemaining: Math.max(0, 5 - loginAttempts)
      });
    }

    // Successful login - reset attempts and update session
    const sessionToken = generateSessionToken();
    const sessionExpiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000); // 30 days

    await prisma.user.update({
      where: { id: user.id },
      data: {
        loginAttempts: 0,
        lockedUntil: null,
        lastLoginAt: new Date(),
        sessionToken,
        sessionExpiresAt
      }
    });

    // Register/update device if provided
    if (deviceId && deviceType) {
      await prisma.deviceToken.upsert({
        where: {
          userId_deviceId: {
            userId: user.id,
            deviceId
          }
        },
        update: {
          lastUsedAt: new Date(),
          isActive: true
        },
        create: {
          userId: user.id,
          deviceId,
          deviceType,
          lastUsedAt: new Date()
        }
      });
    }

    // Generate tokens
    const token = generateToken(user.id, true);

    console.log(`✅ PIN login successful for ${phone}`);

    res.status(200).json({
      success: true,
      message: 'Login successful',
      token,
      sessionToken,
      user: {
        id: user.id,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        role: user.role,
        phoneVerified: user.phoneVerified
      }
    });

  } catch (error) {
    console.error('PIN login error:', error);
    res.status(500).json({
      success: false,
      message: 'Login failed'
    });
  }
};

// Enable biometric authentication
const enableBiometric = async (req, res) => {
  try {
    const { deviceId, biometricHash, deviceType } = req.body;
    const userId = req.user.id;

    if (!deviceId || !biometricHash) {
      return res.status(400).json({
        success: false,
        message: 'Device ID and biometric hash are required'
      });
    }

    // Update user biometric settings
    await prisma.user.update({
      where: { id: userId },
      data: {
        biometricEnabled: true,
        biometricHash
      }
    });

    // Register device with biometric capability
    await prisma.deviceToken.upsert({
      where: {
        userId_deviceId: {
          userId,
          deviceId
        }
      },
      update: {
        biometricId: biometricHash,
        deviceType: deviceType || 'mobile',
        lastUsedAt: new Date()
      },
      create: {
        userId,
        deviceId,
        deviceType: deviceType || 'mobile',
        biometricId: biometricHash
      }
    });

    console.log(`✅ Biometric enabled for user ${userId} on device ${deviceId}`);

    res.status(200).json({
      success: true,
      message: 'Biometric authentication enabled'
    });

  } catch (error) {
    console.error('Enable biometric error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to enable biometric authentication'
    });
  }
};

// Biometric login
const loginWithBiometric = async (req, res) => {
  try {
    const { phone, biometricHash, deviceId } = req.body;

    if (!phone || !biometricHash || !deviceId) {
      return res.status(400).json({
        success: false,
        message: 'Phone, biometric data, and device ID are required'
      });
    }

    console.log(`👆 Biometric login attempt for ${phone} on device ${deviceId}`);

    // Find user with biometric enabled
    const user = await prisma.user.findUnique({
      where: { phone },
      select: {
        id: true,
        phone: true,
        firstName: true,
        lastName: true,
        email: true,
        role: true,
        isActive: true,
        phoneVerified: true,
        biometricEnabled: true,
        biometricHash: true
      }
    });

    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }

    if (!user.isActive) {
      return res.status(401).json({
        success: false,
        message: 'Account has been deactivated'
      });
    }

    if (!user.biometricEnabled) {
      return res.status(400).json({
        success: false,
        message: 'Biometric authentication not enabled'
      });
    }

    // Verify device is registered
    const device = await prisma.deviceToken.findUnique({
      where: {
        userId_deviceId: {
          userId: user.id,
          deviceId
        }
      }
    });

    if (!device || !device.biometricId) {
      return res.status(401).json({
        success: false,
        message: 'Device not registered for biometric authentication'
      });
    }

    // In a real app, you'd verify the biometric hash against stored templates
    // For this demo, we'll check if it matches the stored hash
    if (device.biometricId !== biometricHash) {
      return res.status(401).json({
        success: false,
        message: 'Biometric authentication failed'
      });
    }

    // Successful biometric login
    const sessionToken = generateSessionToken();
    const sessionExpiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);

    await prisma.user.update({
      where: { id: user.id },
      data: {
        lastLoginAt: new Date(),
        sessionToken,
        sessionExpiresAt
      }
    });

    await prisma.deviceToken.update({
      where: {
        userId_deviceId: {
          userId: user.id,
          deviceId
        }
      },
      data: {
        lastUsedAt: new Date()
      }
    });

    const token = generateToken(user.id, true);

    console.log(`✅ Biometric login successful for ${phone}`);

    res.status(200).json({
      success: true,
      message: 'Biometric login successful',
      token,
      sessionToken,
      user: {
        id: user.id,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        role: user.role,
        phoneVerified: user.phoneVerified
      }
    });

  } catch (error) {
    console.error('Biometric login error:', error);
    res.status(500).json({
      success: false,
      message: 'Biometric login failed'
    });
  }
};

// Auto-login with session token
const autoLogin = async (req, res) => {
  try {
    const { sessionToken, deviceId } = req.body;

    if (!sessionToken) {
      return res.status(400).json({
        success: false,
        message: 'Session token is required'
      });
    }

    console.log(`🔄 Auto-login attempt with session token`);

    // Find user with valid session
    const user = await prisma.user.findFirst({
      where: {
        sessionToken,
        sessionExpiresAt: {
          gt: new Date()
        },
        isActive: true,
        autoLoginEnabled: true
      },
      select: {
        id: true,
        phone: true,
        firstName: true,
        lastName: true,
        email: true,
        role: true,
        phoneVerified: true,
        sessionExpiresAt: true
      }
    });

    if (!user) {
      return res.status(401).json({
        success: false,
        message: 'Invalid or expired session'
      });
    }

    // Update last login and device usage
    await prisma.user.update({
      where: { id: user.id },
      data: {
        lastLoginAt: new Date()
      }
    });

    if (deviceId) {
      await prisma.deviceToken.updateMany({
        where: {
          userId: user.id,
          deviceId
        },
        data: {
          lastUsedAt: new Date()
        }
      });
    }

    const token = generateToken(user.id, true);

    console.log(`✅ Auto-login successful for user ${user.id}`);

    res.status(200).json({
      success: true,
      message: 'Auto-login successful',
      token,
      user: {
        id: user.id,
        phone: user.phone,
        firstName: user.firstName,
        lastName: user.lastName,
        email: user.email,
        role: user.role,
        phoneVerified: user.phoneVerified
      }
    });

  } catch (error) {
    console.error('Auto-login error:', error);
    res.status(500).json({
      success: false,
      message: 'Auto-login failed'
    });
  }
};

// Get user security settings
const getSecuritySettings = async (req, res) => {
  try {
    const userId = req.user.id;

    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: {
        pin: true,
        biometricEnabled: true,
        autoLoginEnabled: true,
        requirePinForTransactions: true,
        pinSetAt: true,
        deviceTokens: {
          select: {
            deviceId: true,
            deviceName: true,
            deviceType: true,
            biometricId: true,
            isActive: true,
            lastUsedAt: true
          }
        }
      }
    });

    res.status(200).json({
      success: true,
      settings: {
        hasPIN: !!user.pin,
        biometricEnabled: user.biometricEnabled,
        autoLoginEnabled: user.autoLoginEnabled,
        requirePinForTransactions: user.requirePinForTransactions,
        pinSetAt: user.pinSetAt,
        registeredDevices: user.deviceTokens.length,
        devices: user.deviceTokens
      }
    });

  } catch (error) {
    console.error('Get security settings error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to get security settings'
    });
  }
};

// Update security settings
const updateSecuritySettings = async (req, res) => {
  try {
    const userId = req.user.id;
    const { autoLoginEnabled, requirePinForTransactions } = req.body;

    const updateData = {};
    if (typeof autoLoginEnabled === 'boolean') {
      updateData.autoLoginEnabled = autoLoginEnabled;
    }
    if (typeof requirePinForTransactions === 'boolean') {
      updateData.requirePinForTransactions = requirePinForTransactions;
    }

    await prisma.user.update({
      where: { id: userId },
      data: updateData
    });

    res.status(200).json({
      success: true,
      message: 'Security settings updated successfully'
    });

  } catch (error) {
    console.error('Update security settings error:', error);
    res.status(500).json({
      success: false,
      message: 'Failed to update security settings'
    });
  }
};

// Logout and invalidate session
const logout = async (req, res) => {
  try {
    const userId = req.user.id;
    const { deviceId } = req.body;

    // Clear session token
    await prisma.user.update({
      where: { id: userId },
      data: {
        sessionToken: null,
        sessionExpiresAt: null
      }
    });

    // Deactivate device if specified
    if (deviceId) {
      await prisma.deviceToken.updateMany({
        where: {
          userId,
          deviceId
        },
        data: {
          isActive: false
        }
      });
    }

    res.status(200).json({
      success: true,
      message: 'Logout successful'
    });

  } catch (error) {
    console.error('Logout error:', error);
    res.status(500).json({
      success: false,
      message: 'Logout failed'
    });
  }
};

module.exports = {
  setupPIN,
  loginWithPIN,
  enableBiometric,
  loginWithBiometric,
  autoLogin,
  getSecuritySettings,
  updateSecuritySettings,
  logout
};

Directory Contents

Dirs: 1 × Files: 17

Name Size Perms Modified Actions
- drwxr-xr-x 2026-03-20 23:25:48
Edit Download
127 B lr--r--r-- 2026-03-14 01:49:22
Edit Download
15.34 KB lrw-r--r-- 2026-02-19 22:06:47
Edit Download
30.77 KB lrw-r--r-- 2026-03-04 06:33:18
Edit Download
62.98 KB lrw-r--r-- 2026-03-01 23:46:52
Edit Download
10.79 KB lrw-r--r-- 2026-02-07 16:28:16
Edit Download
14.17 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
6.32 KB lrw-r--r-- 2026-02-06 17:10:19
Edit Download
15.13 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.50 KB lrw-r--r-- 2026-03-01 05:05:52
Edit Download
12.15 KB lrw-r--r-- 2026-02-27 04:02:38
Edit Download
25.39 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
17.61 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
6.45 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
2.33 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
12.78 KB lrw-r--r-- 2026-02-21 08:13:30
Edit Download
9.96 KB lrw-r--r-- 2026-03-01 05:05:51
Edit Download
8.51 KB lrw-r--r-- 2026-02-20 19:31:28
Edit Download

If ZipArchive is unavailable, a .tar will be created (no compression).