From 4945b5429848b36fc0ee41cf0277ed79f53d8286 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Thu, 17 Oct 2024 07:25:06 +0800 Subject: [PATCH] fix: fetch override to attach XSRF token to fix csrfProtection issue (#1014) During the migration from Axios to fetch, we overlooked the fact that Axios automatically handled CSRF tokens, while fetch does not. When CSRF protection was turned on, requests were failing with an "invalid CSRF token" error for users accessing the app even via HTTPS. This commit overrides fetch to ensure that the CSRF token is included in all requests. fix #1011 --- src/pages/_app.tsx | 1 + src/utils/fetchOverride.ts | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/utils/fetchOverride.ts diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ba0677c64..e5704052d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -12,6 +12,7 @@ import { SettingsProvider } from '@app/context/SettingsContext'; import { UserContext } from '@app/context/UserContext'; import type { User } from '@app/hooks/useUser'; import '@app/styles/globals.css'; +import '@app/utils/fetchOverride'; import { polyfillIntl } from '@app/utils/polyfillIntl'; import { MediaServerType } from '@server/constants/server'; import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces'; diff --git a/src/utils/fetchOverride.ts b/src/utils/fetchOverride.ts new file mode 100644 index 000000000..e0a900125 --- /dev/null +++ b/src/utils/fetchOverride.ts @@ -0,0 +1,46 @@ +const getCsrfToken = (): string | null => { + if (typeof window !== 'undefined') { + const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/); + return match ? decodeURIComponent(match[1]) : null; + } + return null; +}; + +const isSameOrigin = (url: RequestInfo | URL): boolean => { + const parsedUrl = new URL( + url instanceof Request ? url.url : url.toString(), + window.location.origin + ); + return parsedUrl.origin === window.location.origin; +}; + +// We are using a custom fetch implementation to add the X-XSRF-TOKEN heade +// to all requests. This is required when CSRF protection is enabled. +if (typeof window !== 'undefined') { + const originalFetch: typeof fetch = window.fetch; + + (window as typeof globalThis).fetch = async ( + input: RequestInfo | URL, + init?: RequestInit + ): Promise => { + if (!isSameOrigin(input)) { + return originalFetch(input, init); + } + + const csrfToken = getCsrfToken(); + + const headers = { + ...(init?.headers || {}), + ...(csrfToken ? { 'XSRF-TOKEN': csrfToken } : {}), + }; + + const newInit: RequestInit = { + ...init, + headers, + }; + + return originalFetch(input, newInit); + }; +} + +export {};