142 lines
4.3 KiB
JavaScript
142 lines
4.3 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* Tests for server/middleware/requireAuth.js
|
|
*
|
|
* requireAuth guards all authenticated API routes. Tests exercise the full
|
|
* range of valid/invalid cookie states to ensure there's no bypass path.
|
|
*/
|
|
|
|
import requireAuth from '../../server/middleware/requireAuth.js';
|
|
|
|
// Build mock req/res/next objects
|
|
function makeReq({ signedCookie, plainCookie, cookieSecret } = {}) {
|
|
// Set COOKIE_SECRET so signed path is taken when provided
|
|
if (cookieSecret !== undefined) {
|
|
process.env.COOKIE_SECRET = cookieSecret;
|
|
} else {
|
|
delete process.env.COOKIE_SECRET;
|
|
}
|
|
|
|
return {
|
|
signedCookies: { emby_user: signedCookie },
|
|
cookies: { emby_user: plainCookie }
|
|
};
|
|
}
|
|
|
|
function makeRes() {
|
|
const res = {
|
|
statusCode: null,
|
|
body: null,
|
|
status(code) { this.statusCode = code; return this; },
|
|
json(body) { this.body = body; return this; }
|
|
};
|
|
return res;
|
|
}
|
|
|
|
afterEach(() => {
|
|
delete process.env.COOKIE_SECRET;
|
|
});
|
|
|
|
describe('requireAuth middleware', () => {
|
|
describe('valid sessions', () => {
|
|
it('calls next() with a valid signed cookie', () => {
|
|
const payload = JSON.stringify({ id: 'u1', name: 'Alice', isAdmin: true });
|
|
const req = makeReq({ signedCookie: payload, cookieSecret: 'secret' });
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalledOnce();
|
|
expect(req.user).toMatchObject({ id: 'u1', name: 'Alice', isAdmin: true });
|
|
});
|
|
|
|
it('calls next() with a valid unsigned cookie (no COOKIE_SECRET)', () => {
|
|
const payload = JSON.stringify({ id: 'u2', name: 'Bob', isAdmin: false });
|
|
const req = makeReq({ plainCookie: payload });
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalledOnce();
|
|
expect(req.user.id).toBe('u2');
|
|
});
|
|
|
|
it('coerces non-boolean isAdmin to boolean', () => {
|
|
const payload = JSON.stringify({ id: 'u3', name: 'Charlie', isAdmin: 1 });
|
|
const req = makeReq({ plainCookie: payload });
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(req.user.isAdmin).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('missing or invalid cookies', () => {
|
|
it('returns 401 when no cookie is present', () => {
|
|
const req = makeReq({});
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(res.statusCode).toBe(401);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns 401 when signed cookie value is false (tampered)', () => {
|
|
// cookie-parser sets signed cookie to false when signature is invalid
|
|
const req = makeReq({ signedCookie: false, cookieSecret: 'secret' });
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(res.statusCode).toBe(401);
|
|
});
|
|
|
|
it('returns 401 for malformed JSON in cookie', () => {
|
|
const req = makeReq({ plainCookie: 'not-json' });
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
|
|
requireAuth(req, res, next);
|
|
|
|
expect(res.statusCode).toBe(401);
|
|
expect(res.body.error).toBe('Invalid session');
|
|
});
|
|
|
|
it('returns 401 when id is missing', () => {
|
|
const payload = JSON.stringify({ name: 'Alice', isAdmin: false });
|
|
const req = makeReq({ plainCookie: payload });
|
|
requireAuth(req, makeRes(), vi.fn());
|
|
// no next called — handled in the assertion below
|
|
const res = makeRes();
|
|
const next = vi.fn();
|
|
requireAuth(req, res, next);
|
|
expect(res.statusCode).toBe(401);
|
|
expect(next).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns 401 when name is missing', () => {
|
|
const payload = JSON.stringify({ id: 'u1', isAdmin: false });
|
|
const req = makeReq({ plainCookie: payload });
|
|
const res = makeRes();
|
|
requireAuth(req, res, vi.fn());
|
|
expect(res.statusCode).toBe(401);
|
|
});
|
|
|
|
it('returns 401 when id is empty string', () => {
|
|
const payload = JSON.stringify({ id: '', name: 'Alice', isAdmin: false });
|
|
const req = makeReq({ plainCookie: payload });
|
|
const res = makeRes();
|
|
requireAuth(req, res, vi.fn());
|
|
expect(res.statusCode).toBe(401);
|
|
});
|
|
});
|
|
});
|