fix(frontend): replace expected-origin-boundary with middleware

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-09-02 19:11:16 +02:00
parent 94cf510736
commit 982bc4ba59
4 changed files with 77 additions and 61 deletions

View file

@ -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 { FrontendConfigContextProvider } from '../../components/common/frontend-config-context/frontend-config-context-provider'
import { MotdModal } from '../../components/global-dialogs/motd-modal/motd-modal' import { MotdModal } from '../../components/global-dialogs/motd-modal/motd-modal'
import { DarkMode } from '../../components/layout/dark-mode/dark-mode' 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 { UiNotificationBoundary } from '../../components/notifications/ui-notification-boundary'
import { StoreProvider } from '../../redux/store-provider' import { StoreProvider } from '../../redux/store-provider'
import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' import { extractBaseUrls } from '../../utils/base-url-from-env-extractor'
@ -30,7 +29,6 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<link color='#b51f08' href='/icons/safari-pinned-tab.svg' rel='mask-icon' /> <link color='#b51f08' href='/icons/safari-pinned-tab.svg' rel='mask-icon' />
</head> </head>
<body> <body>
<ExpectedOriginBoundary expectedOrigin={baseUrls.editor}>
<BaseUrlContextProvider baseUrls={baseUrls}> <BaseUrlContextProvider baseUrls={baseUrls}>
<FrontendConfigContextProvider config={frontendConfig}> <FrontendConfigContextProvider config={frontendConfig}>
<StoreProvider> <StoreProvider>
@ -42,12 +40,13 @@ export default async function RootLayout({ children }: { children: React.ReactNo
</StoreProvider> </StoreProvider>
</FrontendConfigContextProvider> </FrontendConfigContextProvider>
</BaseUrlContextProvider> </BaseUrlContextProvider>
</ExpectedOriginBoundary>
</body> </body>
</html> </html>
) )
} }
export const dynamic = 'force-dynamic'
export const metadata: Metadata = { export const metadata: Metadata = {
themeColor: '#b51f08', themeColor: '#b51f08',
applicationName: 'HedgeDoc', applicationName: 'HedgeDoc',

View file

@ -7,7 +7,6 @@ import '../../../global-styles/index.scss'
import { ApplicationLoader } from '../../components/application-loader/application-loader' import { ApplicationLoader } from '../../components/application-loader/application-loader'
import { BaseUrlContextProvider } from '../../components/common/base-url/base-url-context-provider' import { BaseUrlContextProvider } from '../../components/common/base-url/base-url-context-provider'
import { FrontendConfigContextProvider } from '../../components/common/frontend-config-context/frontend-config-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 { StoreProvider } from '../../redux/store-provider'
import { extractBaseUrls } from '../../utils/base-url-from-env-extractor' import { extractBaseUrls } from '../../utils/base-url-from-env-extractor'
import React from 'react' import React from 'react'
@ -20,7 +19,6 @@ export default async function RootLayout({ children }: { children: React.ReactNo
return ( return (
<html lang='en'> <html lang='en'>
<body> <body>
<ExpectedOriginBoundary expectedOrigin={baseUrls.renderer}>
<BaseUrlContextProvider baseUrls={baseUrls}> <BaseUrlContextProvider baseUrls={baseUrls}>
<FrontendConfigContextProvider config={frontendConfig}> <FrontendConfigContextProvider config={frontendConfig}>
<StoreProvider> <StoreProvider>
@ -28,8 +26,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
</StoreProvider> </StoreProvider>
</FrontendConfigContextProvider> </FrontendConfigContextProvider>
</BaseUrlContextProvider> </BaseUrlContextProvider>
</ExpectedOriginBoundary>
</body> </body>
</html> </html>
) )
} }
export const dynamic = 'force-dynamic'

View file

@ -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<ExpectedOriginBoundaryProps> = ({ children, expectedOrigin }) => {
const currentOrigin = buildOriginFromHeaders()
if (new URL(expectedOrigin).origin !== currentOrigin) {
return (
<span
className={
'text-white bg-dark'
}>{`You can't open this page using this URL. For this endpoint "${expectedOrigin}" is expected.`}</span>
)
}
return children
}

View file

@ -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
}