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