131 lines
4.5 KiB
JavaScript
131 lines
4.5 KiB
JavaScript
const express = require('express');
|
|
const bcrypt = require('bcryptjs');
|
|
const { getDb, getOrCreateSupportGroup } = require('../models/db');
|
|
const { generateToken, authMiddleware, setActiveSession, clearActiveSession } = require('../middleware/auth');
|
|
|
|
module.exports = function(io) {
|
|
const router = express.Router();
|
|
|
|
// Login
|
|
router.post('/login', (req, res) => {
|
|
const { email, password, rememberMe } = req.body;
|
|
const db = getDb();
|
|
|
|
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
|
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
|
|
|
if (user.status === 'suspended') {
|
|
const adminUser = db.prepare('SELECT email FROM users WHERE is_default_admin = 1').get();
|
|
return res.status(403).json({
|
|
error: 'suspended',
|
|
adminEmail: adminUser?.email
|
|
});
|
|
}
|
|
if (user.status === 'deleted') return res.status(403).json({ error: 'Account not found' });
|
|
|
|
const valid = bcrypt.compareSync(password, user.password);
|
|
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
|
|
|
|
const token = generateToken(user.id);
|
|
const ua = req.headers['user-agent'] || '';
|
|
const device = setActiveSession(user.id, token, ua); // displaces prior session on same device class
|
|
// Kick any live socket on the same device class — it now holds a stale token
|
|
if (io) {
|
|
io.to(`user:${user.id}`).emit('session:displaced', { device });
|
|
}
|
|
|
|
const { password: _, ...userSafe } = user;
|
|
res.json({
|
|
token,
|
|
user: userSafe,
|
|
mustChangePassword: !!user.must_change_password,
|
|
rememberMe: !!rememberMe
|
|
});
|
|
});
|
|
|
|
// Change password
|
|
router.post('/change-password', authMiddleware, (req, res) => {
|
|
const { currentPassword, newPassword } = req.body;
|
|
const db = getDb();
|
|
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id);
|
|
|
|
if (!bcrypt.compareSync(currentPassword, user.password)) {
|
|
return res.status(400).json({ error: 'Current password is incorrect' });
|
|
}
|
|
if (newPassword.length < 8) return res.status(400).json({ error: 'Password must be at least 8 characters' });
|
|
|
|
const hash = bcrypt.hashSync(newPassword, 10);
|
|
db.prepare("UPDATE users SET password = ?, must_change_password = 0, updated_at = datetime('now') WHERE id = ?").run(hash, req.user.id);
|
|
|
|
res.json({ success: true });
|
|
});
|
|
|
|
// Get current user
|
|
router.get('/me', authMiddleware, (req, res) => {
|
|
const { password, ...user } = req.user;
|
|
res.json({ user });
|
|
});
|
|
|
|
// Logout — clear active session for this device class only
|
|
router.post('/logout', authMiddleware, (req, res) => {
|
|
clearActiveSession(req.user.id, req.device);
|
|
res.json({ success: true });
|
|
});
|
|
|
|
// Public support contact form — no auth required
|
|
router.post('/support', (req, res) => {
|
|
const { name, email, message } = req.body;
|
|
if (!name?.trim() || !email?.trim() || !message?.trim()) {
|
|
return res.status(400).json({ error: 'All fields are required' });
|
|
}
|
|
if (message.trim().length > 2000) {
|
|
return res.status(400).json({ error: 'Message too long (max 2000 characters)' });
|
|
}
|
|
|
|
const db = getDb();
|
|
|
|
// Get or create the Support group
|
|
const groupId = getOrCreateSupportGroup();
|
|
if (!groupId) return res.status(500).json({ error: 'Support group unavailable' });
|
|
|
|
// Find a system/admin user to post as (default admin)
|
|
const admin = db.prepare('SELECT id FROM users WHERE is_default_admin = 1').get();
|
|
if (!admin) return res.status(500).json({ error: 'No admin configured' });
|
|
|
|
// Format the support message
|
|
const content = `📬 **Support Request**
|
|
**Name:** ${name.trim()}
|
|
**Email:** ${email.trim()}
|
|
|
|
${message.trim()}`;
|
|
|
|
const msgResult = db.prepare(`
|
|
INSERT INTO messages (group_id, user_id, content, type)
|
|
VALUES (?, ?, ?, 'text')
|
|
`).run(groupId, admin.id, content);
|
|
|
|
// Emit socket event so online admins see the message immediately
|
|
const newMsg = db.prepare(`
|
|
SELECT m.*, u.name as user_name, u.display_name as user_display_name, u.avatar as user_avatar
|
|
FROM messages m JOIN users u ON m.user_id = u.id
|
|
WHERE m.id = ?
|
|
`).get(msgResult.lastInsertRowid);
|
|
|
|
if (newMsg) {
|
|
newMsg.reactions = [];
|
|
io.to(`group:${groupId}`).emit('message:new', newMsg);
|
|
}
|
|
|
|
// Notify each admin via their user channel so they can reload groups if needed
|
|
const admins = db.prepare("SELECT id FROM users WHERE role = 'admin' AND status = 'active'").all();
|
|
for (const a of admins) {
|
|
io.to(`user:${a.id}`).emit('notification:new', { type: 'support', groupId });
|
|
}
|
|
|
|
console.log(`[Support] Message from ${email} posted to Support group`);
|
|
res.json({ success: true });
|
|
});
|
|
|
|
return router;
|
|
};
|