refactor: drop redundant iat intersections and reuse JwtPayload in tests
JwtPayload already declares iat?: number, so the & { iat?: number } in
makeRequireAuth and slideSessionIfNeeded was a no-op. The unit test had a
local TestPayload duplicating the same shape — replaced with the canonical
import.
This commit is contained in:
@@ -33,7 +33,7 @@ export function issueSessionCookie(
|
|||||||
export function makeRequireAuth(config: Config) {
|
export function makeRequireAuth(config: Config) {
|
||||||
return async function requireAuth(request: FastifyRequest, reply: FastifyReply): Promise<void> {
|
return async function requireAuth(request: FastifyRequest, reply: FastifyReply): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const payload = await request.jwtVerify<JwtPayload & { iat?: number }>();
|
const payload = await request.jwtVerify<JwtPayload>();
|
||||||
slideSessionIfNeeded(request, reply, payload, request.server, config.cookieSecure);
|
slideSessionIfNeeded(request, reply, payload, request.server, config.cookieSecure);
|
||||||
} catch {
|
} catch {
|
||||||
const isApi = request.url.startsWith('/api/');
|
const isApi = request.url.startsWith('/api/');
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { issueSessionCookie } from './auth.ts';
|
|||||||
export function slideSessionIfNeeded(
|
export function slideSessionIfNeeded(
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
payload: JwtPayload & { iat?: number },
|
payload: JwtPayload,
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
cookieSecure: boolean,
|
cookieSecure: boolean,
|
||||||
): void {
|
): void {
|
||||||
|
|||||||
@@ -3,18 +3,13 @@ import Fastify, { type FastifyInstance } from 'fastify';
|
|||||||
import fastifyCookie from '@fastify/cookie';
|
import fastifyCookie from '@fastify/cookie';
|
||||||
import fastifyJwt from '@fastify/jwt';
|
import fastifyJwt from '@fastify/jwt';
|
||||||
import { slideSessionIfNeeded } from '../../src/middleware/session-renewal.ts';
|
import { slideSessionIfNeeded } from '../../src/middleware/session-renewal.ts';
|
||||||
|
import type { JwtPayload } from '../../src/types.ts';
|
||||||
import {
|
import {
|
||||||
SESSION_COOKIE_NAME,
|
SESSION_COOKIE_NAME,
|
||||||
SESSION_RENEW_THRESHOLD_SECONDS,
|
SESSION_RENEW_THRESHOLD_SECONDS,
|
||||||
SESSION_TTL_SECONDS,
|
SESSION_TTL_SECONDS,
|
||||||
} from '../../src/constants.ts';
|
} from '../../src/constants.ts';
|
||||||
|
|
||||||
interface TestPayload {
|
|
||||||
sub: number;
|
|
||||||
username: string;
|
|
||||||
iat?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildTestServer(): Promise<FastifyInstance> {
|
async function buildTestServer(): Promise<FastifyInstance> {
|
||||||
const app = Fastify();
|
const app = Fastify();
|
||||||
await app.register(fastifyCookie);
|
await app.register(fastifyCookie);
|
||||||
@@ -60,7 +55,7 @@ describe('slideSessionIfNeeded', () => {
|
|||||||
|
|
||||||
it('re-issues cookie when now - iat >= SESSION_RENEW_THRESHOLD_SECONDS', () => {
|
it('re-issues cookie when now - iat >= SESSION_RENEW_THRESHOLD_SECONDS', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = {
|
const payload: JwtPayload = {
|
||||||
sub: 1,
|
sub: 1,
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
iat: NOW_SEC - SESSION_RENEW_THRESHOLD_SECONDS,
|
iat: NOW_SEC - SESSION_RENEW_THRESHOLD_SECONDS,
|
||||||
@@ -73,7 +68,7 @@ describe('slideSessionIfNeeded', () => {
|
|||||||
|
|
||||||
it('skips re-issue when now - iat < SESSION_RENEW_THRESHOLD_SECONDS', () => {
|
it('skips re-issue when now - iat < SESSION_RENEW_THRESHOLD_SECONDS', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = {
|
const payload: JwtPayload = {
|
||||||
sub: 1,
|
sub: 1,
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
iat: NOW_SEC - (SESSION_RENEW_THRESHOLD_SECONDS - 1),
|
iat: NOW_SEC - (SESSION_RENEW_THRESHOLD_SECONDS - 1),
|
||||||
@@ -84,35 +79,35 @@ describe('slideSessionIfNeeded', () => {
|
|||||||
|
|
||||||
it('skips re-issue on /logout path', () => {
|
it('skips re-issue on /logout path', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = { sub: 1, username: 'alice', iat: 0 }; // ancient
|
const payload: JwtPayload = { sub: 1, username: 'alice', iat: 0 }; // ancient
|
||||||
slideSessionIfNeeded(fakeRequest('/logout') as never, reply as never, payload, app, false);
|
slideSessionIfNeeded(fakeRequest('/logout') as never, reply as never, payload, app, false);
|
||||||
expect(reply.setCookieCalls.length).toBe(0);
|
expect(reply.setCookieCalls.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips re-issue on /api/v1/auth/logout path', () => {
|
it('skips re-issue on /api/v1/auth/logout path', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = { sub: 1, username: 'alice', iat: 0 };
|
const payload: JwtPayload = { sub: 1, username: 'alice', iat: 0 };
|
||||||
slideSessionIfNeeded(fakeRequest('/api/v1/auth/logout') as never, reply as never, payload, app, false);
|
slideSessionIfNeeded(fakeRequest('/api/v1/auth/logout') as never, reply as never, payload, app, false);
|
||||||
expect(reply.setCookieCalls.length).toBe(0);
|
expect(reply.setCookieCalls.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips re-issue when /logout has a query string', () => {
|
it('skips re-issue when /logout has a query string', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = { sub: 1, username: 'alice', iat: 0 };
|
const payload: JwtPayload = { sub: 1, username: 'alice', iat: 0 };
|
||||||
slideSessionIfNeeded(fakeRequest('/logout?redirect=foo') as never, reply as never, payload, app, false);
|
slideSessionIfNeeded(fakeRequest('/logout?redirect=foo') as never, reply as never, payload, app, false);
|
||||||
expect(reply.setCookieCalls.length).toBe(0);
|
expect(reply.setCookieCalls.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('treats missing iat as 0 (forces refresh)', () => {
|
it('treats missing iat as 0 (forces refresh)', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = { sub: 1, username: 'alice' };
|
const payload: JwtPayload = { sub: 1, username: 'alice' };
|
||||||
slideSessionIfNeeded(fakeRequest('/upload') as never, reply as never, payload, app, false);
|
slideSessionIfNeeded(fakeRequest('/upload') as never, reply as never, payload, app, false);
|
||||||
expect(reply.setCookieCalls.length).toBe(1);
|
expect(reply.setCookieCalls.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('produces a cookie carrying Max-Age=SESSION_TTL_SECONDS', () => {
|
it('produces a cookie carrying Max-Age=SESSION_TTL_SECONDS', () => {
|
||||||
const reply = fakeReply();
|
const reply = fakeReply();
|
||||||
const payload: TestPayload = { sub: 1, username: 'alice', iat: 0 };
|
const payload: JwtPayload = { sub: 1, username: 'alice', iat: 0 };
|
||||||
slideSessionIfNeeded(fakeRequest('/upload') as never, reply as never, payload, app, true);
|
slideSessionIfNeeded(fakeRequest('/upload') as never, reply as never, payload, app, true);
|
||||||
expect(reply.setCookieCalls[0].opts.maxAge).toBe(SESSION_TTL_SECONDS);
|
expect(reply.setCookieCalls[0].opts.maxAge).toBe(SESSION_TTL_SECONDS);
|
||||||
expect(reply.setCookieCalls[0].opts.secure).toBe(true);
|
expect(reply.setCookieCalls[0].opts.secure).toBe(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user