diff --git a/frontend/src/app/(editor)/layout.tsx b/frontend/src/app/(editor)/layout.tsx index 6a8e96241..56a660297 100644 --- a/frontend/src/app/(editor)/layout.tsx +++ b/frontend/src/app/(editor)/layout.tsx @@ -9,7 +9,6 @@ import { BaseUrlContextProvider } from '../../components/common/base-url/base-ur import { FrontendConfigContextProvider } from '../../components/common/frontend-config-context/frontend-config-context-provider' import { MotdModal } from '../../components/global-dialogs/motd-modal/motd-modal' import { DarkMode } from '../../components/layout/dark-mode/dark-mode' -import { ExpectedOriginBoundary } from '../../components/layout/expected-origin-boundary' import { UiNotificationBoundary } from '../../components/notifications/ui-notification-boundary' import { StoreProvider } from '../../redux/store-provider' import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' @@ -30,24 +29,24 @@ export default async function RootLayout({ children }: { children: React.ReactNo - - - - - - - - {children} - - - - - + + + + + + + {children} + + + + ) } +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { themeColor: '#b51f08', applicationName: 'HedgeDoc', diff --git a/frontend/src/app/(render)/layout.tsx b/frontend/src/app/(render)/layout.tsx index 3c34b2e18..583930e5d 100644 --- a/frontend/src/app/(render)/layout.tsx +++ b/frontend/src/app/(render)/layout.tsx @@ -7,7 +7,6 @@ import '../../../global-styles/index.scss' import { ApplicationLoader } from '../../components/application-loader/application-loader' import { BaseUrlContextProvider } from '../../components/common/base-url/base-url-context-provider' import { FrontendConfigContextProvider } from '../../components/common/frontend-config-context/frontend-config-context-provider' -import { ExpectedOriginBoundary } from '../../components/layout/expected-origin-boundary' import { StoreProvider } from '../../redux/store-provider' import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' import React from 'react' @@ -20,16 +19,16 @@ export default async function RootLayout({ children }: { children: React.ReactNo return ( - - - - - {children} - - - - + + + + {children} + + + ) } + +export const dynamic = 'force-dynamic' diff --git a/frontend/src/components/layout/expected-origin-boundary.tsx b/frontend/src/components/layout/expected-origin-boundary.tsx deleted file mode 100644 index e440ab47e..000000000 --- a/frontend/src/components/layout/expected-origin-boundary.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ -import { headers } from 'next/headers' -import type { PropsWithChildren } from 'react' -import React from 'react' - -export interface ExpectedOriginBoundaryProps extends PropsWithChildren { - expectedOrigin: string -} - -export const buildOriginFromHeaders = (): string | undefined => { - const headers1 = headers() - const host = headers1.get('x-forwarded-host') ?? headers1.get('host') - if (host === null) { - return undefined - } - - const protocol = headers1.get('x-forwarded-proto')?.split(',')[0] ?? 'http' - return `${protocol}://${host}` -} - -export const ExpectedOriginBoundary: React.FC = ({ children, expectedOrigin }) => { - const currentOrigin = buildOriginFromHeaders() - - if (new URL(expectedOrigin).origin !== currentOrigin) { - return ( - {`You can't open this page using this URL. For this endpoint "${expectedOrigin}" is expected.`} - ) - } - return children -} diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts new file mode 100644 index 000000000..f44e50386 --- /dev/null +++ b/frontend/src/middleware.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' +import { extractBaseUrls } from './utils/base-url-from-env-extractor' + +/** + * Next.js middleware that checks if the expected and the current origin align. + * + * @param request The current request to check + */ +export function middleware(request: NextRequest) { + const currentOrigin = determineOriginFromHeaders(request.headers) + const expectedOrigin = determineExpectedOrigin(request) + + if (currentOrigin === expectedOrigin) { + return + } + + return new NextResponse( + `You can't open this page using this URL. For this endpoint "${expectedOrigin}" is expected.`, + { + status: 400, + headers: { 'content-type': 'text/plain' } + } + ) +} + +/** + * Determines the host and protocol of the current request by checking the host or x-forwarded-host header. + * + * @param headers The request headers provided by Next.js + */ +const determineOriginFromHeaders = (headers: Headers): string | undefined => { + const host = headers.get('x-forwarded-host') ?? headers.get('host') + if (host === null) { + return undefined + } + + const protocol = headers.get('x-forwarded-proto')?.split(',')[0] ?? 'http' + return `${protocol}://${host}` +} + +/** + * Uses the current route to determine the expected origin. + * + * @param request The current request + */ +const determineExpectedOrigin = (request: NextRequest): string => { + return new URL(request.nextUrl.pathname === '/render' ? extractBaseUrls().renderer : extractBaseUrls().editor).origin +}