feat(postgres): migrate all route files from SQLite to pg pool
- All 16 route files now import pool from ../db directly
- Removed db parameter from all factory functions
- All callbacks replaced with async/await pool.query()
- All ? placeholders converted to $1, $2... numbered params
- datetime('now') → NOW(), INSERT OR IGNORE → ON CONFLICT DO NOTHING
- LIKE → ILIKE for case-insensitive searches
- Error detection: err.code === '23505' for unique violations
- server.js no longer passes pool/db/requireAuth to route factories
- Only ivantiFindings.js still receives pool (pending task 8 rewrite)
This commit is contained in:
@@ -3,6 +3,7 @@ const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const crypto = require('crypto');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const pool = require('../db');
|
||||
const { requireAuth, requireGroup } = require('../middleware/auth');
|
||||
|
||||
const loginLimiter = rateLimit({
|
||||
@@ -13,7 +14,7 @@ const loginLimiter = rateLimit({
|
||||
message: { error: 'Too many login attempts. Please try again in 15 minutes.' }
|
||||
});
|
||||
|
||||
function createAuthRouter(db, logAudit) {
|
||||
function createAuthRouter(logAudit) {
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
@@ -39,19 +40,14 @@ function createAuthRouter(db, logAudit) {
|
||||
|
||||
try {
|
||||
// Find user
|
||||
const user = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT * FROM users WHERE username = ?',
|
||||
[username],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
'SELECT * FROM users WHERE username = $1',
|
||||
[username]
|
||||
);
|
||||
const user = rows[0];
|
||||
|
||||
if (!user) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: null,
|
||||
username: username,
|
||||
action: 'login_failed',
|
||||
@@ -64,7 +60,7 @@ function createAuthRouter(db, logAudit) {
|
||||
}
|
||||
|
||||
if (!user.is_active) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: user.id,
|
||||
username: username,
|
||||
action: 'login_failed',
|
||||
@@ -79,7 +75,7 @@ function createAuthRouter(db, logAudit) {
|
||||
// Verify password
|
||||
const validPassword = await bcrypt.compare(password, user.password_hash);
|
||||
if (!validPassword) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: user.id,
|
||||
username: username,
|
||||
action: 'login_failed',
|
||||
@@ -96,28 +92,16 @@ function createAuthRouter(db, logAudit) {
|
||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
||||
|
||||
// Create session
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'INSERT INTO sessions (session_id, user_id, expires_at) VALUES (?, ?, ?)',
|
||||
[sessionId, user.id, expiresAt.toISOString()],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
await pool.query(
|
||||
'INSERT INTO sessions (session_id, user_id, expires_at) VALUES ($1, $2, $3)',
|
||||
[sessionId, user.id, expiresAt.toISOString()]
|
||||
);
|
||||
|
||||
// Update last login
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[user.id],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
await pool.query(
|
||||
'UPDATE users SET last_login = NOW() WHERE id = $1',
|
||||
[user.id]
|
||||
);
|
||||
|
||||
// Set cookie
|
||||
res.cookie('session_id', sessionId, {
|
||||
@@ -127,7 +111,7 @@ function createAuthRouter(db, logAudit) {
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
});
|
||||
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
action: 'login',
|
||||
@@ -166,27 +150,31 @@ function createAuthRouter(db, logAudit) {
|
||||
|
||||
if (sessionId) {
|
||||
// Look up user before deleting session
|
||||
const session = await new Promise((resolve) => {
|
||||
db.get(
|
||||
let session = null;
|
||||
try {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT u.id as user_id, u.username FROM sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_id = ?`,
|
||||
[sessionId],
|
||||
(err, row) => resolve(row || null)
|
||||
WHERE s.session_id = $1`,
|
||||
[sessionId]
|
||||
);
|
||||
});
|
||||
session = rows[0] || null;
|
||||
} catch (err) {
|
||||
// Non-critical — proceed with logout
|
||||
}
|
||||
|
||||
// Delete session from database
|
||||
await new Promise((resolve) => {
|
||||
db.run(
|
||||
'DELETE FROM sessions WHERE session_id = ?',
|
||||
[sessionId],
|
||||
() => resolve()
|
||||
try {
|
||||
await pool.query(
|
||||
'DELETE FROM sessions WHERE session_id = $1',
|
||||
[sessionId]
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
// Non-critical — proceed with logout
|
||||
}
|
||||
|
||||
if (session) {
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: session.user_id,
|
||||
username: session.username,
|
||||
action: 'logout',
|
||||
@@ -221,19 +209,15 @@ function createAuthRouter(db, logAudit) {
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
`SELECT s.*, u.id as user_id, u.username, u.email, u.user_group, u.bu_teams, u.is_active
|
||||
FROM sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_id = ? AND s.expires_at > datetime('now')`,
|
||||
[sessionId],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
`SELECT s.*, u.id as user_id, u.username, u.email, u.user_group, u.bu_teams, u.is_active
|
||||
FROM sessions s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.session_id = $1 AND s.expires_at > NOW()`,
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
const session = rows[0];
|
||||
|
||||
if (!session) {
|
||||
res.clearCookie('session_id');
|
||||
@@ -271,18 +255,14 @@ function createAuthRouter(db, logAudit) {
|
||||
* @returns {object} 401 - { error: 'Account is disabled' } (clears session cookie)
|
||||
* @returns {object} 500 - { error: 'Failed to fetch profile' }
|
||||
*/
|
||||
router.get('/profile', requireAuth(db), async (req, res) => {
|
||||
router.get('/profile', requireAuth(), async (req, res) => {
|
||||
try {
|
||||
const user = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT id, username, email, user_group, created_at, last_login, is_active FROM users WHERE id = ?',
|
||||
[req.user.id],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
'SELECT id, username, email, user_group, created_at, last_login, is_active FROM users WHERE id = $1',
|
||||
[req.user.id]
|
||||
);
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
if (!user || !user.is_active) {
|
||||
res.clearCookie('session_id');
|
||||
@@ -327,7 +307,7 @@ function createAuthRouter(db, logAudit) {
|
||||
* @returns {object} 429 - { error: 'Too many password change attempts. Please try again later.' }
|
||||
* @returns {object} 500 - { error: 'Failed to change password' }
|
||||
*/
|
||||
router.post('/change-password', requireAuth(db), passwordChangeLimiter, async (req, res) => {
|
||||
router.post('/change-password', requireAuth(), passwordChangeLimiter, async (req, res) => {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
@@ -340,16 +320,12 @@ function createAuthRouter(db, logAudit) {
|
||||
|
||||
try {
|
||||
// Fetch user's password hash and active status
|
||||
const user = await new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT password_hash, is_active FROM users WHERE id = ?',
|
||||
[req.user.id],
|
||||
(err, row) => {
|
||||
if (err) reject(err);
|
||||
else resolve(row);
|
||||
}
|
||||
);
|
||||
});
|
||||
const { rows } = await pool.query(
|
||||
'SELECT password_hash, is_active FROM users WHERE id = $1',
|
||||
[req.user.id]
|
||||
);
|
||||
|
||||
const user = rows[0];
|
||||
|
||||
if (!user || !user.is_active) {
|
||||
return res.status(401).json({ error: 'Account is disabled' });
|
||||
@@ -363,18 +339,12 @@ function createAuthRouter(db, logAudit) {
|
||||
|
||||
// Hash new password and update
|
||||
const newHash = await bcrypt.hash(newPassword, 10);
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
'UPDATE users SET password_hash = ? WHERE id = ?',
|
||||
[newHash, req.user.id],
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
await pool.query(
|
||||
'UPDATE users SET password_hash = $1 WHERE id = $2',
|
||||
[newHash, req.user.id]
|
||||
);
|
||||
|
||||
logAudit(db, {
|
||||
logAudit({
|
||||
userId: req.user.id,
|
||||
username: req.user.username,
|
||||
action: 'password_change',
|
||||
@@ -401,17 +371,9 @@ function createAuthRouter(db, logAudit) {
|
||||
* @returns {object} 403 - { error: 'Insufficient permissions', required: ['Admin'], current: '...' }
|
||||
* @returns {object} 500 - { error: 'Cleanup failed' }
|
||||
*/
|
||||
router.post('/cleanup-sessions', requireAuth(db), requireGroup('Admin'), async (req, res) => {
|
||||
router.post('/cleanup-sessions', requireAuth(), requireGroup('Admin'), async (req, res) => {
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
"DELETE FROM sessions WHERE expires_at < datetime('now')",
|
||||
(err) => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
await pool.query("DELETE FROM sessions WHERE expires_at < NOW()");
|
||||
res.json({ message: 'Expired sessions cleaned up' });
|
||||
} catch (err) {
|
||||
console.error('Session cleanup error:', err);
|
||||
|
||||
Reference in New Issue
Block a user