import { test } from 'node:test'; import assert from 'node:assert/strict'; import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js'; import { requireAuth, DEV_USER_ID } from '../../src/middleware/auth.js'; import { generateToken, hashToken } from '../../src/auth/tokens.js'; function mockRes() { const res = { statusCode: 200, body: null, status(n) { this.statusCode = n; return this; }, json(o) { this.body = o; return this; }, }; return res; } function mockReq({ session = null, authHeader = null } = {}) { return { session, headers: authHeader ? { authorization: authHeader } : {}, }; } test('AUTH_ENABLED=false → attaches dev user and calls next', async () => { delete process.env.AUTH_ENABLED; const req = mockReq(); const res = mockRes(); let called = false; await requireAuth(req, res, () => { called = true; }); assert.equal(called, true); assert.equal(req.user.id, DEV_USER_ID); assert.equal(req.user.username, 'dev'); }); test('AUTH_ENABLED=true + no session + no bearer → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; try { const { requireAuth } = await import('../../src/middleware/auth.js?cache=' + Date.now()); const req = mockReq(); const res = mockRes(); await requireAuth(req, res, () => {}); assert.equal(res.statusCode, 401); assert.deepEqual(res.body, { error: 'unauthorized' }); } finally { await pool.end(); } }); test('valid session within idle/absolute window → next + req.user populated', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; const { rows } = await pool.query( `INSERT INTO users (username, password_hash, display_name, role) VALUES ('alice', 'x', 'Alice', 'admin') RETURNING id`); const userId = rows[0].id; try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const now = Date.now(); const req = mockReq({ session: { user_id: userId, first_seen_at: now - 1000, last_seen_at: now - 500 } }); const res = mockRes(); let called = false; await requireAuth(req, res, () => { called = true; }); assert.equal(called, true, 'next not called; body=' + JSON.stringify(res.body)); assert.equal(req.user.id, userId); assert.equal(req.user.username, 'alice'); } finally { await pool.end(); } }); test('idle-expired session (>1h since last_seen_at) → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; const { rows } = await pool.query( `INSERT INTO users (username, password_hash) VALUES ('b', 'x') RETURNING id`); try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const now = Date.now(); const session = { user_id: rows[0].id, first_seen_at: now - 1000, last_seen_at: now - (61 * 60 * 1000), destroy(cb){ cb(); } }; const req = mockReq({ session }); const res = mockRes(); await requireAuth(req, res, () => {}); assert.equal(res.statusCode, 401); } finally { await pool.end(); } }); test('absolute-expired session (>8h since first_seen_at) → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; const { rows } = await pool.query( `INSERT INTO users (username, password_hash) VALUES ('c', 'x') RETURNING id`); try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const now = Date.now(); const session = { user_id: rows[0].id, first_seen_at: now - (9 * 3600 * 1000), last_seen_at: now - 100, destroy(cb){ cb(); } }; const req = mockReq({ session }); const res = mockRes(); await requireAuth(req, res, () => {}); assert.equal(res.statusCode, 401); } finally { await pool.end(); } }); test('valid bearer token → next + req.user populated', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; const { rows: u } = await pool.query( `INSERT INTO users (username, password_hash) VALUES ('d', 'x') RETURNING id`); const token = generateToken(); await pool.query( `INSERT INTO api_tokens (user_id, name, token_hash, token_prefix) VALUES ($1, 'test', $2, $3)`, [u[0].id, hashToken(token), token.slice(0, 8)]); try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const req = mockReq({ authHeader: 'Bearer ' + token }); const res = mockRes(); let called = false; await requireAuth(req, res, () => { called = true; }); assert.equal(called, true, 'next not called; body=' + JSON.stringify(res.body)); assert.equal(req.user.username, 'd'); } finally { await pool.end(); } }); test('invalid bearer token → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const req = mockReq({ authHeader: 'Bearer dfl_nope' }); const res = mockRes(); await requireAuth(req, res, () => {}); assert.equal(res.statusCode, 401); } finally { await pool.end(); } }); test('bearer token whose user was deleted → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { const pool = await setupTestDb(); process.env.AUTH_ENABLED = 'true'; process.env.DATABASE_URL = process.env.TEST_DATABASE_URL; const { rows: u } = await pool.query( `INSERT INTO users (username, password_hash) VALUES ('e', 'x') RETURNING id`); const token = generateToken(); await pool.query( `INSERT INTO api_tokens (user_id, name, token_hash, token_prefix) VALUES ($1, 'test', $2, $3)`, [u[0].id, hashToken(token), token.slice(0, 8)]); await pool.query(`DELETE FROM users WHERE id = $1`, [u[0].id]); try { const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now()); const req = mockReq({ authHeader: 'Bearer ' + token }); const res = mockRes(); await requireAuth(req, res, () => {}); assert.equal(res.statusCode, 401); } finally { await pool.end(); } });