fix(config): Replace HD_DOMAIN and HD_EDITOR_BASE_URL with HD_BASE_URL

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-02-05 09:31:33 +01:00
parent 65ac00913b
commit 5e1fdbe81d
21 changed files with 255 additions and 92 deletions

View file

@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: CC0-1.0
HD_DOMAIN="http://localhost"
HD_BASE_URL="http://localhost/"
HD_MEDIA_BACKEND="filesystem"
HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH="uploads/"
HD_DATABASE_TYPE="sqlite"

View file

@ -21,7 +21,7 @@ To build a production image, run the following command *from the root of the rep
When you run the image, you need to provide environment variables to configure HedgeDoc.
See [the config docs](../../docs/content/config/index.md) for more information.
This example starts HedgeDoc on localhost, with non-persistent storage:
`docker run -e HD_DOMAIN=http://localhost -e HD_MEDIA_BACKEND=filesystem -e HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH=uploads -e HD_DATABASE_TYPE=sqlite -e HD_DATABASE_NAME=hedgedoc.sqlite -e HD_SESSION_SECRET=foobar -e HD_LOGLEVEL=debug -p 3000:3000 hedgedoc-prod`
`docker run -e HD_BASE_URL=http://localhost -e HD_MEDIA_BACKEND=filesystem -e HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH=uploads -e HD_DATABASE_TYPE=sqlite -e HD_DATABASE_NAME=hedgedoc.sqlite -e HD_SESSION_SECRET=foobar -e HD_LOGLEVEL=debug -p 3000:3000 hedgedoc-prod`
## Build a development image

View file

@ -9,9 +9,9 @@ import appConfig from './app.config';
import { Loglevel } from './loglevel.enum';
describe('appConfig', () => {
const domain = 'https://example.com';
const invalidDomain = 'localhost';
const rendererBaseUrl = 'https://render.example.com';
const baseUrl = 'https://example.com/';
const invalidBaseUrl = 'localhost';
const rendererBaseUrl = 'https://render.example.com/';
const port = 3333;
const negativePort = -9000;
const floatPort = 3.14;
@ -26,7 +26,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
HD_RENDERER_BASE_URL: rendererBaseUrl,
PORT: port.toString(),
HD_LOGLEVEL: loglevel,
@ -38,7 +38,7 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
expect(config.port).toEqual(port);
expect(config.loglevel).toEqual(loglevel);
@ -50,7 +50,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: port.toString(),
HD_LOGLEVEL: loglevel,
HD_PERSIST_INTERVAL: '100',
@ -61,8 +61,8 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.rendererBaseUrl).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(baseUrl);
expect(config.port).toEqual(port);
expect(config.loglevel).toEqual(loglevel);
expect(config.persistInterval).toEqual(100);
@ -73,7 +73,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
HD_RENDERER_BASE_URL: rendererBaseUrl,
HD_LOGLEVEL: loglevel,
HD_PERSIST_INTERVAL: '100',
@ -84,7 +84,7 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
expect(config.port).toEqual(3000);
expect(config.loglevel).toEqual(loglevel);
@ -96,7 +96,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
HD_RENDERER_BASE_URL: rendererBaseUrl,
PORT: port.toString(),
HD_PERSIST_INTERVAL: '100',
@ -107,7 +107,7 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
expect(config.port).toEqual(port);
expect(config.loglevel).toEqual(Loglevel.WARN);
@ -119,7 +119,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
HD_RENDERER_BASE_URL: rendererBaseUrl,
HD_LOGLEVEL: loglevel,
PORT: port.toString(),
@ -130,7 +130,7 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
expect(config.port).toEqual(port);
expect(config.loglevel).toEqual(Loglevel.TRACE);
@ -142,7 +142,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
HD_RENDERER_BASE_URL: rendererBaseUrl,
HD_LOGLEVEL: loglevel,
PORT: port.toString(),
@ -154,7 +154,7 @@ describe('appConfig', () => {
},
);
const config = appConfig();
expect(config.domain).toEqual(domain);
expect(config.baseUrl).toEqual(baseUrl);
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
expect(config.port).toEqual(port);
expect(config.loglevel).toEqual(Loglevel.TRACE);
@ -163,11 +163,11 @@ describe('appConfig', () => {
});
});
describe('throws error', () => {
it('when given a non-valid HD_DOMAIN', async () => {
it('when given a non-valid HD_BASE_URL', async () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: invalidDomain,
HD_BASE_URL: invalidBaseUrl,
PORT: port.toString(),
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -176,7 +176,25 @@ describe('appConfig', () => {
clear: true,
},
);
expect(() => appConfig()).toThrow('HD_DOMAIN');
expect(() => appConfig()).toThrow('HD_BASE_URL');
restore();
});
it('when given a base url with path but no trailing slash in HD_BASE_URL', async () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_BASE_URL: 'https://example.org/a',
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
},
{
clear: true,
},
);
expect(() => appConfig()).toThrow(
'"HD_BASE_URL" must end with a trailing slash',
);
restore();
});
@ -184,7 +202,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: negativePort.toString(),
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -201,7 +219,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: outOfRangePort.toString(),
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -220,7 +238,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: floatPort.toString(),
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -237,7 +255,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: invalidPort,
HD_LOGLEVEL: loglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -254,7 +272,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: port.toString(),
HD_LOGLEVEL: invalidLoglevel,
/* eslint-enable @typescript-eslint/naming-convention */
@ -271,7 +289,7 @@ describe('appConfig', () => {
const restore = mockedEnv(
{
/* eslint-disable @typescript-eslint/naming-convention */
HD_DOMAIN: domain,
HD_BASE_URL: baseUrl,
PORT: port.toString(),
HD_LOGLEVEL: invalidLoglevel,
HD_PERSIST_INTERVAL: invalidPersistInterval.toString(),

View file

@ -3,31 +3,50 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
MissingTrailingSlashError,
parseUrl,
WrongProtocolError,
} from '@hedgedoc/commons';
import { registerAs } from '@nestjs/config';
import * as Joi from 'joi';
import { CustomHelpers, ErrorReport } from 'joi';
import { Loglevel } from './loglevel.enum';
import { buildErrorMessage, parseOptionalNumber } from './utils';
export interface AppConfig {
domain: string;
baseUrl: string;
rendererBaseUrl: string;
port: number;
loglevel: Loglevel;
persistInterval: number;
}
function validateUrlWithTrailingSlash(
value: string,
helpers: CustomHelpers,
): string | ErrorReport {
try {
return parseUrl(value).isPresent() ? value : helpers.error('string.uri');
} catch (error) {
if (error instanceof MissingTrailingSlashError) {
return helpers.error('url.missingTrailingSlash');
} else if (error instanceof WrongProtocolError) {
return helpers.error('url.wrongProtocol');
} else {
throw error;
}
}
}
const schema = Joi.object({
domain: Joi.string()
.uri({
scheme: /https?/,
})
.label('HD_DOMAIN'),
baseUrl: Joi.string()
.custom(validateUrlWithTrailingSlash)
.label('HD_BASE_URL'),
rendererBaseUrl: Joi.string()
.uri({
scheme: /https?/,
})
.default(Joi.ref('domain'))
.custom(validateUrlWithTrailingSlash)
.default(Joi.ref('baseUrl'))
.optional()
.label('HD_RENDERER_BASE_URL'),
port: Joi.number()
@ -48,12 +67,17 @@ const schema = Joi.object({
.default(10)
.optional()
.label('HD_PERSIST_INTERVAL'),
}).messages({
// eslint-disable-next-line @typescript-eslint/naming-convention
'url.missingTrailingSlash': '{{#label}} must end with a trailing slash',
// eslint-disable-next-line @typescript-eslint/naming-convention
'url.wrongProtocol': '{{#label}} protocol must be HTTP or HTTPS',
});
export default registerAs('appConfig', () => {
const appConfig = schema.validate(
{
domain: process.env.HD_DOMAIN,
baseUrl: process.env.HD_BASE_URL,
rendererBaseUrl: process.env.HD_RENDERER_BASE_URL,
port: parseOptionalNumber(process.env.PORT),
loglevel: process.env.HD_LOGLEVEL,

View file

@ -11,7 +11,7 @@ import { Loglevel } from '../loglevel.enum';
export function createDefaultMockAppConfig(): AppConfig {
return {
domain: 'md.example.com',
baseUrl: 'md.example.com',
rendererBaseUrl: 'md-renderer.example.com',
port: 3000,
loglevel: Loglevel.ERROR,

View file

@ -167,7 +167,7 @@ describe('FrontendConfigService', () => {
]) {
it(`works with ${JSON.stringify(authConfigConfigured)}`, async () => {
const appConfig: AppConfig = {
domain: domain,
baseUrl: domain,
rendererBaseUrl: 'https://renderer.example.org',
port: 3000,
loglevel: Loglevel.ERROR,
@ -325,7 +325,7 @@ describe('FrontendConfigService', () => {
]) {
it(`combination #${index} works`, async () => {
const appConfig: AppConfig = {
domain: domain,
baseUrl: domain,
rendererBaseUrl: 'https://renderer.example.org',
port: 3000,
loglevel: Loglevel.ERROR,

View file

@ -18,7 +18,7 @@ describe('App', () => {
})
.overrideProvider(getConfigToken('appConfig'))
.useValue({
domain: 'localhost',
baseUrl: 'localhost',
port: 3333,
})
.overrideProvider(getConfigToken('mediaConfig'))