diff --git a/src/middleware/idempotency.ts b/src/middleware/idempotency.ts index 404d4f2b..66c8d537 100644 --- a/src/middleware/idempotency.ts +++ b/src/middleware/idempotency.ts @@ -1,4 +1,4 @@ -import type { NextFunction, Request, Response } from 'express'; +import type { NextFunction, Request, Response, RequestHandler } from 'express'; import type { Pool } from 'pg'; import { createHash } from 'crypto'; import { config } from '../config/index.js'; @@ -342,3 +342,15 @@ export async function idempotencyMiddleware( next(error); } } + +/** + * Factory that returns a 3-parameter Express RequestHandler for idempotencyMiddleware. + * Express treats 4-parameter functions as error handlers, so this wrapper ensures + * Express dispatches the middleware during normal request processing. + */ +export function createIdempotencyMiddleware(opts?: IdempotencyConfig): RequestHandler { + return (req: Request, res: Response, next: NextFunction) => { + idempotencyMiddleware(req, res, next, opts); + }; +} + diff --git a/src/routes/refresh-token.ts b/src/routes/refresh-token.ts index 1f1616a4..9f7301fa 100644 --- a/src/routes/refresh-token.ts +++ b/src/routes/refresh-token.ts @@ -14,9 +14,10 @@ * - Token hashes are never exposed in the response */ -import { Router } from 'express'; +import { Router, type Request } from 'express'; import { requireAuth } from '../middleware/requireAuth.js'; import { correlationMiddleware } from '../middleware/correlation.js'; +import { idempotencyMiddleware } from '../middleware/idempotency.js'; import { getClientIp, DEFAULT_PROXY_HEADERS } from '../lib/clientIp.js'; import { encodeCursor, parseCursor } from '../lib/cursorPagination.js'; import { @@ -45,6 +46,7 @@ export interface RefreshTokenRouterDeps { refreshTokenRepository?: RefreshTokenRepository; rateLimitMiddleware?: RequestHandler; rateLimiter?: TokenBucketRateLimiter; + idempotencyMiddleware?: RequestHandler; } export function createRefreshTokenRouter(deps: RefreshTokenRouterDeps = {}): Router { @@ -58,6 +60,15 @@ export function createRefreshTokenRouter(deps: RefreshTokenRouterDeps = {}): Rou deps.rateLimiter, ); + const idempotencyHandler: RequestHandler = + deps.idempotencyMiddleware ?? + ((req, res, next) => + idempotencyMiddleware(req, res, next, { + methods: ['POST', 'PATCH'], + })); + + router.use(idempotencyHandler); + /** * GET /api/refresh-token *