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 {};