76 lines
3.6 KiB
JavaScript
76 lines
3.6 KiB
JavaScript
const request = require('supertest');
|
|
const jwt = require('jsonwebtoken');
|
|
const { randomUUID } = require('crypto');
|
|
const { bootCrmDb, seedMinimal, assignAdminRole, buildRouteApp } = require('../integration/helpers/crmDb');
|
|
|
|
let db, cleanup, adminId, customerId, eventId, apps, revocation;
|
|
const slug = 'logout-revocation';
|
|
const cases = [
|
|
['auth', '/logout', 'admin', 'admin_token'],
|
|
['auth', '/gallery/logout', 'gallery', `gallery_token_${slug}`],
|
|
['customerAuth', '/logout', 'customer', 'customer_token'],
|
|
['adminAuth', '/logout', 'admin', 'admin_token'],
|
|
];
|
|
const sign = type => jwt.sign({
|
|
type, ...(type === 'admin' ? { id: adminId } : type === 'customer' ? { customerId } : { eventId, eventSlug: slug }),
|
|
jti: randomUUID(),
|
|
}, process.env.JWT_SECRET, { issuer: 'picpeak-auth' });
|
|
|
|
beforeAll(async () => {
|
|
({ db, cleanup } = await bootCrmDb());
|
|
({ adminId, customerId } = await seedMinimal(db));
|
|
await assignAdminRole(db, adminId);
|
|
const event = await require('../../src/services/eventCreationService').createEvent({
|
|
event_type: 'wedding', event_name: 'Logout revocation', event_date: '2026-10-01',
|
|
slug, password: 'Logout-Strong-Password-924!', expiration_days: 30,
|
|
customer_email: 'customer@example.test', admin_email: 'admin@example.test',
|
|
}, { actor: { id: adminId }, source: 'v1' });
|
|
eventId = event.id;
|
|
await db('events').where({ id: eventId }).update({ slug });
|
|
revocation = require('../../src/utils/tokenRevocation');
|
|
apps = Object.fromEntries(['auth', 'adminAuth', 'customerAuth'].map(name => [
|
|
name, buildRouteApp('/', require(`../../src/routes/${name}`)),
|
|
]));
|
|
});
|
|
afterEach(() => jest.restoreAllMocks());
|
|
afterAll(async () => {
|
|
await require('../../src/services/serviceShutdown').stopServices();
|
|
if (cleanup) await cleanup();
|
|
});
|
|
|
|
it.each(cases)('%s%s revokes a no-expiry %s cookie session', async (route, path, type, cookie) => {
|
|
const token = sign(type);
|
|
const sessionApp = type === 'customer' ? apps.customerAuth : apps.auth;
|
|
const sessionPath = type === 'gallery' ? `/session?slug=${slug}` : '/session';
|
|
const session = () => request(sessionApp).get(sessionPath).set('Cookie', `${cookie}=${token}`);
|
|
const before = await session();
|
|
expect(before.status).toBe(200);
|
|
if (type !== 'customer') expect(before.body.valid).toBe(true);
|
|
|
|
const res = await request(apps[route]).post(path).set('Cookie', `${cookie}=${token}`).send({ slug });
|
|
expect(res.status).toBe(200);
|
|
expect(res.headers['set-cookie'].some(value => value.startsWith(`${cookie}=;`))).toBe(true);
|
|
await revocation.cleanupExpiredRevocations();
|
|
expect(await revocation.isTokenRevoked(jwt.decode(token))).toBe(true);
|
|
const after = await session();
|
|
if (type === 'customer') expect(after.status).toBe(401);
|
|
else expect(after.body.valid).toBe(false);
|
|
});
|
|
|
|
it.each(cases)('%s%s reports failed persistence for %s logout and clears its cookie', async (route, path, type, cookie) => {
|
|
const token = sign(type);
|
|
const realQuery = db.client.query;
|
|
jest.spyOn(db.client, 'query').mockImplementation(function (connection, query) {
|
|
if (/^insert into [`"]revoked_tokens[`"]/.test(query.sql)) {
|
|
return Promise.reject(new Error('simulated revocation write failure'));
|
|
}
|
|
return realQuery.call(this, connection, query);
|
|
});
|
|
const res = await request(apps[route]).post(path).set('Cookie', `${cookie}=${token}`).send({ slug });
|
|
expect(res.status).toBe(500);
|
|
expect(res.body.error).toBeTruthy();
|
|
expect(res.body.message).not.toBe('Logged out successfully');
|
|
expect(res.headers['set-cookie'].some(value => value.startsWith(`${cookie}=;`))).toBe(true);
|
|
expect(await revocation.isTokenRevoked(jwt.decode(token))).toBe(false);
|
|
});
|