feat(mam-api): POST /auth/logout
This commit is contained in:
parent
bcfc19e530
commit
d75a0241eb
2 changed files with 32 additions and 0 deletions
|
|
@ -98,5 +98,15 @@ router.post('/login', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} 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 default router;
|
||||||
export { realUserCount };
|
export { realUserCount };
|
||||||
|
|
|
||||||
|
|
@ -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
|
assert.equal(e2, 'invalid credentials'); // identical message — no enumeration
|
||||||
} finally { await close(); await pool.end(); }
|
} 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(); }
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue