mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-02 07:59:56 -04:00
Refactor handling of environment variables (#2303)
* Refactor environment variables Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e412115a78
commit
39a4125cb0
85 changed files with 624 additions and 461 deletions
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isMockMode } from './test-modes'
|
||||
import { backendUrl } from './backend-url'
|
||||
|
||||
/**
|
||||
* Generates the url to the api.
|
||||
*/
|
||||
export const apiUrl = isMockMode ? `/api/mock-backend/private/` : `${backendUrl}api/private/`
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isMockMode } from './test-modes'
|
||||
|
||||
/**
|
||||
* Generates the backend URL from the environment variable `NEXT_PUBLIC_BACKEND_BASE_URL` or the mock default if mock mode is activated.
|
||||
*
|
||||
* @throws Error if the environment variable is unset or doesn't end with "/"
|
||||
* @return the backend url that should be used in the app
|
||||
*/
|
||||
const generateBackendUrl = (): string => {
|
||||
if (!isMockMode) {
|
||||
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_BASE_URL
|
||||
if (backendUrl === undefined) {
|
||||
throw new Error('NEXT_PUBLIC_BACKEND_BASE_URL is unset and mock mode is disabled')
|
||||
} else if (!backendUrl.endsWith('/')) {
|
||||
throw new Error("NEXT_PUBLIC_BACKEND_BASE_URL must end with an '/'")
|
||||
} else {
|
||||
return backendUrl
|
||||
}
|
||||
} else {
|
||||
return '/'
|
||||
}
|
||||
}
|
||||
|
||||
export const backendUrl = generateBackendUrl()
|
68
src/utils/base-url-from-env-extractor.test.ts
Normal file
68
src/utils/base-url-from-env-extractor.test.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { BaseUrlFromEnvExtractor } from './base-url-from-env-extractor'
|
||||
|
||||
describe('BaseUrlFromEnvExtractor', () => {
|
||||
it('should return the base urls if both are valid urls', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
const result = baseUrlFromEnvExtractor.extractBaseUrls()
|
||||
expect(result.isPresent()).toBeTruthy()
|
||||
expect(result.get()).toStrictEqual({
|
||||
renderer: 'https://renderer.example.org/',
|
||||
editor: 'https://editor.example.org/'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return an empty optional if no var is set', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = undefined
|
||||
process.env.HD_RENDERER_BASE_URL = undefined
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if editor base url isn't an URL", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'bibedibabedibu'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if renderer base url isn't an URL", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'bibedibabedibu'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if editor base url isn't ending with a slash", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org/'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it("should return an empty optional if renderer base url isn't ending with a slash", () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
process.env.HD_RENDERER_BASE_URL = 'https://renderer.example.org'
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
expect(baseUrlFromEnvExtractor.extractBaseUrls().isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should copy editor base url to renderer base url if renderer base url is omitted', () => {
|
||||
process.env.HD_EDITOR_BASE_URL = 'https://editor.example.org/'
|
||||
delete process.env.HD_RENDERER_BASE_URL
|
||||
const baseUrlFromEnvExtractor = new BaseUrlFromEnvExtractor()
|
||||
const result = baseUrlFromEnvExtractor.extractBaseUrls()
|
||||
expect(result.isPresent()).toBeTruthy()
|
||||
expect(result.get()).toStrictEqual({
|
||||
renderer: 'https://editor.example.org/',
|
||||
editor: 'https://editor.example.org/'
|
||||
})
|
||||
})
|
||||
})
|
89
src/utils/base-url-from-env-extractor.ts
Normal file
89
src/utils/base-url-from-env-extractor.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
import type { BaseUrls } from '../components/common/base-url/base-url-context-provider'
|
||||
import { Logger } from './logger'
|
||||
import { isTestMode } from './test-modes'
|
||||
|
||||
/**
|
||||
* Extracts the editor and renderer base urls from the environment variables.
|
||||
*/
|
||||
export class BaseUrlFromEnvExtractor {
|
||||
private baseUrls: Optional<BaseUrls> | undefined
|
||||
private logger = new Logger('Base URL Configuration')
|
||||
|
||||
private extractUrlFromEnvVar(envVarName: string, envVarValue: string | undefined): Optional<URL> {
|
||||
return Optional.ofNullable(envVarValue)
|
||||
.filter((value) => {
|
||||
const endsWithSlash = value.endsWith('/')
|
||||
if (!endsWithSlash) {
|
||||
this.logger.error(`${envVarName} must end with an '/'`)
|
||||
}
|
||||
return endsWithSlash
|
||||
})
|
||||
.map((value) => {
|
||||
try {
|
||||
return new URL(value)
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private extractEditorBaseUrlFromEnv(): Optional<URL> {
|
||||
const envValue = this.extractUrlFromEnvVar('HD_EDITOR_BASE_URL', process.env.HD_EDITOR_BASE_URL)
|
||||
if (envValue.isEmpty()) {
|
||||
this.logger.error("HD_EDITOR_BASE_URL isn't a valid URL!")
|
||||
}
|
||||
return envValue
|
||||
}
|
||||
|
||||
private extractRendererBaseUrlFromEnv(editorBaseUrl: URL): Optional<URL> {
|
||||
if (isTestMode) {
|
||||
this.logger.info('Test mode activated. Using editor base url for renderer.')
|
||||
return Optional.of(editorBaseUrl)
|
||||
}
|
||||
|
||||
if (!process.env.HD_RENDERER_BASE_URL) {
|
||||
this.logger.info('HD_RENDERER_BASE_URL is unset. Using editor base url for renderer.')
|
||||
return Optional.of(editorBaseUrl)
|
||||
}
|
||||
|
||||
return this.extractUrlFromEnvVar('HD_RENDERER_BASE_URL', process.env.HD_RENDERER_BASE_URL)
|
||||
}
|
||||
|
||||
private renewBaseUrls(): void {
|
||||
this.baseUrls = this.extractEditorBaseUrlFromEnv().flatMap((editorBaseUrl) =>
|
||||
this.extractRendererBaseUrlFromEnv(editorBaseUrl).map((rendererBaseUrl) => {
|
||||
return {
|
||||
editor: editorBaseUrl.toString(),
|
||||
renderer: rendererBaseUrl.toString()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.baseUrls.ifPresent((urls) => {
|
||||
this.logger.info('Editor base URL', urls.editor.toString())
|
||||
this.logger.info('Renderer base URL', urls.renderer.toString())
|
||||
})
|
||||
}
|
||||
|
||||
private isEnvironmentExtractDone(): boolean {
|
||||
return this.baseUrls !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the editor and renderer base urls from the environment variables.
|
||||
*
|
||||
* @return An {@link Optional} with the base urls.
|
||||
*/
|
||||
public extractBaseUrls(): Optional<BaseUrls> {
|
||||
if (!this.isEnvironmentExtractDone()) {
|
||||
this.renewBaseUrls()
|
||||
}
|
||||
return Optional.ofNullable(this.baseUrls).flatMap((value) => value)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { backendUrl } from './backend-url'
|
||||
import { isMockMode } from './test-modes'
|
||||
|
||||
/**
|
||||
* Generates the url to the assets.
|
||||
*/
|
||||
export const customizeAssetsUrl = isMockMode
|
||||
? `/mock-public/`
|
||||
: process.env.NEXT_PUBLIC_CUSTOMIZE_ASSETS_URL || `${backendUrl}public/`
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the given string is a positive answer (yes, true or 1).
|
||||
*
|
||||
* @param value The value to check
|
||||
*/
|
||||
export const isPositiveAnswer = (value: string) => {
|
||||
const lowerValue = value.toLowerCase()
|
||||
return lowerValue === 'yes' || lowerValue === '1' || lowerValue === 'true'
|
||||
}
|
44
src/utils/test-modes.js
Normal file
44
src/utils/test-modes.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is intentionally a js and not a ts file because it is used in `next.config.js`
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the given string is a positive answer (yes, true or 1).
|
||||
*
|
||||
* @param {string} value The value to check
|
||||
* @return {boolean} {@code true} if the value describes a positive answer string
|
||||
*/
|
||||
const isPositiveAnswer = (value) => {
|
||||
const lowerValue = value.toLowerCase()
|
||||
return lowerValue === 'yes' || lowerValue === '1' || lowerValue === 'true'
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines if the current runtime is built in e2e test mode.
|
||||
* @type boolean
|
||||
*/
|
||||
const isTestMode = !!process.env.NEXT_PUBLIC_TEST_MODE && isPositiveAnswer(process.env.NEXT_PUBLIC_TEST_MODE)
|
||||
|
||||
/**
|
||||
* Defines if the current runtime should use the mocked backend.
|
||||
* @type boolean
|
||||
*/
|
||||
const isMockMode = !!process.env.NEXT_PUBLIC_USE_MOCK_API && isPositiveAnswer(process.env.NEXT_PUBLIC_USE_MOCK_API)
|
||||
|
||||
/**
|
||||
* Defines if the current runtime was built in development mode.
|
||||
* @type boolean
|
||||
*/
|
||||
const isDevMode = process.env.NODE_ENV === 'development'
|
||||
|
||||
module.exports = {
|
||||
isTestMode,
|
||||
isMockMode,
|
||||
isDevMode
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isPositiveAnswer } from './is-positive-answer'
|
||||
|
||||
/**
|
||||
* Checks if the current runtime is built in e2e test mode.
|
||||
*/
|
||||
export const isTestMode = !!process.env.NEXT_PUBLIC_TEST_MODE && isPositiveAnswer(process.env.NEXT_PUBLIC_TEST_MODE)
|
||||
|
||||
/**
|
||||
* Checks if the current runtime should use the mocked backend.
|
||||
*/
|
||||
export const isMockMode =
|
||||
!!process.env.NEXT_PUBLIC_USE_MOCK_API && isPositiveAnswer(process.env.NEXT_PUBLIC_USE_MOCK_API)
|
||||
|
||||
/**
|
||||
* Checks if the current runtime was built in development mode.
|
||||
*/
|
||||
export const isDevMode = process.env.NODE_ENV === 'development'
|
27
src/utils/uri-origin-boundary.tsx
Normal file
27
src/utils/uri-origin-boundary.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useMemo } from 'react'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import { isClientSideRendering } from './is-client-side-rendering'
|
||||
import { useBaseUrl } from '../hooks/common/use-base-url'
|
||||
|
||||
/**
|
||||
* Checks if the url of the current browser window matches the expected origin.
|
||||
* This is necessary to ensure that the render endpoint is only opened from the rendering origin.
|
||||
*
|
||||
* @param children The children react element that should be rendered if the origin is correct
|
||||
*/
|
||||
export const ExpectedOriginBoundary: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const baseUrl = useBaseUrl()
|
||||
const expectedOrigin = useMemo(() => new URL(baseUrl).origin, [baseUrl])
|
||||
|
||||
if (isClientSideRendering() && window.location.origin !== expectedOrigin) {
|
||||
return <span>{`You can't open this page using this URL. For this endpoint "${expectedOrigin}" is expected.`}</span>
|
||||
} else {
|
||||
return <Fragment>{children}</Fragment>
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue