Files
TIS_RMS/backend/src/controllers/authController.js
T

212 lines
8.7 KiB
JavaScript

const db = require('../config/db');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const { createNotification } = require('./notificationController');
// POST /api/auth/login
exports.login = (req, res) => {
const { username, password } = req.body;
try {
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ message: 'Invalid username or password' });
}
const token = jwt.sign(
{ id: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '75d' } // Extended for Remember Me support
);
res.json({
token,
user: {
id: user.id,
username: user.username,
role: user.role,
firstName: user.first_name,
lastName: user.last_name
}
});
} catch (error) {
res.status(500).json({ message: 'Login failed', error: error.message });
}
};
// GET /api/auth/profile
exports.getProfile = (req, res) => {
try {
const user = db.prepare('SELECT id, username, first_name, middle_name, last_name, extension, role, email, phone FROM users WHERE id = ?').get(req.user.id);
res.json(user);
} catch (error) {
res.status(500).json({ message: 'Failed to fetch profile', error: error.message });
}
};
// PUT /api/auth/profile
exports.updateProfile = (req, res) => {
const { firstName, middleName, lastName, extension, phone, email } = req.body;
try {
db.prepare(`
UPDATE users
SET first_name = ?, middle_name = ?, last_name = ?, extension = ?, phone = ?, email = ?, updated_at = (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
WHERE id = ?
`).run(firstName, middleName, lastName, extension, phone, email, req.user.id);
res.json({ message: 'Profile updated successfully' });
} catch (error) {
res.status(500).json({ message: 'Failed to update profile', error: error.message });
}
};
// PUT /api/auth/change-password — requires currentPassword + newPassword
exports.changePassword = (req, res) => {
const { currentPassword, newPassword, confirmPassword } = req.body;
if (!currentPassword || !newPassword || !confirmPassword) {
return res.status(400).json({ message: 'All password fields are required.' });
}
if (newPassword !== confirmPassword) {
return res.status(400).json({ message: 'New passwords do not match.' });
}
if (newPassword.length < 6) {
return res.status(400).json({ message: 'New password must be at least 6 characters.' });
}
try {
const user = db.prepare('SELECT password FROM users WHERE id = ?').get(req.user.id);
if (!bcrypt.compareSync(currentPassword, user.password)) {
return res.status(400).json({ message: 'Current password is incorrect.' });
}
if (newPassword === currentPassword) {
return res.status(400).json({ message: 'New password cannot be the same as the current password.' });
}
const hashedNewPassword = bcrypt.hashSync(newPassword, 10);
db.prepare("UPDATE users SET password = ?, updated_at = (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) WHERE id = ?")
.run(hashedNewPassword, req.user.id);
res.json({ message: 'Password changed successfully.' });
} catch (error) {
res.status(500).json({ message: 'Failed to change password', error: error.message });
}
};
// POST /api/auth/forgot-password — admin/teacher submits a reset request
exports.requestPasswordReset = (req, res) => {
const { username, newPassword, confirmPassword } = req.body;
if (!username || !newPassword || !confirmPassword) {
return res.status(400).json({ message: 'All fields are required.' });
}
if (newPassword !== confirmPassword) {
return res.status(400).json({ message: 'Passwords do not match.' });
}
if (newPassword.length < 6) {
return res.status(400).json({ message: 'Password must be at least 6 characters.' });
}
try {
const user = db.prepare('SELECT id, role, password FROM users WHERE username = ?').get(username);
if (!user) {
return res.status(404).json({ message: 'Username not found.' });
}
if (user.role === 'admin') {
return res.status(403).json({ message: 'Admin cannot submit password reset requests.' });
}
if (bcrypt.compareSync(newPassword, user.password)) {
return res.status(400).json({ message: 'New password cannot be the same as the current password.' });
}
// Cancel any existing pending request for this user
db.prepare("DELETE FROM password_reset_requests WHERE user_id = ? AND status = 'pending'").run(user.id);
const hashedPassword = bcrypt.hashSync(newPassword, 10);
db.prepare(`
INSERT INTO password_reset_requests (user_id, new_password_hash)
VALUES (?, ?)
`).run(user.id, hashedPassword);
createNotification(null, 'Password Reset Request', `User "${username}" has requested a password reset.`, 'user');
res.json({ message: 'Password reset request submitted. Awaiting Admin approval.' });
} catch (error) {
res.status(500).json({ message: 'Failed to submit request', error: error.message });
}
};
// GET /api/auth/reset-requests — super admin views pending requests
exports.getResetRequests = (req, res) => {
try {
const requests = db.prepare(`
SELECT r.id, r.status, r.requested_at,
u.id as user_id, u.username, u.first_name, u.last_name, u.role
FROM password_reset_requests r
JOIN users u ON r.user_id = u.id
WHERE r.status = 'pending'
ORDER BY r.requested_at ASC
`).all();
res.json(requests);
} catch (error) {
res.status(500).json({ message: 'Failed to fetch requests', error: error.message });
}
};
// PUT /api/auth/reset-requests/:id/approve — super admin approves
exports.approveResetRequest = (req, res) => {
const { id } = req.params;
try {
const request = db.prepare("SELECT * FROM password_reset_requests WHERE id = ? AND status = 'pending'").get(id);
if (!request) return res.status(404).json({ message: 'Request not found or already reviewed.' });
db.prepare("UPDATE users SET password = ?, updated_at = (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) WHERE id = ?")
.run(request.new_password_hash, request.user_id);
db.prepare(`UPDATE password_reset_requests SET status = 'approved', reviewed_at = (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), reviewed_by = ? WHERE id = ?`)
.run(req.user.id, id);
createNotification(request.user_id, 'Password Reset Approved', 'Your password reset request has been approved.', 'system');
res.json({ message: 'Password reset approved and applied.' });
} catch (error) {
res.status(500).json({ message: 'Failed to approve request', error: error.message });
}
};
// PUT /api/auth/reset-requests/:id/reject — super admin rejects
exports.rejectResetRequest = (req, res) => {
const { id } = req.params;
try {
const request = db.prepare("SELECT * FROM password_reset_requests WHERE id = ? AND status = 'pending'").get(id);
if (!request) return res.status(404).json({ message: 'Request not found or already reviewed.' });
db.prepare(`UPDATE password_reset_requests SET status = 'rejected', reviewed_at = (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), reviewed_by = ? WHERE id = ?`)
.run(req.user.id, id);
createNotification(request.user_id, 'Password Reset Rejected', 'Your password reset request was rejected by the administrator.', 'system');
res.json({ message: 'Password reset request rejected.' });
} catch (error) {
res.status(500).json({ message: 'Failed to reject request', error: error.message });
}
};
// POST /api/auth/verify-password
exports.verifyPassword = (req, res) => {
const { password } = req.body;
if (!password) {
return res.status(400).json({ message: 'Password is required.' });
}
try {
const user = db.prepare('SELECT password FROM users WHERE id = ?').get(req.user.id);
if (!user || !bcrypt.compareSync(password, user.password)) {
return res.status(401).json({ message: 'Incorrect password.' });
}
res.json({ success: true });
} catch (error) {
res.status(500).json({ message: 'Verification failed', error: error.message });
}
};