mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
fix(frontend): replace expected-origin-boundary with middleware
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
94cf510736
commit
982bc4ba59
4 changed files with 77 additions and 61 deletions
|
@ -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,24 +29,24 @@ 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>
|
<ApplicationLoader>
|
||||||
<ApplicationLoader>
|
<DarkMode />
|
||||||
<DarkMode />
|
<MotdModal />
|
||||||
<MotdModal />
|
<UiNotificationBoundary>{children}</UiNotificationBoundary>
|
||||||
<UiNotificationBoundary>{children}</UiNotificationBoundary>
|
</ApplicationLoader>
|
||||||
</ApplicationLoader>
|
</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',
|
||||||
|
|
|
@ -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,16 +19,16 @@ 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>
|
<ApplicationLoader>{children}</ApplicationLoader>
|
||||||
<ApplicationLoader>{children}</ApplicationLoader>
|
</StoreProvider>
|
||||||
</StoreProvider>
|
</FrontendConfigContextProvider>
|
||||||
</FrontendConfigContextProvider>
|
</BaseUrlContextProvider>
|
||||||
</BaseUrlContextProvider>
|
|
||||||
</ExpectedOriginBoundary>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
55
frontend/src/middleware.ts
Normal file
55
frontend/src/middleware.ts
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue