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:
2026-05-09 10:23:24 -07:00
parent a4355e1ef3
commit cbc22dcac4
3 changed files with 10 additions and 15 deletions

View File

@@ -33,7 +33,7 @@ export function issueSessionCookie(
export function makeRequireAuth(config: Config) {
return async function requireAuth(request: FastifyRequest, reply: FastifyReply): Promise<void> {
try {
const payload = await request.jwtVerify<JwtPayload & { iat?: number }>();
const payload = await request.jwtVerify<JwtPayload>();
slideSessionIfNeeded(request, reply, payload, request.server, config.cookieSecure);
} catch {
const isApi = request.url.startsWith('/api/');

View File

@@ -7,7 +7,7 @@ import { issueSessionCookie } from './auth.ts';
export function slideSessionIfNeeded(
request: FastifyRequest,
reply: FastifyReply,
payload: JwtPayload & { iat?: number },
payload: JwtPayload,
server: FastifyInstance,
cookieSecure: boolean,
): void {

View File

@@ -3,18 +3,13 @@ import Fastify, { type FastifyInstance } from 'fastify';
import fastifyCookie from '@fastify/cookie';
import fastifyJwt from '@fastify/jwt';
import { slideSessionIfNeeded } from '../../src/middleware/session-renewal.ts';
import type { JwtPayload } from '../../src/types.ts';
import {
SESSION_COOKIE_NAME,
SESSION_RENEW_THRESHOLD_SECONDS,
SESSION_TTL_SECONDS,
} from '../../src/constants.ts';
interface TestPayload {
sub: number;
username: string;
iat?: number;
}
async function buildTestServer(): Promise<FastifyInstance> {
const app = Fastify();
await app.register(fastifyCookie);
@@ -60,7 +55,7 @@ describe('slideSessionIfNeeded', () => {
it('re-issues cookie when now - iat >= SESSION_RENEW_THRESHOLD_SECONDS', () => {
const reply = fakeReply();
const payload: TestPayload = {
const payload: JwtPayload = {
sub: 1,
username: 'alice',
iat: NOW_SEC - SESSION_RENEW_THRESHOLD_SECONDS,
@@ -73,7 +68,7 @@ describe('slideSessionIfNeeded', () => {
it('skips re-issue when now - iat < SESSION_RENEW_THRESHOLD_SECONDS', () => {
const reply = fakeReply();
const payload: TestPayload = {
const payload: JwtPayload = {
sub: 1,
username: 'alice',
iat: NOW_SEC - (SESSION_RENEW_THRESHOLD_SECONDS - 1),
@@ -84,35 +79,35 @@ describe('slideSessionIfNeeded', () => {
it('skips re-issue on /logout path', () => {
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);
expect(reply.setCookieCalls.length).toBe(0);
});
it('skips re-issue on /api/v1/auth/logout path', () => {
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);
expect(reply.setCookieCalls.length).toBe(0);
});
it('skips re-issue when /logout has a query string', () => {
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);
expect(reply.setCookieCalls.length).toBe(0);
});
it('treats missing iat as 0 (forces refresh)', () => {
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);
expect(reply.setCookieCalls.length).toBe(1);
});
it('produces a cookie carrying Max-Age=SESSION_TTL_SECONDS', () => {
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);
expect(reply.setCookieCalls[0].opts.maxAge).toBe(SESSION_TTL_SECONDS);
expect(reply.setCookieCalls[0].opts.secure).toBe(true);