Software: Apache. PHP/8.1.30 uname -a: Linux server1.tuhinhossain.com 5.15.0-163-generic #173-Ubuntu SMP Tue Oct 14 17:51:00 UTC uid=1002(picotech) gid=1003(picotech) groups=1003(picotech),0(root) Safe-mode: OFF (not secure) /home/picotech/domains/note.picotech.app/public_html/src/controllers/ drwxr-xr-x | |
| Viewing file: Select action/file-type: const { User, Otp, FailedLoginAttempt } = require('../models');
const { generateToken, generateRefreshToken, verifyRefreshToken } = require('../middleware/auth');
const { AppError, catchAsync } = require('../middleware/errorHandler');
const config = require('../config/config');
const crypto = require('crypto');
// Account lockout configuration
const MAX_LOGIN_ATTEMPTS = 5;
const LOCKOUT_DURATION = 15 * 60 * 1000; // 15 minutes in milliseconds
// Send OTP for registration
const sendRegistrationOTP = catchAsync(async (req, res) => {
const { email, name } = req.body;
// Check if registration is enabled
if (!config.auth.registrationEnabled) {
throw new AppError('Registration is currently disabled', 403, 'REGISTRATION_DISABLED');
}
// Check if user already exists and is active
const existingUser = await User.findByEmail(email);
if (existingUser && existingUser.is_active) {
throw new AppError('User with this email already exists', 409, 'USER_EXISTS');
}
// Always remove any existing unused OTPs for this email/type before creating new one
await Otp.destroy({
where: {
email,
otp_type: 'registration'
},
});
// Generate and save new OTP
const otpRecord = await Otp.generateOTP(email, 'registration');
// TODO: Implement proper email service for OTP delivery
// For development, OTP is only logged (never exposed in responses)
if (process.env.NODE_ENV === 'development') {
console.log(`OTP for ${email}: ${otpRecord.otp_code}`);
}
res.json({
success: true,
message: 'OTP sent to your email'
});
});
// Verify OTP and complete registration
const verifyOTP = catchAsync(async (req, res) => {
const { email, otp } = req.body;
// Find valid OTP
const otpRecord = await Otp.findValidOTP(email, otp, 'registration');
if (!otpRecord) {
throw new AppError('Invalid or expired OTP', 400, 'INVALID_OTP');
}
// Check if OTP is expired
if (otpRecord.isExpired()) {
throw new AppError('OTP has expired', 400, 'OTP_EXPIRED');
}
// Check attempts
if (!otpRecord.canAttempt()) {
throw new AppError('Too many failed attempts', 400, 'MAX_ATTEMPTS_EXCEEDED');
}
// Mark OTP as used
otpRecord.is_used = true;
await otpRecord.save();
res.json({
success: true,
message: 'OTP verified successfully',
});
});
// Complete registration with password
const register = catchAsync(async (req, res) => {
const { email, password, name } = req.body;
// Check if registration is enabled
if (!config.auth.registrationEnabled) {
throw new AppError('Registration is currently disabled', 403, 'REGISTRATION_DISABLED');
}
// Check if user already exists and is active
const existingUser = await User.findByEmail(email);
if (existingUser && existingUser.is_active) {
throw new AppError('User with this email already exists', 409, 'USER_EXISTS');
}
// Verify that OTP was verified for this email
const verifiedOtp = await Otp.findOne({
where: {
email,
otp_type: 'registration',
is_used: true,
},
order: [['updated_at', 'DESC']],
});
if (!verifiedOtp) {
throw new AppError('Please verify your email first', 400, 'EMAIL_NOT_VERIFIED');
}
// Create the user
const user = await User.create({
email,
name,
password_hash: password, // Will be hashed by model hook
is_active: true,
email_verified: true, // OTP verification serves as email verification
verification_token: crypto.randomBytes(32).toString('hex'),
});
// Clean up used OTPs for this email
await Otp.destroy({
where: {
email,
otp_type: 'registration',
},
});
// Generate tokens
const token = generateToken(user.id);
const refreshToken = generateRefreshToken(user.id);
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
user: user.toJSON(),
token,
refreshToken,
},
});
});
// Check if account is locked
const isAccountLocked = async (email) => {
return await FailedLoginAttempt.isAccountLocked(email);
};
// Record failed login attempt
const recordFailedLogin = async (email, req) => {
const ipAddress = req?.ip || req?.connection?.remoteAddress || null;
const userAgent = req?.get('User-Agent') || null;
await FailedLoginAttempt.recordFailedAttempt(email, ipAddress, userAgent);
};
// Clear failed login attempts on successful login
const clearFailedLoginAttempts = async (email) => {
await FailedLoginAttempt.clearFailedAttempts(email);
};
// Login user
const login = catchAsync(async (req, res) => {
const { email, password } = req.body;
// Check if account is locked
if (await isAccountLocked(email)) {
throw new AppError('Account is temporarily locked due to too many failed login attempts. Please try again later.', 429, 'ACCOUNT_LOCKED');
}
// Find user by email
const user = await User.findOne({
where: { email },
attributes: ['id', 'email', 'name', 'password_hash', 'is_active', 'email_verified'],
});
if (!user || !user.is_active) {
await recordFailedLogin(email, req);
throw new AppError('Invalid email or password', 401, 'INVALID_CREDENTIALS');
}
// Check password
const isPasswordValid = await user.validatePassword(password);
if (!isPasswordValid) {
await recordFailedLogin(email, req);
throw new AppError('Invalid email or password', 401, 'INVALID_CREDENTIALS');
}
// Clear failed attempts on successful login
await clearFailedLoginAttempts(email);
// Generate tokens
const token = generateToken(user.id);
const refreshToken = generateRefreshToken(user.id);
// Update last login
user.last_login = new Date();
await user.save();
res.json({
success: true,
message: 'Login successful',
data: {
user: user.toJSON(),
token,
refreshToken,
},
});
});
// Refresh access token
const refreshToken = catchAsync(async (req, res) => {
const { refreshToken: token } = req.body;
if (!token) {
throw new AppError('Refresh token is required', 400, 'MISSING_REFRESH_TOKEN');
}
// Verify refresh token
const decoded = verifyRefreshToken(token);
// Find user
const user = await User.findActiveById(decoded.userId);
if (!user) {
throw new AppError('User not found or inactive', 401, 'USER_NOT_FOUND');
}
// Generate new tokens
const newToken = generateToken(user.id);
const newRefreshToken = generateRefreshToken(user.id);
res.json({
success: true,
message: 'Token refreshed successfully',
data: {
token: newToken,
refreshToken: newRefreshToken,
},
});
});
// Get current user profile
const getProfile = catchAsync(async (req, res) => {
const user = await User.findByPk(req.user.id, {
attributes: { exclude: ['password_hash'] },
});
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
res.json({
success: true,
data: { user },
});
});
// Update user profile
const updateProfile = catchAsync(async (req, res) => {
const { name, avatar_url } = req.body;
const userId = req.user.id;
const user = await User.findByPk(userId);
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
// Update user fields
if (name !== undefined) user.name = name;
if (avatar_url !== undefined) user.avatar_url = avatar_url;
await user.save();
res.json({
success: true,
message: 'Profile updated successfully',
data: { user: user.toJSON() },
});
});
// Change email
const changeEmail = catchAsync(async (req, res) => {
const { email } = req.body;
const userId = req.user.id;
const user = await User.findByPk(userId);
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
// Check if new email is already taken
const existingUser = await User.findByEmail(email);
if (existingUser && existingUser.id !== userId) {
throw new AppError('Email is already in use', 409, 'EMAIL_EXISTS');
}
// Update email and reset verification
user.email = email;
user.email_verified = false;
user.verification_token = crypto.randomBytes(32).toString('hex');
await user.save();
res.json({
success: true,
message: 'Email changed successfully. Please verify your new email.',
data: { user: user.toJSON() },
});
});
// Change password
const changePassword = catchAsync(async (req, res) => {
const { currentPassword, newPassword } = req.body;
const userId = req.user.id;
const user = await User.findOne({
where: { id: userId },
attributes: ['id', 'password_hash'],
});
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
// Verify current password
const isCurrentPasswordValid = await user.validatePassword(currentPassword);
if (!isCurrentPasswordValid) {
throw new AppError('Current password is incorrect', 400, 'INVALID_CURRENT_PASSWORD');
}
// Update password
user.password_hash = newPassword; // Will be hashed by model hook
await user.save();
res.json({
success: true,
message: 'Password changed successfully',
});
});
// Forgot password
const forgotPassword = catchAsync(async (req, res) => {
const { email } = req.body;
const user = await User.findByEmail(email);
if (!user) {
// Don't reveal if user exists or not
return res.json({
success: true,
message: 'If an account with that email exists, a password reset link has been sent',
});
}
// Generate reset token
const resetToken = crypto.randomBytes(32).toString('hex');
const resetTokenExpiry = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
user.reset_password_token = resetToken;
user.reset_password_expires = resetTokenExpiry;
await user.save();
// TODO: Implement proper email service for password reset
// For development, token is only logged (never exposed in responses)
if (process.env.NODE_ENV === 'development') {
console.log(`Password reset token for ${email}: ${resetToken}`);
}
res.json({
success: true,
message: 'Password reset link has been sent to your email'
});
});
// Reset password
const resetPassword = catchAsync(async (req, res) => {
const { token, password } = req.body;
const user = await User.findOne({
where: {
reset_password_token: token,
reset_password_expires: {
[require('sequelize').Op.gt]: new Date(),
},
},
});
if (!user) {
throw new AppError('Invalid or expired reset token', 400, 'INVALID_RESET_TOKEN');
}
// Update password and clear reset token
user.password_hash = password; // Will be hashed by model hook
user.reset_password_token = null;
user.reset_password_expires = null;
await user.save();
res.json({
success: true,
message: 'Password reset successfully',
});
});
// Verify email
const verifyEmail = catchAsync(async (req, res) => {
const { token } = req.params;
const user = await User.findOne({
where: { verification_token: token },
});
if (!user) {
throw new AppError('Invalid verification token', 400, 'INVALID_VERIFICATION_TOKEN');
}
user.email_verified = true;
user.verification_token = null;
await user.save();
res.json({
success: true,
message: 'Email verified successfully',
});
});
// Resend verification email
const resendVerification = catchAsync(async (req, res) => {
const userId = req.user.id;
const user = await User.findByPk(userId);
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
if (user.email_verified) {
throw new AppError('Email is already verified', 400, 'EMAIL_ALREADY_VERIFIED');
}
// Generate new verification token
user.verification_token = crypto.randomBytes(32).toString('hex');
await user.save();
// TODO: Send verification email
res.json({
success: true,
message: 'Verification email sent',
});
});
// Logout (client-side token invalidation)
const logout = catchAsync(async (req, res) => {
// In a stateless JWT system, logout is typically handled client-side
// by removing the token from storage. However, we can log this event.
res.json({
success: true,
message: 'Logged out successfully',
});
});
// Delete account
const deleteAccount = catchAsync(async (req, res) => {
const { password } = req.body;
const userId = req.user.id;
const user = await User.findOne({
where: { id: userId },
attributes: ['id', 'password_hash'],
});
if (!user) {
throw new AppError('User not found', 404, 'USER_NOT_FOUND');
}
// Verify password before deletion
const isPasswordValid = await user.validatePassword(password);
if (!isPasswordValid) {
throw new AppError('Password is incorrect', 400, 'INVALID_PASSWORD');
}
// Soft delete by deactivating account
user.is_active = false;
await user.save();
res.json({
success: true,
message: 'Account deleted successfully',
});
});
// Get registration config
const getRegistrationConfig = catchAsync(async (req, res) => {
res.json({
success: true,
data: {
registrationEnabled: config.auth.registrationEnabled,
},
});
});
module.exports = {
sendRegistrationOTP,
verifyOTP,
register,
login,
refreshToken,
getProfile,
updateProfile,
changeEmail,
changePassword,
forgotPassword,
resetPassword,
verifyEmail,
resendVerification,
logout,
deleteAccount,
getRegistrationConfig,
}; |
:: Command execute :: | |
--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0037 ]-- |