From d75a0241eb2eb758a713bee142c56d925a479b10 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Wed, 27 May 2026 14:38:05 -0400 Subject: [PATCH] feat(mam-api): POST /auth/logout --- services/mam-api/src/routes/auth.js | 10 ++++++++++ services/mam-api/test/routes/auth.test.js | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/services/mam-api/src/routes/auth.js b/services/mam-api/src/routes/auth.js index 6efaf37..269f58b 100644 --- a/services/mam-api/src/routes/auth.js +++ b/services/mam-api/src/routes/auth.js @@ -98,5 +98,15 @@ router.post('/login', async (req, res, next) => { } catch (err) { next(err); } }); +// POST /api/v1/auth/logout — destroys the session and clears the cookie. +router.post('/logout', (req, res) => { + if (!req.session) return res.status(204).end(); + req.session.destroy(err => { + if (err) console.error('[auth] session destroy failed:', err.message); + res.clearCookie('dragonflight.sid', { path: '/' }); + res.status(204).end(); + }); +}); + export default router; export { realUserCount }; diff --git a/services/mam-api/test/routes/auth.test.js b/services/mam-api/test/routes/auth.test.js index e70168c..07a7566 100644 --- a/services/mam-api/test/routes/auth.test.js +++ b/services/mam-api/test/routes/auth.test.js @@ -176,3 +176,25 @@ test('POST /auth/login with wrong password → 401 + generic message (no enumera assert.equal(e2, 'invalid credentials'); // identical message — no enumeration } finally { await close(); await pool.end(); } }); + +test('POST /auth/logout destroys the session row and the cookie no longer unlocks /me', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => { + const pool = await setupTestDb(); + const hash = await hashPassword('correct-horse-battery'); + await pool.query(`INSERT INTO users (username, password_hash) VALUES ('alice', $1)`, [hash]); + const { baseUrl, close } = await appWithSessionAndMe(pool); + try { + const loginRes = await fetch(baseUrl + '/api/v1/auth/login', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'alice', password: 'correct-horse-battery' }), + }); + const cookie = (loginRes.headers.get('set-cookie') || '').split(';')[0]; + + const logoutRes = await fetch(baseUrl + '/api/v1/auth/logout', { + method: 'POST', headers: { cookie }, + }); + assert.equal(logoutRes.status, 204); + + const meRes = await fetch(baseUrl + '/api/v1/protected/me', { headers: { cookie } }); + assert.equal(meRes.status, 401); + } finally { await close(); await pool.end(); } +});