v0.9.88 major change sqlite to postgres
This commit is contained in:
@@ -1,10 +1,8 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { getDb } = require('../models/db');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { query, queryOne, exec } = require('../models/db');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'changeme_super_secret';
|
||||
|
||||
// Classify a User-Agent string into 'mobile' or 'desktop'.
|
||||
// Tablets are treated as mobile (one shared slot).
|
||||
function getDeviceClass(ua) {
|
||||
if (!ua) return 'desktop';
|
||||
const s = ua.toLowerCase();
|
||||
@@ -13,24 +11,21 @@ function getDeviceClass(ua) {
|
||||
return 'desktop';
|
||||
}
|
||||
|
||||
function authMiddleware(req, res, next) {
|
||||
async function authMiddleware(req, res, next) {
|
||||
const token = req.headers.authorization?.split(' ')[1] || req.cookies?.token;
|
||||
if (!token) return res.status(401).json({ error: 'Unauthorized' });
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
const db = getDb();
|
||||
const user = db.prepare('SELECT * FROM users WHERE id = ? AND status = ?').get(decoded.id, 'active');
|
||||
const user = await queryOne(req.schema,
|
||||
"SELECT * FROM users WHERE id = $1 AND status = 'active'", [decoded.id]
|
||||
);
|
||||
if (!user) return res.status(401).json({ error: 'User not found or suspended' });
|
||||
|
||||
// Per-device enforcement: token must match an active session row
|
||||
const session = db.prepare('SELECT * FROM active_sessions WHERE user_id = ? AND token = ?').get(decoded.id, token);
|
||||
if (!session) {
|
||||
return res.status(401).json({ error: 'Session expired. Please log in again.' });
|
||||
}
|
||||
|
||||
req.user = user;
|
||||
req.token = token;
|
||||
const session = await queryOne(req.schema,
|
||||
'SELECT * FROM active_sessions WHERE user_id = $1 AND token = $2', [decoded.id, token]
|
||||
);
|
||||
if (!session) return res.status(401).json({ error: 'Session expired. Please log in again.' });
|
||||
req.user = user;
|
||||
req.token = token;
|
||||
req.device = session.device;
|
||||
next();
|
||||
} catch (e) {
|
||||
@@ -43,52 +38,57 @@ function adminMiddleware(req, res, next) {
|
||||
next();
|
||||
}
|
||||
|
||||
// Allows admins OR members of groups designated as Tool Managers
|
||||
function teamManagerMiddleware(req, res, next) {
|
||||
async function teamManagerMiddleware(req, res, next) {
|
||||
if (req.user?.role === 'admin') return next();
|
||||
const db = getDb();
|
||||
// Prefer unified key, fall back to legacy keys for older installs
|
||||
const tmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_tool_managers'").get();
|
||||
const gmSetting = db.prepare("SELECT value FROM settings WHERE key = 'team_group_managers'").get();
|
||||
const allowedGroupIds = [
|
||||
...new Set([
|
||||
...JSON.parse(tmSetting?.value || '[]'),
|
||||
...JSON.parse(gmSetting?.value || '[]'),
|
||||
])
|
||||
];
|
||||
if (allowedGroupIds.length === 0) return res.status(403).json({ error: 'Access denied' });
|
||||
const member = db.prepare(`
|
||||
SELECT 1 FROM user_group_members WHERE user_id = ? AND user_group_id IN (${allowedGroupIds.map(() => '?').join(',')})
|
||||
`).get(req.user.id, ...allowedGroupIds);
|
||||
if (!member) return res.status(403).json({ error: 'Access denied' });
|
||||
next();
|
||||
try {
|
||||
const tmSetting = await queryOne(req.schema,
|
||||
"SELECT value FROM settings WHERE key = 'team_tool_managers'"
|
||||
);
|
||||
const gmSetting = await queryOne(req.schema,
|
||||
"SELECT value FROM settings WHERE key = 'team_group_managers'"
|
||||
);
|
||||
const allowedGroupIds = [
|
||||
...new Set([
|
||||
...JSON.parse(tmSetting?.value || '[]'),
|
||||
...JSON.parse(gmSetting?.value || '[]'),
|
||||
])
|
||||
];
|
||||
if (allowedGroupIds.length === 0) return res.status(403).json({ error: 'Access denied' });
|
||||
const placeholders = allowedGroupIds.map((_, i) => `$${i + 2}`).join(',');
|
||||
const member = await queryOne(req.schema,
|
||||
`SELECT 1 FROM user_group_members WHERE user_id = $1 AND user_group_id IN (${placeholders})`,
|
||||
[req.user.id, ...allowedGroupIds]
|
||||
);
|
||||
if (!member) return res.status(403).json({ error: 'Access denied' });
|
||||
next();
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
function generateToken(userId) {
|
||||
return jwt.sign({ id: userId }, JWT_SECRET, { expiresIn: '30d' });
|
||||
}
|
||||
|
||||
// Upsert the active session for this user+device class.
|
||||
// Displaces any prior session on the same device class; the other device class is unaffected.
|
||||
function setActiveSession(userId, token, userAgent) {
|
||||
const db = getDb();
|
||||
async function setActiveSession(schema, userId, token, userAgent) {
|
||||
const device = getDeviceClass(userAgent);
|
||||
db.prepare(`
|
||||
await exec(schema, `
|
||||
INSERT INTO active_sessions (user_id, device, token, ua, created_at)
|
||||
VALUES (?, ?, ?, ?, datetime('now'))
|
||||
ON CONFLICT(user_id, device) DO UPDATE SET token = ?, ua = ?, created_at = datetime('now')
|
||||
`).run(userId, device, token, userAgent || null, token, userAgent || null);
|
||||
VALUES ($1, $2, $3, $4, NOW())
|
||||
ON CONFLICT (user_id, device) DO UPDATE SET token = $3, ua = $4, created_at = NOW()
|
||||
`, [userId, device, token, userAgent || null]);
|
||||
return device;
|
||||
}
|
||||
|
||||
// Clear one device slot on logout, or all slots (no device arg) for suspend/delete
|
||||
function clearActiveSession(userId, device) {
|
||||
const db = getDb();
|
||||
async function clearActiveSession(schema, userId, device) {
|
||||
if (device) {
|
||||
db.prepare('DELETE FROM active_sessions WHERE user_id = ? AND device = ?').run(userId, device);
|
||||
await exec(schema, 'DELETE FROM active_sessions WHERE user_id = $1 AND device = $2', [userId, device]);
|
||||
} else {
|
||||
db.prepare('DELETE FROM active_sessions WHERE user_id = ?').run(userId);
|
||||
await exec(schema, 'DELETE FROM active_sessions WHERE user_id = $1', [userId]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { authMiddleware, adminMiddleware, teamManagerMiddleware, generateToken, setActiveSession, clearActiveSession, getDeviceClass };
|
||||
module.exports = {
|
||||
authMiddleware, adminMiddleware, teamManagerMiddleware,
|
||||
generateToken, setActiveSession, clearActiveSession, getDeviceClass,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user