mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import 'server-only'
|
|
|
|
import { getCampaignInviteBaseURL } from '@/utilities/campaignInviteOrigin'
|
|
|
|
/**
|
|
* CSRF guard for cookie-authenticated JSON route handlers (auto-save popovers):
|
|
* a same-origin browser request either omits `Origin` (same-site navigation)
|
|
* or sends one matching an origin this deployment answers as. Two bases are
|
|
* accepted, and both must hold for every consumer:
|
|
*
|
|
* - the literal `request.url` base — what a browser sends when it reaches this
|
|
* server directly (dev, worktrees, direct TLS);
|
|
* - the proxy-aware public base resolved with the established
|
|
* `getCampaignInviteBaseURL` policy (`x-forwarded-*` for local authorities,
|
|
* `NEXT_PUBLIC_SITE_URL` — HTTPS public DNS or throw — in production). This
|
|
* is what a browser sends behind the Cloudflare tunnel, where `request.url`
|
|
* still carries the internal `http://localhost:3000` origin.
|
|
*
|
|
* Resolving the second base can throw (production misconfiguration); the
|
|
* failure is contained to that base so the decision falls to the first.
|
|
* Anything that matches neither is rejected before the body is even parsed.
|
|
*
|
|
* The `Origin` under verification doubles as the resolver's `requestOrigin`
|
|
* input: production ignores it (the configured site URL decides), while
|
|
* dev/test accept any localhost/127.0.0.1/[::1] port through it — a dev-only
|
|
* relaxation, never a production one.
|
|
*
|
|
* Every `POST /campanha/**` endpoint inherits this through
|
|
* `campaignJsonMutationRoute`; the two remaining JSON routes
|
|
* (`campanha/api/ai-transcribe`, `api/social-feed/sync`) call it directly and
|
|
* keep their own rejection envelopes.
|
|
*/
|
|
export const isSameOriginRequest = (request: Request): boolean => {
|
|
const origin = request.headers.get('origin')
|
|
if (!origin) return true
|
|
|
|
let originURL: URL
|
|
try {
|
|
originURL = new URL(origin)
|
|
} catch {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
if (originURL.origin === new URL(request.url).origin) return true
|
|
} catch {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
return (
|
|
originURL.origin ===
|
|
new URL(
|
|
getCampaignInviteBaseURL({
|
|
requestOrigin: origin,
|
|
forwardedHost: request.headers.get('x-forwarded-host') ?? request.headers.get('host'),
|
|
forwardedProto: request.headers.get('x-forwarded-proto'),
|
|
}),
|
|
).origin
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|