Repository navigation
fix(core): keep the HTTP status of client-safe errors (expired token → 401, not 500) - #727
Conversation
…→ 401, not 500)
The express error boundary boomified every non-Boom error without a
statusCode, so a ClientSafeError such as "Invalid Token: Token is expired"
(401) answered 500. Keep the status for client-safe errors with a 4xx/5xx
statusCode; every other error, including upstream FetchError statuses,
still answers 500.
A malformed bearer token (not base64 JSON, or JSON without id/token) now
throws ClientSafeError('Invalid Token: Token is malformed', 401) instead of
a SyntaxError/TypeError that answered 500.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
✅ Deploy Preview for friggframework-org canceled.
|
|
| if ( | ||
| !['string', 'number'].includes(typeof jsonToken?.id) || | ||
| typeof jsonToken?.token !== 'string' | ||
| ) { | ||
| throw new ClientSafeError('Invalid Token: Token is malformed', 401); | ||
| } |
There was a problem hiding this comment.
🟣 pre-existing, not blocking: pre-existing, partial fix: a bearer token whose id has the wrong shape for the database still gets a 500 instead of the promised 401. The new check accepts any string or number id, but validateAndGetToken cannot handle every such value. On Postgres, _convertId throws a plain Error for id: "abc". On Mongo, Prisma rejects a number or a non-ObjectId string. Fix: turn every id the adapter cannot look up into ClientSafeError('Invalid Token: Token is malformed', 401) (Postgres: integer-parsable; Mongo: 24-hex ObjectId string), which covers the 2 sites: token-repository-postgres.js:90 and token-repository-mongo.js:58. [also at: packages/core/token/repositories/token-repository-mongo.js:225 - pre-existing. A bearer token whose decoded id is a string or number but not a valid id for the adapter still gets a 500 on Mongo and Postgres.]
A small fix can ride a push you are already making; otherwise a short reply is enough.
Why this was flagged
The trigger is a client sending Authorization: Bearer <base64 of {"id":"abc","token":"x"}> (or {"id":123,...} on Mongo). The new check at token-repository-postgres.js:266-271 lets it through because typeof id is 'string'. Then validateAndGetToken calls _convertId (token-repository-postgres.js:32-34), which throws new Error('Invalid ID: abc cannot be converted to integer'). That error is not client-safe, so toBoom makes it a 500 'Internal Server Error' and logs frigg.http.request_failed at ERROR. On Mongo, prisma.token.findUnique({ where: { id: tokenObj.id } }) (token-repository-mongo.js:57-58) throws a Prisma validation or malformed-ObjectID error for a numeric or non-hex id, with the same 500 result. The base branch also returned 500 here. The PR says malformed tokens now get 401, but the shape check is looser than what each adapter's lookup accepts.
Verification: pre-existing. The new check at token-repository-postgres.js:266-271 only tests !['string', 'number'].includes(typeof jsonToken?.id), so "abc" passes it. _convertId (lines 30-37) does parseInt and then throws. That is a plain Error, not a ClientSafeError, so the new toBoom falls back to 500. The base commit already returned 500 for these inputs, so nothing gets worse.
An automated safety screen replaced some characters, such as angle brackets, in the code quoted in this comment with HTML escape codes.
|
🚀 PR was released in |


Problem
An expired session token on
GET /api/integrations(seen on2.0.0-next.120andnext.125) answers 500{"error":"Internal Server Error"}instead of 401. The token repository throwsClientSafeError('Invalid Token: Token is expired', 401), but the express boundary inhandlers/app-handler-helpers.jscalledBoom.boomify(err)without astatusCode, so every non-Boom error, including everyClientSafeError, became 500.A malformed bearer token had the same outcome:
getJSONTokenFromBase64BufferTokenthrew aSyntaxErrorfromJSON.parse(or aTypeErrorlater fornull/{}payloads) → 500.Fix
handlers/app-handler-helpers.js: boomify with{ statusCode }only whenerr.isClientSafe === trueand the status is an integer 400–599. Everything else still answers 500 withInternal Server Error.FetchErrorcarries an upstream third-party status and is not client-safe, so it stays 500. A 5xxClientSafeErrorkeeps its status but its message stays hidden.idandtokenthrowsClientSafeError('Invalid Token: Token is malformed', 401).This brings the express boundary in line with
core/create-handler.js, which already returns the status of client-safe errors for non-express Lambdas.Not changed
core/create-handler.jsuseserror.statusCode || 400with no range check, and a 5xxClientSafeErrorthere returns its message. This predates the PR.handlers/routers/db-migration{,.handler}.jskeep their own boundaries that always answer 500 (admin only).token/repositories/token-repository.js(exported, not used by the user repositories) still uses rawJSON.parse.statusCode400,expose: true) still answer 500, because they are notClientSafeError.Test plan
ClientSafeError→ 401{ error: 'Invalid Token: Token is expired' }, one WARNrequest_rejected, norequest_failedClientSafeError403/404 keep their status and messageClientSafeError503 → 503Internal Server ErrorClientSafeErrorwith statusCode 200/302/600/'401'/'nope' → 500Internal Server ErrorFetchErrorwith an upstream 401 → 500Error→ 500 with no message leak; Boom errors unchanged (existing tests)null, number,{}, missing token) → 401ClientSafeErroron all three repositories; a created token still round-tripsnpx jestin packages/core: same 7 failing suites asnext(env/pre-existing), no new failures🤖 Generated with Claude Code
Note
Medium Risk
Touches authentication error handling and HTTP status mapping at the Express boundary; behavior change is intentional but affects all Express routes and token parsing.
Overview
Fixes Express HTTP handlers returning 500 for auth failures that should be 401 (and other client-safe 4xx), by teaching the error middleware to preserve
statusCodewhenerr.isClientSafeis true and the code is a valid HTTP error (400–599).The Express boundary in
app-handler-helpers.jsnow uses atoBoomhelper instead of plainBoom.boomify(err). 4xxClientSafeErrorresponses include the safe message and logrequest_rejected; 5xx client-safe errors keep the status but still returnInternal Server Error. Invalid or non-integer status codes and non-client-safe errors (e.g.FetchErrorwith an upstream 401) still answer 500.Mongo, Postgres, and DocumentDB token repositories no longer let
JSON.parsefailures bubble as 500s: malformed base64 bearer payloads throwClientSafeError('Invalid Token: Token is malformed', 401)after shape validation (id+ stringtoken).Tests cover the Express middleware matrix and shared
getJSONTokenFromBase64BufferTokenbehavior across all three repositories.Reviewed by Cursor Bugbot for commit f8fcc58. Bugbot is set up for automated code reviews on this repo. Configure here.
📦 Published PR as canary version:
2.0.0--canary.727.f8fcc58.0✨ Test out this PR locally via:
npm install @friggframework/admin-scripts@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/core@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/devtools@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/eslint-config@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/prettier-config@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/schemas@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/serverless-plugin@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/test@2.0.0--canary.727.f8fcc58.0 npm install @friggframework/ui@2.0.0--canary.727.f8fcc58.0 # or yarn add @friggframework/admin-scripts@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/core@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/devtools@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/eslint-config@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/prettier-config@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/schemas@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/serverless-plugin@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/test@2.0.0--canary.727.f8fcc58.0 yarn add @friggframework/ui@2.0.0--canary.727.f8fcc58.0Version
Published prerelease version:
v2.0.0-next.126Changelog
🐛 Bug Fix
@friggframework/core@friggframework/admin-scripts,@friggframework/core,@friggframework/devtools,@friggframework/eslint-config,@friggframework/prettier-config,@friggframework/schemas,@friggframework/serverless-plugin,@friggframework/test,@friggframework/uiAuthors: 1