212 lines
8.7 KiB
JavaScript
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 });
|
|
}
|
|
};
|