Conversation
`POST /api/auth/users/resend-invite` answered `400 Invalid request origin` behind any reverse proxy in production. Creating a user sent the invite fine; only the resend path broke. The route handed `getSecurityEmailBaseUrl` the URL string instead of the request. With a string the origin check only sees the URL origin, and Next.js builds a route handler's URL from its own listen host and port, so behind a proxy that origin is `https://localhost:3000`, never the public host. The loopback exemption needs the `Host` / `X-Forwarded-*` headers to find an allowed origin, and a string carries none, so the check threw. Pass the request, as every other caller already does. Add a route test for the proxy case: production mode, public `APP_URL`, loopback request URL, public host in the headers. It returned 400 before this change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jtomaszewski
left a comment
There was a problem hiding this comment.
Is it just here? perhaps getSecurityEmailBaseUrl shouldnt accept url as argument if that causes bugs?
It accepts either string or the full request. The other callers used the full req object so the function can get the proper url from the headers. This invocation was the only instance which passed the raw (incorrect) url string - in this case the url string was incorrect because of the proxy we have
|
|
yeah then change the interface of getSecurityEmailBaseUrl so it doesnt allow for incorrect invocation. |
`getSecurityEmailBaseUrl`, `toSecurityEmailUrl` and `assertAllowedAppOrigin` took `Request | string | undefined`. A string input can never pass the check behind a reverse proxy, because the public host only ever arrives in the headers, so the string overload existed only to be misused. The resend-invite route was the one caller that did. Narrow the input to `Request | undefined` and drop the string branches. Callers with no request context, such as the CLI user command and the onboarding ready email, still pass nothing. Passing a string is now a compile error. Requested in review by jtomaszewski. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Done in 4ffc50c. |
|
No description provided. |
Problem
POST /api/auth/users/resend-inviteanswers400 {"error":"Invalid request origin"}on any deployment behind a reverse proxy in production. Creating a user still sends the invite email. Only the "Resend invite" button breaks. We hit this on Edube prod (Cloud Run,APP_URL=https://edube.me) and locally under Conductor, and carried a patch-package workaround until now. Present in 0.6.7 and 0.7.0.Log line from prod:
Cause
The route passed
req.urltogetSecurityEmailBaseUrl. Every other caller (reset.ts,requestRedirect.ts,customer_accounts/api/signup.ts, onboarding) passesreq.The origin check in
@open-mercato/shared/lib/urlcan read two things. Given aRequestit also readsHost,X-Forwarded-HostandX-Forwarded-Proto, finds the public origin there, and then tolerates a loopback URL origin. Given a string it only has the URL origin.Behind a proxy the URL origin is never the public host. Next.js builds a route handler's request URL from its own listen hostname and port (
next-server.js,${protocol}://${this.fetchHostname}:${this.port}${req.url}), so undernext startit ishttps://localhost:3000/.... WithNODE_ENV=productionand a non-loopbackAPP_URL, no exemption applies and the route maps the throw to 400.Fix
One word in
packages/core/src/modules/auth/api/users/resend-invite/route.ts:Interface change (review follow-up)
The helper took
Request | string | undefined. A string can never satisfy the check behind a proxy, so the overload existed only to be misused. The second commit narrowsgetSecurityEmailBaseUrl,toSecurityEmailUrlandassertAllowedAppOrigintoRequest | undefinedand drops the string branches. Passing a string is now a compile error. Callers with no request at all (the CLI user command, the onboarding ready email) still pass nothing.These helpers are not in the
BACKWARD_COMPATIBILITY.mdsignature table. A third-party caller passing a string would fail to compile after this change, and would have been getting 400s in production anyway.Verification
Added a case to
resend-invite.route.test.ts: production mode,APP_URL=https://app.example, request URLhttps://localhost:3000/..., headershost: app.exampleandx-forwarded-proto: https. It returned 400 and logged the exact warning above before the change. It passes now.sharedandonboarding: 0 errors;coreshows only its pre-existing unrelated errors, none in the origin-check callersLabels
skip-qa: no UI file touched, no schema or API change, regression test in the same PR.risk-low: one call-site change aligning with every sibling route.🤖 Generated with Claude Code