From 5497b7b4aa59f27683745c8dd1abcec62562ddf4 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Tue, 21 Jul 2026 16:45:19 -0400 Subject: [PATCH] chore(express): remove redundant option-object casts Strip the as any and as SomeOptions casts on the option literals passed to the core handlers. These casts disabled excess-property checking, which is what let a mistyped option be silently dropped in the getSeamlessUser regression. Removing them lets the compiler check each literal against its handler interface. Delete the dead forwardedClientIp: undefined property on the ensureCookies middleware options (the value is computed per request), and replace the blanket req.query cast in the metrics handlers with a small normalizer that reduces Express's ParsedQs to the scalar query record the core handler declares. No public API change. --- .changeset/remove-option-casts.md | 5 +++ packages/express/src/createServer.ts | 7 ++-- packages/express/src/handlers/admin.ts | 26 +++++++------- .../src/handlers/bootstrapAdmininvite.ts | 2 +- packages/express/src/handlers/finishLogin.ts | 2 +- .../express/src/handlers/finishRegister.ts | 2 +- .../express/src/handlers/internalMetrics.ts | 35 ++++++++++++++----- packages/express/src/handlers/login.ts | 2 +- packages/express/src/handlers/me.ts | 2 +- .../src/handlers/pollMagicLinkConfirmation.ts | 2 +- packages/express/src/handlers/register.ts | 2 +- .../express/src/handlers/requestMagicLink.ts | 2 +- packages/express/src/handlers/requestOtp.ts | 2 +- packages/express/src/handlers/sessions.ts | 6 ++-- packages/express/src/handlers/systemConfig.ts | 6 ++-- .../express/src/middleware/ensureCookies.ts | 2 +- 16 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 .changeset/remove-option-casts.md diff --git a/.changeset/remove-option-casts.md b/.changeset/remove-option-casts.md new file mode 100644 index 0000000..9a77ba0 --- /dev/null +++ b/.changeset/remove-option-casts.md @@ -0,0 +1,5 @@ +--- +"@seamless-auth/express": patch +--- + +Remove the redundant `as any` / option-object casts across the Express adapter so the compiler checks each option literal against its handler interface. These casts were the construct that previously let a mistyped option (`serviceAuthorization`) be silently dropped. No public API change. One internal cleanup with a visible edge case: the internal metrics handlers now reduce an array-valued query parameter to its first value rather than letting it reach the upstream comma-joined, which the scalar handler contract never supported. diff --git a/packages/express/src/createServer.ts b/packages/express/src/createServer.ts index eb0705f..e0e0dcb 100644 --- a/packages/express/src/createServer.ts +++ b/packages/express/src/createServer.ts @@ -294,7 +294,7 @@ export function createSeamlessAuthServer( const resolvedPath = typeof path === "function" ? path(req) : path; const upstream = await authFetch( `${resolvedOpts.authServerUrl}/${resolvedPath}${queryString ? `?${queryString}` : ""}`, - options as any, + options, ); const data = await upstream.json(); @@ -330,8 +330,7 @@ export function createSeamlessAuthServer( audience: "seamless-auth", keyId: resolvedOpts.jwksKid, resolveClientIp: resolvedOpts.resolveClientIp, - forwardedClientIp: undefined, - } as any), + }), ); r.post( @@ -494,7 +493,7 @@ export function createSeamlessAuthServer( method: "GET", serviceAuthorization: buildProxyServiceAuthorization(resolvedOpts), forwardedClientIp: buildForwardedClientIp(req, resolvedOpts.resolveClientIp), - } as any, + }, ); const data = await upstream.json(); diff --git a/packages/express/src/handlers/admin.ts b/packages/express/src/handlers/admin.ts index d1f7bd1..b80f18c 100644 --- a/packages/express/src/handlers/admin.ts +++ b/packages/express/src/handlers/admin.ts @@ -41,7 +41,7 @@ export const getUsers = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const createUser = async ( @@ -57,7 +57,7 @@ export const createUser = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), body: req.body, - } as any), + }), ); export const deleteUser = async ( @@ -73,7 +73,7 @@ export const deleteUser = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), body: req.body, - } as any), + }), ); export const updateUser = async ( @@ -89,7 +89,7 @@ export const updateUser = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), body: req.body, - } as any), + }), ); export const getUserDetail = async ( @@ -104,7 +104,7 @@ export const getUserDetail = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const getUserAnomalies = async ( @@ -119,7 +119,7 @@ export const getUserAnomalies = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const getAuthEvents = async ( @@ -135,7 +135,7 @@ export const getAuthEvents = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), query: req.query, - } as any), + }), ); export const getCredentialCount = async ( @@ -150,7 +150,7 @@ export const getCredentialCount = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const listAllSessions = async ( @@ -166,7 +166,7 @@ export const listAllSessions = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), query: req.query, - } as any), + }), ); export const listUserSessions = async ( @@ -181,7 +181,7 @@ export const listUserSessions = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const revokeUserSession = async ( @@ -196,7 +196,7 @@ export const revokeUserSession = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const revokeAllUserSessions = async ( @@ -211,7 +211,7 @@ export const revokeAllUserSessions = async ( authorization: buildServiceAuthorization(req, opts), serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any), + }), ); export const recoverUserForDeviceReplacement = async ( @@ -227,5 +227,5 @@ export const recoverUserForDeviceReplacement = async ( serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), body: req.body, - } as any), + }), ); diff --git a/packages/express/src/handlers/bootstrapAdmininvite.ts b/packages/express/src/handlers/bootstrapAdmininvite.ts index 1f1c007..3ab1ad3 100644 --- a/packages/express/src/handlers/bootstrapAdmininvite.ts +++ b/packages/express/src/handlers/bootstrapAdmininvite.ts @@ -22,7 +22,7 @@ export async function bootstrapAdminInvite( serviceAuthorization: opts.messaging ? buildInternalServiceAuthorization(opts) : buildProxyServiceAuthorization(opts), - } as any); + }); if (result.error) { return res.status(result.status).json({ error: result.error }); diff --git a/packages/express/src/handlers/finishLogin.ts b/packages/express/src/handlers/finishLogin.ts index 0270fba..b2ef3d0 100644 --- a/packages/express/src/handlers/finishLogin.ts +++ b/packages/express/src/handlers/finishLogin.ts @@ -23,7 +23,7 @@ export async function finishLogin( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any, + }, { authServerUrl: opts.authServerUrl, audience: opts.audience, diff --git a/packages/express/src/handlers/finishRegister.ts b/packages/express/src/handlers/finishRegister.ts index 3de6ac1..cd30afd 100644 --- a/packages/express/src/handlers/finishRegister.ts +++ b/packages/express/src/handlers/finishRegister.ts @@ -23,7 +23,7 @@ export async function finishRegister( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any, + }, { authServerUrl: opts.authServerUrl, audience: opts.audience, diff --git a/packages/express/src/handlers/internalMetrics.ts b/packages/express/src/handlers/internalMetrics.ts index 410f7d2..bb0e0dc 100644 --- a/packages/express/src/handlers/internalMetrics.ts +++ b/packages/express/src/handlers/internalMetrics.ts @@ -22,6 +22,23 @@ function handle(res: Response, result: any) { return res.status(result.status).json(result.body); } +// Express types req.query as ParsedQs, whose values may be arrays or nested +// objects. The metrics handlers only forward scalar query params, so reduce to +// the first string value per key instead of casting the whole object. +function toQueryRecord( + query: Request["query"], +): Record { + const record: Record = {}; + for (const [key, value] of Object.entries(query)) { + if (typeof value === "string") { + record[key] = value; + } else if (Array.isArray(value) && typeof value[0] === "string") { + record[key] = value[0]; + } + } + return record; +} + export async function getAuthEventSummary( req: Request, res: Response, @@ -34,8 +51,8 @@ export async function getAuthEventSummary( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - query: req.query as any, - } as any); + query: toQueryRecord(req.query), + }); return handle(res, result); } @@ -52,8 +69,8 @@ export async function getAuthEventTimeseries( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - query: req.query as any, - } as any); + query: toQueryRecord(req.query), + }); return handle(res, result); } @@ -70,7 +87,7 @@ export async function getLoginStats( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } @@ -87,7 +104,7 @@ export async function getSecurityAnomalies( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } @@ -104,7 +121,7 @@ export async function getDashboardMetrics( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } @@ -121,8 +138,8 @@ export async function getGroupedEventSummary( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - query: req.query as any, - } as any); + query: toQueryRecord(req.query), + }); return handle(res, result); } diff --git a/packages/express/src/handlers/login.ts b/packages/express/src/handlers/login.ts index 9af4bdc..7d4119b 100644 --- a/packages/express/src/handlers/login.ts +++ b/packages/express/src/handlers/login.ts @@ -21,7 +21,7 @@ export async function login( preAuthCookieName: opts.preAuthCookieName!, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any, + }, ); if (result.setCookies) { diff --git a/packages/express/src/handlers/me.ts b/packages/express/src/handlers/me.ts index a5ae0eb..61948c4 100644 --- a/packages/express/src/handlers/me.ts +++ b/packages/express/src/handlers/me.ts @@ -20,7 +20,7 @@ export async function me( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); if (result.clearCookies) { const signer = buildCookieSigner(opts); diff --git a/packages/express/src/handlers/pollMagicLinkConfirmation.ts b/packages/express/src/handlers/pollMagicLinkConfirmation.ts index fef4100..8d3d156 100644 --- a/packages/express/src/handlers/pollMagicLinkConfirmation.ts +++ b/packages/express/src/handlers/pollMagicLinkConfirmation.ts @@ -22,7 +22,7 @@ export async function pollMagicLinkConfirmation( { authorization, forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any, + }, { authServerUrl: opts.authServerUrl, audience: opts.audience, diff --git a/packages/express/src/handlers/register.ts b/packages/express/src/handlers/register.ts index 12710ea..387c1b3 100644 --- a/packages/express/src/handlers/register.ts +++ b/packages/express/src/handlers/register.ts @@ -27,7 +27,7 @@ export async function register( serviceAuthorization: opts.messaging ? buildInternalServiceAuthorization(opts) : buildProxyServiceAuthorization(opts), - } as any, + }, ); if (result.setCookies) { diff --git a/packages/express/src/handlers/requestMagicLink.ts b/packages/express/src/handlers/requestMagicLink.ts index 1ecc894..8fabe4a 100644 --- a/packages/express/src/handlers/requestMagicLink.ts +++ b/packages/express/src/handlers/requestMagicLink.ts @@ -25,7 +25,7 @@ export async function requestMagicLink( serviceAuthorization: opts.messaging ? buildInternalServiceAuthorization(opts) : buildProxyServiceAuthorization(opts), - } as any, + }, ); if (result.error) { diff --git a/packages/express/src/handlers/requestOtp.ts b/packages/express/src/handlers/requestOtp.ts index ee0c361..1e0f561 100644 --- a/packages/express/src/handlers/requestOtp.ts +++ b/packages/express/src/handlers/requestOtp.ts @@ -29,7 +29,7 @@ export async function requestOtp( serviceAuthorization: opts.messaging ? buildInternalServiceAuthorization(opts) : buildProxyServiceAuthorization(opts), - } as any, + }, ); if (result.error) { diff --git a/packages/express/src/handlers/sessions.ts b/packages/express/src/handlers/sessions.ts index 3426d89..d1b5d70 100644 --- a/packages/express/src/handlers/sessions.ts +++ b/packages/express/src/handlers/sessions.ts @@ -31,7 +31,7 @@ export async function listSessions( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } @@ -48,7 +48,7 @@ export async function revokeSession( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } @@ -65,7 +65,7 @@ export async function revokeAllSessions( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); return handle(res, result); } diff --git a/packages/express/src/handlers/systemConfig.ts b/packages/express/src/handlers/systemConfig.ts index 95c43b5..3a24743 100644 --- a/packages/express/src/handlers/systemConfig.ts +++ b/packages/express/src/handlers/systemConfig.ts @@ -24,7 +24,7 @@ export async function getAvailableRoles( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); if (result.error) { return res.status(result.status).json({ error: result.error }); @@ -45,7 +45,7 @@ export async function getSystemConfigAdmin( authorization, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); if (result.error) { return res.status(result.status).json({ error: result.error }); @@ -67,7 +67,7 @@ export async function updateSystemConfig( payload: req.body, serviceAuthorization: buildProxyServiceAuthorization(opts), forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any); + }); if (result.error) { return res.status(result.status).json({ error: result.error }); diff --git a/packages/express/src/middleware/ensureCookies.ts b/packages/express/src/middleware/ensureCookies.ts index 253f9b0..bccf174 100644 --- a/packages/express/src/middleware/ensureCookies.ts +++ b/packages/express/src/middleware/ensureCookies.ts @@ -62,7 +62,7 @@ export function createEnsureCookiesMiddleware( audience: opts.audience, keyId: opts.keyId, forwardedClientIp: buildForwardedClientIp(req, opts.resolveClientIp), - } as any, + }, ); applyResult(res, req, result, opts, cookieSigner);