mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-09 13:51:57 -04:00
fix(frontend config): Remove origins from frontend configuration
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
9b2cc5ceba
commit
35032eef09
8 changed files with 120 additions and 163 deletions
|
@ -23,7 +23,7 @@ We also provide an `.env.example` file containing a minimal configuration in the
|
||||||
|--------------------------|-----------|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|--------------------------|-----------|-----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `HD_DOMAIN` | - | `https://md.example.com` | The URL the HedgeDoc instance runs on. |
|
| `HD_DOMAIN` | - | `https://md.example.com` | The URL the HedgeDoc instance runs on. |
|
||||||
| `PORT` | 3000 | | The port the HedgeDoc instance runs on. |
|
| `PORT` | 3000 | | The port the HedgeDoc instance runs on. |
|
||||||
| `HD_RENDERER_ORIGIN` | HD_DOMAIN | | The URL the renderer runs on. If omitted this will be same as `HD_DOMAIN`. |
|
| `HD_RENDERER_BASE_URL` | HD_DOMAIN | | The URL the renderer runs on. If omitted this will be same as `HD_DOMAIN`. |
|
||||||
| `HD_LOGLEVEL` | warn | | The loglevel that should be used. Options are `error`, `warn`, `info`, `debug` or `trace`. |
|
| `HD_LOGLEVEL` | warn | | The loglevel that should be used. Options are `error`, `warn`, `info`, `debug` or `trace`. |
|
||||||
| `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed,alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. |
|
| `HD_FORBIDDEN_NOTE_IDS` | - | `notAllowed,alsoNotAllowed` | A list of note ids (separated by `,`), that are not allowed to be created or requested by anyone. |
|
||||||
| `HD_MAX_DOCUMENT_LENGTH` | 100000 | | The maximum length of any one document. Changes to this will impact performance for your users. |
|
| `HD_MAX_DOCUMENT_LENGTH` | 100000 | | The maximum length of any one document. Changes to this will impact performance for your users. |
|
||||||
|
|
|
@ -52,9 +52,12 @@ export async function setupApp(
|
||||||
);
|
);
|
||||||
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: appConfig.rendererOrigin,
|
origin: appConfig.rendererBaseUrl,
|
||||||
});
|
});
|
||||||
logger.log(`Enabling CORS for '${appConfig.rendererOrigin}'`, 'AppBootstrap');
|
logger.log(
|
||||||
|
`Enabling CORS for '${appConfig.rendererBaseUrl}'`,
|
||||||
|
'AppBootstrap',
|
||||||
|
);
|
||||||
|
|
||||||
app.useGlobalPipes(setupValidationPipe(logger));
|
app.useGlobalPipes(setupValidationPipe(logger));
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Loglevel } from './loglevel.enum';
|
||||||
describe('appConfig', () => {
|
describe('appConfig', () => {
|
||||||
const domain = 'https://example.com';
|
const domain = 'https://example.com';
|
||||||
const invalidDomain = 'localhost';
|
const invalidDomain = 'localhost';
|
||||||
const rendererOrigin = 'https://render.example.com';
|
const rendererBaseUrl = 'https://render.example.com';
|
||||||
const port = 3333;
|
const port = 3333;
|
||||||
const negativePort = -9000;
|
const negativePort = -9000;
|
||||||
const floatPort = 3.14;
|
const floatPort = 3.14;
|
||||||
|
@ -27,7 +27,7 @@ describe('appConfig', () => {
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_DOMAIN: domain,
|
HD_DOMAIN: domain,
|
||||||
HD_RENDERER_ORIGIN: rendererOrigin,
|
HD_RENDERER_BASE_URL: rendererBaseUrl,
|
||||||
PORT: port.toString(),
|
PORT: port.toString(),
|
||||||
HD_LOGLEVEL: loglevel,
|
HD_LOGLEVEL: loglevel,
|
||||||
HD_PERSIST_INTERVAL: '100',
|
HD_PERSIST_INTERVAL: '100',
|
||||||
|
@ -39,7 +39,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(rendererOrigin);
|
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
|
||||||
expect(config.port).toEqual(port);
|
expect(config.port).toEqual(port);
|
||||||
expect(config.loglevel).toEqual(loglevel);
|
expect(config.loglevel).toEqual(loglevel);
|
||||||
expect(config.persistInterval).toEqual(100);
|
expect(config.persistInterval).toEqual(100);
|
||||||
|
@ -62,7 +62,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(domain);
|
expect(config.rendererBaseUrl).toEqual(domain);
|
||||||
expect(config.port).toEqual(port);
|
expect(config.port).toEqual(port);
|
||||||
expect(config.loglevel).toEqual(loglevel);
|
expect(config.loglevel).toEqual(loglevel);
|
||||||
expect(config.persistInterval).toEqual(100);
|
expect(config.persistInterval).toEqual(100);
|
||||||
|
@ -74,7 +74,7 @@ describe('appConfig', () => {
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_DOMAIN: domain,
|
HD_DOMAIN: domain,
|
||||||
HD_RENDERER_ORIGIN: rendererOrigin,
|
HD_RENDERER_BASE_URL: rendererBaseUrl,
|
||||||
HD_LOGLEVEL: loglevel,
|
HD_LOGLEVEL: loglevel,
|
||||||
HD_PERSIST_INTERVAL: '100',
|
HD_PERSIST_INTERVAL: '100',
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
@ -85,7 +85,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(rendererOrigin);
|
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
|
||||||
expect(config.port).toEqual(3000);
|
expect(config.port).toEqual(3000);
|
||||||
expect(config.loglevel).toEqual(loglevel);
|
expect(config.loglevel).toEqual(loglevel);
|
||||||
expect(config.persistInterval).toEqual(100);
|
expect(config.persistInterval).toEqual(100);
|
||||||
|
@ -97,7 +97,7 @@ describe('appConfig', () => {
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_DOMAIN: domain,
|
HD_DOMAIN: domain,
|
||||||
HD_RENDERER_ORIGIN: rendererOrigin,
|
HD_RENDERER_BASE_URL: rendererBaseUrl,
|
||||||
PORT: port.toString(),
|
PORT: port.toString(),
|
||||||
HD_PERSIST_INTERVAL: '100',
|
HD_PERSIST_INTERVAL: '100',
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
@ -108,7 +108,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(rendererOrigin);
|
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
|
||||||
expect(config.port).toEqual(port);
|
expect(config.port).toEqual(port);
|
||||||
expect(config.loglevel).toEqual(Loglevel.WARN);
|
expect(config.loglevel).toEqual(Loglevel.WARN);
|
||||||
expect(config.persistInterval).toEqual(100);
|
expect(config.persistInterval).toEqual(100);
|
||||||
|
@ -120,7 +120,7 @@ describe('appConfig', () => {
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_DOMAIN: domain,
|
HD_DOMAIN: domain,
|
||||||
HD_RENDERER_ORIGIN: rendererOrigin,
|
HD_RENDERER_BASE_URL: rendererBaseUrl,
|
||||||
HD_LOGLEVEL: loglevel,
|
HD_LOGLEVEL: loglevel,
|
||||||
PORT: port.toString(),
|
PORT: port.toString(),
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
@ -131,7 +131,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(rendererOrigin);
|
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
|
||||||
expect(config.port).toEqual(port);
|
expect(config.port).toEqual(port);
|
||||||
expect(config.loglevel).toEqual(Loglevel.TRACE);
|
expect(config.loglevel).toEqual(Loglevel.TRACE);
|
||||||
expect(config.persistInterval).toEqual(10);
|
expect(config.persistInterval).toEqual(10);
|
||||||
|
@ -143,7 +143,7 @@ describe('appConfig', () => {
|
||||||
{
|
{
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
HD_DOMAIN: domain,
|
HD_DOMAIN: domain,
|
||||||
HD_RENDERER_ORIGIN: rendererOrigin,
|
HD_RENDERER_BASE_URL: rendererBaseUrl,
|
||||||
HD_LOGLEVEL: loglevel,
|
HD_LOGLEVEL: loglevel,
|
||||||
PORT: port.toString(),
|
PORT: port.toString(),
|
||||||
HD_PERSIST_INTERVAL: '0',
|
HD_PERSIST_INTERVAL: '0',
|
||||||
|
@ -155,7 +155,7 @@ describe('appConfig', () => {
|
||||||
);
|
);
|
||||||
const config = appConfig();
|
const config = appConfig();
|
||||||
expect(config.domain).toEqual(domain);
|
expect(config.domain).toEqual(domain);
|
||||||
expect(config.rendererOrigin).toEqual(rendererOrigin);
|
expect(config.rendererBaseUrl).toEqual(rendererBaseUrl);
|
||||||
expect(config.port).toEqual(port);
|
expect(config.port).toEqual(port);
|
||||||
expect(config.loglevel).toEqual(Loglevel.TRACE);
|
expect(config.loglevel).toEqual(Loglevel.TRACE);
|
||||||
expect(config.persistInterval).toEqual(0);
|
expect(config.persistInterval).toEqual(0);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { buildErrorMessage, parseOptionalNumber } from './utils';
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
domain: string;
|
domain: string;
|
||||||
rendererOrigin: string;
|
rendererBaseUrl: string;
|
||||||
port: number;
|
port: number;
|
||||||
loglevel: Loglevel;
|
loglevel: Loglevel;
|
||||||
persistInterval: number;
|
persistInterval: number;
|
||||||
|
@ -23,13 +23,13 @@ const schema = Joi.object({
|
||||||
scheme: /https?/,
|
scheme: /https?/,
|
||||||
})
|
})
|
||||||
.label('HD_DOMAIN'),
|
.label('HD_DOMAIN'),
|
||||||
rendererOrigin: Joi.string()
|
rendererBaseUrl: Joi.string()
|
||||||
.uri({
|
.uri({
|
||||||
scheme: /https?/,
|
scheme: /https?/,
|
||||||
})
|
})
|
||||||
.default(Joi.ref('domain'))
|
.default(Joi.ref('domain'))
|
||||||
.optional()
|
.optional()
|
||||||
.label('HD_RENDERER_ORIGIN'),
|
.label('HD_RENDERER_BASE_URL'),
|
||||||
port: Joi.number()
|
port: Joi.number()
|
||||||
.positive()
|
.positive()
|
||||||
.integer()
|
.integer()
|
||||||
|
@ -54,7 +54,7 @@ export default registerAs('appConfig', () => {
|
||||||
const appConfig = schema.validate(
|
const appConfig = schema.validate(
|
||||||
{
|
{
|
||||||
domain: process.env.HD_DOMAIN,
|
domain: process.env.HD_DOMAIN,
|
||||||
rendererOrigin: process.env.HD_RENDERER_ORIGIN,
|
rendererBaseUrl: process.env.HD_RENDERER_BASE_URL,
|
||||||
port: parseOptionalNumber(process.env.PORT),
|
port: parseOptionalNumber(process.env.PORT),
|
||||||
loglevel: process.env.HD_LOGLEVEL,
|
loglevel: process.env.HD_LOGLEVEL,
|
||||||
persistInterval: process.env.HD_PERSIST_INTERVAL,
|
persistInterval: process.env.HD_PERSIST_INTERVAL,
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default registerAs(
|
||||||
'appConfig',
|
'appConfig',
|
||||||
(): AppConfig => ({
|
(): AppConfig => ({
|
||||||
domain: 'md.example.com',
|
domain: 'md.example.com',
|
||||||
rendererOrigin: 'md-renderer.example.com',
|
rendererBaseUrl: 'md-renderer.example.com',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
loglevel: Loglevel.ERROR,
|
loglevel: Loglevel.ERROR,
|
||||||
persistInterval: 10,
|
persistInterval: 10,
|
||||||
|
|
|
@ -123,22 +123,6 @@ export class SpecialUrlsDto extends BaseDto {
|
||||||
imprint?: URL;
|
imprint?: URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IframeCommunicationDto extends BaseDto {
|
|
||||||
/**
|
|
||||||
* The origin under which the editor page will be served
|
|
||||||
* @example https://md.example.com
|
|
||||||
*/
|
|
||||||
@IsUrl()
|
|
||||||
editorOrigin: URL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The origin under which the renderer page will be served
|
|
||||||
* @example https://md-renderer.example.com
|
|
||||||
*/
|
|
||||||
@IsUrl()
|
|
||||||
rendererOrigin: URL;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FrontendConfigDto extends BaseDto {
|
export class FrontendConfigDto extends BaseDto {
|
||||||
/**
|
/**
|
||||||
* Maximum access level for guest users
|
* Maximum access level for guest users
|
||||||
|
@ -195,12 +179,4 @@ export class FrontendConfigDto extends BaseDto {
|
||||||
*/
|
*/
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
maxDocumentLength: number;
|
maxDocumentLength: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* The frontend capsules the markdown rendering into a secured iframe, to increase the security. The browser will treat the iframe target as cross-origin even if they are on the same domain.
|
|
||||||
* You can go even one step further and serve the editor and the renderer on different (sub)domains to eliminate even more attack vectors by making sessions, cookies, etc. not available for the renderer, because they aren't set on the renderer origin.
|
|
||||||
* However, The editor and the renderer need to know the other's origin to communicate with each other, even if they are the same.
|
|
||||||
*/
|
|
||||||
@ValidateNested()
|
|
||||||
iframeCommunication: IframeCommunicationDto;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,7 +168,7 @@ describe('FrontendConfigService', () => {
|
||||||
it(`works with ${JSON.stringify(authConfigConfigured)}`, async () => {
|
it(`works with ${JSON.stringify(authConfigConfigured)}`, async () => {
|
||||||
const appConfig: AppConfig = {
|
const appConfig: AppConfig = {
|
||||||
domain: domain,
|
domain: domain,
|
||||||
rendererOrigin: domain,
|
rendererBaseUrl: 'https://renderer.example.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
loglevel: Loglevel.ERROR,
|
loglevel: Loglevel.ERROR,
|
||||||
persistInterval: 10,
|
persistInterval: 10,
|
||||||
|
@ -315,14 +315,10 @@ describe('FrontendConfigService', () => {
|
||||||
const customName = 'Test Branding Name';
|
const customName = 'Test Branding Name';
|
||||||
|
|
||||||
let index = 1;
|
let index = 1;
|
||||||
for (const renderOrigin of [undefined, 'http://md-renderer.example.com']) {
|
|
||||||
for (const customLogo of [undefined, 'https://example.com/logo.png']) {
|
for (const customLogo of [undefined, 'https://example.com/logo.png']) {
|
||||||
for (const privacyLink of [undefined, 'https://example.com/privacy']) {
|
for (const privacyLink of [undefined, 'https://example.com/privacy']) {
|
||||||
for (const termsOfUseLink of [undefined, 'https://example.com/terms']) {
|
for (const termsOfUseLink of [undefined, 'https://example.com/terms']) {
|
||||||
for (const imprintLink of [
|
for (const imprintLink of [undefined, 'https://example.com/imprint']) {
|
||||||
undefined,
|
|
||||||
'https://example.com/imprint',
|
|
||||||
]) {
|
|
||||||
for (const plantUmlServer of [
|
for (const plantUmlServer of [
|
||||||
undefined,
|
undefined,
|
||||||
'https://plantuml.example.com',
|
'https://plantuml.example.com',
|
||||||
|
@ -330,7 +326,7 @@ describe('FrontendConfigService', () => {
|
||||||
it(`combination #${index} works`, async () => {
|
it(`combination #${index} works`, async () => {
|
||||||
const appConfig: AppConfig = {
|
const appConfig: AppConfig = {
|
||||||
domain: domain,
|
domain: domain,
|
||||||
rendererOrigin: renderOrigin ?? domain,
|
rendererBaseUrl: 'https://renderer.example.org',
|
||||||
port: 3000,
|
port: 3000,
|
||||||
loglevel: Loglevel.ERROR,
|
loglevel: Loglevel.ERROR,
|
||||||
persistInterval: 10,
|
persistInterval: 10,
|
||||||
|
@ -400,14 +396,6 @@ describe('FrontendConfigService', () => {
|
||||||
expect(config.branding.logo).toEqual(
|
expect(config.branding.logo).toEqual(
|
||||||
customLogo ? new URL(customLogo) : undefined,
|
customLogo ? new URL(customLogo) : undefined,
|
||||||
);
|
);
|
||||||
expect(config.iframeCommunication.editorOrigin).toEqual(
|
|
||||||
new URL(appConfig.domain),
|
|
||||||
);
|
|
||||||
expect(config.iframeCommunication.rendererOrigin).toEqual(
|
|
||||||
appConfig.rendererOrigin
|
|
||||||
? new URL(appConfig.rendererOrigin)
|
|
||||||
: new URL(appConfig.domain),
|
|
||||||
);
|
|
||||||
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
expect(config.maxDocumentLength).toEqual(maxDocumentLength);
|
||||||
expect(config.plantUmlServer).toEqual(
|
expect(config.plantUmlServer).toEqual(
|
||||||
plantUmlServer ? new URL(plantUmlServer) : undefined,
|
plantUmlServer ? new URL(plantUmlServer) : undefined,
|
||||||
|
@ -432,5 +420,4 @@ describe('FrontendConfigService', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
AuthProviderType,
|
AuthProviderType,
|
||||||
BrandingDto,
|
BrandingDto,
|
||||||
FrontendConfigDto,
|
FrontendConfigDto,
|
||||||
IframeCommunicationDto,
|
|
||||||
SpecialUrlsDto,
|
SpecialUrlsDto,
|
||||||
} from './frontend-config.dto';
|
} from './frontend-config.dto';
|
||||||
|
|
||||||
|
@ -50,7 +49,6 @@ export class FrontendConfigService {
|
||||||
allowRegister: this.authConfig.local.enableRegister,
|
allowRegister: this.authConfig.local.enableRegister,
|
||||||
authProviders: this.getAuthProviders(),
|
authProviders: this.getAuthProviders(),
|
||||||
branding: this.getBranding(),
|
branding: this.getBranding(),
|
||||||
iframeCommunication: this.getIframeCommunication(),
|
|
||||||
maxDocumentLength: this.noteConfig.maxDocumentLength,
|
maxDocumentLength: this.noteConfig.maxDocumentLength,
|
||||||
plantUmlServer: this.externalServicesConfig.plantUmlServer
|
plantUmlServer: this.externalServicesConfig.plantUmlServer
|
||||||
? new URL(this.externalServicesConfig.plantUmlServer)
|
? new URL(this.externalServicesConfig.plantUmlServer)
|
||||||
|
@ -146,11 +144,4 @@ export class FrontendConfigService {
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getIframeCommunication(): IframeCommunicationDto {
|
|
||||||
return {
|
|
||||||
editorOrigin: new URL(this.appConfig.domain),
|
|
||||||
rendererOrigin: new URL(this.appConfig.rendererOrigin),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue