From 286575315e85a716cdd7c22025545fe453f69883 Mon Sep 17 00:00:00 2001 From: Philip Molares Date: Thu, 31 Dec 2020 22:37:37 +0100 Subject: [PATCH] Extend config with various options from 1.x Signed-off-by: Philip Molares --- src/config/app.config.ts | 123 ++++++--- src/config/auth-config.ts | 331 ++++++++++++++++++++++++ src/config/csp-config.ts | 24 ++ src/config/database-config.ts | 42 +++ src/config/database-dialect.enum.ts | 12 + src/config/gitlab.enum.ts | 16 ++ src/config/hsts-config.ts | 30 +++ src/config/linkify-header-style.enum.ts | 11 + src/config/loglevel.enum.ts | 13 + src/config/media-config.ts | 93 +++++++ src/config/utils.ts | 17 ++ src/media/backends/backend-type.enum.ts | 2 +- src/media/media.service.ts | 4 +- 13 files changed, 686 insertions(+), 32 deletions(-) create mode 100644 src/config/auth-config.ts create mode 100644 src/config/csp-config.ts create mode 100644 src/config/database-config.ts create mode 100644 src/config/database-dialect.enum.ts create mode 100644 src/config/gitlab.enum.ts create mode 100644 src/config/hsts-config.ts create mode 100644 src/config/linkify-header-style.enum.ts create mode 100644 src/config/loglevel.enum.ts create mode 100644 src/config/media-config.ts create mode 100644 src/config/utils.ts diff --git a/src/config/app.config.ts b/src/config/app.config.ts index 122fda6f9..bfd687325 100644 --- a/src/config/app.config.ts +++ b/src/config/app.config.ts @@ -6,47 +6,112 @@ import { registerAs } from '@nestjs/config'; import * as Joi from 'joi'; +import { Loglevel } from './loglevel.enum'; +import { appConfigHsts, HstsConfig, hstsSchema } from './hsts-config'; +import { appConfigCsp, CspConfig, cspSchema } from './csp-config'; +import { appConfigMedia, MediaConfig, mediaSchema } from './media-config'; +import { + appConfigDatabase, + DatabaseConfig, + databaseSchema, +} from './database-config'; +import { appConfigAuth, AuthConfig, authSchema } from './auth-config'; + +// import { LinkifyHeaderStyle } from './linkify-header-style'; export interface AppConfig { + domain: string; port: number; - media: { - backend: { - use: string; - filesystem: { - uploadPath: string; - }; - }; - }; + loglevel: Loglevel; + /*linkifyHeaderStyle: LinkifyHeaderStyle; + sourceURL: string; + urlPath: string; + host: string; + path: string; + urlAddPort: boolean; + cookiePolicy: string; + protocolUseSSL: boolean; + allowOrigin: string[]; + useCDN: boolean; + enableAnonymous: boolean; + enableAnonymousEdits: boolean; + enableFreeURL: boolean; + forbiddenNoteIDs: string[]; + defaultPermission: string; + sessionSecret: string; + sessionLife: number; + tooBusyLag: number; + enableGravatar: boolean;*/ + hsts: HstsConfig; + csp: CspConfig; + media: MediaConfig; + database: DatabaseConfig; + auth: AuthConfig; } const schema = Joi.object({ - port: Joi.number(), - media: { - backend: { - use: Joi.string().valid('filesystem'), - filesystem: { - uploadPath: Joi.when('...use', { - is: Joi.valid('filesystem'), - then: Joi.string(), - otherwise: Joi.optional(), - }), - }, - }, - }, + domain: Joi.string(), + port: Joi.number().default(3000).optional(), + loglevel: Joi.string() + .valid(...Object.values(Loglevel)) + .default(Loglevel.WARN) + .optional(), + /*linkifyHeaderStyle: Joi.string().valid(...Object.values(LinkifyHeaderStyle)).default(LinkifyHeaderStyle.GFM).optional(), + sourceURL: Joi.string(), + urlPath: Joi.string(), + host: Joi.string().default('::').optional(), + path: Joi.string(), + urlAddPort: Joi.boolean().default(false).optional(), + cookiePolicy: Joi.string(), + protocolUseSSL: Joi.boolean().default(true).optional(), + allowOrigin: Joi.array().items(Joi.string()), + useCDN: Joi.boolean().default(false).optional(), + enableAnonymous: Joi.boolean().default(true).optional(), + enableAnonymousEdits: Joi.boolean().default(false).optional(), + enableFreeURL: Joi.boolean().default(false).optional(), + forbiddenNoteIDs: Joi.array().items(Joi.string()), + defaultPermission: Joi.string(), + sessionSecret: Joi.string(), + sessionLife: Joi.number().default(14 * 24 * 60 * 60 * 1000).optional(), + tooBusyLag: Joi.number().default(70).optional(), + enableGravatar: Joi.boolean().default(true).optional(),*/ + hsts: hstsSchema, + csp: cspSchema, + media: mediaSchema, + database: databaseSchema, + auth: authSchema, }); export default registerAs('appConfig', async () => { const appConfig = schema.validate( { + domain: process.env.HD_DOMAIN, port: parseInt(process.env.PORT) || undefined, - media: { - backend: { - use: process.env.HD_MEDIA_BACKEND, - filesystem: { - uploadPath: process.env.HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH, - }, - }, - }, + loglevel: process.env.HD_LOGLEVEL, //|| Loglevel.WARN, + /*linkifyHeaderStyle: process.env.HD_LINKIFY_HEADER_STYLE, + sourceURL: process.env.HD_SOURCE_URL, + urlPath: process.env.HD_URL_PATH, + host: process.env.HD_HOST || '::', + path: process.env.HD_PATH, + urlAddPort: process.env.HD_URL_ADDPORT, + cookiePolicy: process.env.HD_COOKIE_POLICY, + protocolUseSSL: process.env.HD_PROTOCOL_USESSL || true, + allowOrigin: toArrayConfig(process.env.HD_ALLOW_ORIGIN), + useCDN: process.env.HD_USECDN, + enableAnonymous: process.env.HD_ENABLE_ANONYMOUS || true, + enableAnonymousEdits: process.env.HD_ENABLE_ANONYMOUS_EDITS, + enableFreeURL: process.env.HD_ENABLE_FREEURL, + forbiddenNoteIDs: toArrayConfig(process.env.HD_FORBIDDEN_NOTE_IDS), + defaultPermission: process.env.HD_DEFAULT_PERMISSION, + sessionSecret: process.env.HD_SESSION_SECRET, + sessionLife: parseInt(process.env.HD_SESSION_LIFE) || 14 * 24 * 60 * 60 * 1000, + tooBusyLag: parseInt(process.env.HD_TOOBUSY_LAG) || 70, + enableGravatar: process.env.HD_ENABLE_GRAVATAR || true,*/ + hsts: appConfigHsts, + csp: appConfigCsp, + media: appConfigMedia, + database: appConfigDatabase, + auth: appConfigAuth, }, { abortEarly: false, diff --git a/src/config/auth-config.ts b/src/config/auth-config.ts new file mode 100644 index 000000000..d8ab34fcd --- /dev/null +++ b/src/config/auth-config.ts @@ -0,0 +1,331 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Joi from 'joi'; +import { GitlabScope, GitlabVersion } from './gitlab.enum'; +import { toArrayConfig } from './utils'; + +export interface AuthConfig { + email: { + enableLogin: boolean; + enableRegister: boolean; + }; + facebook: { + clientID: string; + clientSecret: string; + }; + twitter: { + consumerKey: string; + consumerSecret: string; + }; + github: { + clientID: string; + clientSecret: string; + }; + dropbox: { + clientID: string; + clientSecret: string; + appKey: string; + }; + google: { + clientID: string; + clientSecret: string; + apiKey: string; + }; + gitlab: [ + { + providerName: string; + baseURL: string; + clientID: string; + clientSecret: string; + scope: GitlabScope; + version: GitlabVersion; + }, + ]; + // ToDo: tlsOptions exist in config.json.example. See https://nodejs.org/api/tls.html#tls_tls_connect_options_callback + ldap: [ + { + providerName: string; + url: string; + bindDn: string; + bindCredentials: string; + searchBase: string; + searchFilter: string; + searchAttributes: string[]; + usernameField: string; + useridField: string; + tlsCa: string[]; + }, + ]; + saml: [ + { + providerName: string; + idpSsoUrl: string; + idpCert: string; + clientCert: string; + issuer: string; + identifierFormat: string; + disableRequestedAuthnContext: string; + groupAttribute: string; + requiredGroups: string[]; + externalGroups: string; + attribute: { + id: string; + username: string; + email: string; + }; + }, + ]; + oauth2: [ + { + providerName: string; + baseURL: string; + userProfileURL: string; + userProfileIdAttr: string; + userProfileUsernameAttr: string; + userProfileDisplayNameAttr: string; + userProfileEmailAttr: string; + tokenURL: string; + authorizationURL: string; + clientID: string; + clientSecret: string; + scope: string; + rolesClaim: string; + accessRole: string; + }, + ]; +} + +export const authSchema = Joi.object({ + email: { + enableLogin: Joi.boolean().default(false).optional(), + enableRegister: Joi.boolean().default(false).optional(), + }, + facebook: { + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + }, + twitter: { + consumerKey: Joi.string().optional(), + consumerSecret: Joi.string().optional(), + }, + github: { + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + }, + dropbox: { + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + appKey: Joi.string().optional(), + }, + google: { + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + apiKey: Joi.string().optional(), + }, + gitlab: Joi.array() + .items( + Joi.object({ + providerName: Joi.string().default('Gitlab').optional(), + baseURL: Joi.string().optional(), + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + scope: Joi.string() + .valid(...Object.values(GitlabScope)) + .default(GitlabScope.READ_USER) + .optional(), + version: Joi.string() + .valid(...Object.values(GitlabVersion)) + .default(GitlabVersion.V4) + .optional(), + }), + ) + .optional(), + // ToDo: should searchfilter have a default? + ldap: Joi.array() + .items( + Joi.object({ + providerName: Joi.string().default('LDAP').optional(), + url: Joi.string().optional(), + bindDn: Joi.string().optional(), + bindCredentials: Joi.string().optional(), + searchBase: Joi.string().optional(), + searchFilter: Joi.string().default('(uid={{username}})').optional(), + searchAttributes: Joi.array().items(Joi.string()), + usernameField: Joi.string().default('userid').optional(), + useridField: Joi.string().optional(), + tlsCa: Joi.array().items(Joi.string()), + }), + ) + .optional(), + saml: Joi.array() + .items( + Joi.object({ + providerName: Joi.string().default('SAML').optional(), + idpSsoUrl: Joi.string().optional(), + idpCert: Joi.string().optional(), + clientCert: Joi.string().optional(), + // ToDo: (default: config.serverURL) will be build on-the-fly in the config/index.js from domain, urlAddPort and urlPath. + issuer: Joi.string().optional(), //.default().optional(), + identifierFormat: Joi.string() + .default('urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress') + .optional(), + disableRequestedAuthnContext: Joi.boolean().default(false).optional(), + groupAttribute: Joi.string().optional(), + requiredGroups: Joi.array().items(Joi.string()), + externalGroups: Joi.array().items(Joi.string()), + attribute: { + id: Joi.string().default('NameId').optional(), + username: Joi.string().default('NameId').optional(), + email: Joi.string().default('NameId').optional(), + }, + }), + ) + .optional(), + oauth2: Joi.array() + .items( + Joi.object({ + providerName: Joi.string().default('OAuth2').optional(), + baseURL: Joi.string().optional(), + userProfileURL: Joi.string().optional(), + userProfileIdAttr: Joi.string().optional(), + userProfileUsernameAttr: Joi.string().optional(), + userProfileDisplayNameAttr: Joi.string().optional(), + userProfileEmailAttr: Joi.string().optional(), + tokenURL: Joi.string().optional(), + authorizationURL: Joi.string().optional(), + clientID: Joi.string().optional(), + clientSecret: Joi.string().optional(), + scope: Joi.string().optional(), + rolesClaim: Joi.string().optional(), + accessRole: Joi.string().optional(), + }), + ) + .optional(), +}); + +// ToDo: Validate these with Joi to prevent duplicate entries? + +const gitlabNames = toArrayConfig(process.env.HD_AUTH_GITLABS, ','); +const ldapNames = toArrayConfig(process.env.HD_AUTH_LDAPS, ','); +const samlNames = toArrayConfig(process.env.HD_AUTH_SAMLS, ','); +const oauth2Names = toArrayConfig(process.env.HD_AUTH_OAUTH2S, ','); + +const gitlabs = gitlabNames.map((gitlabName) => { + return { + providerName: process.env[`HD_AUTH_GITLAB_${gitlabName}_PROVIDER_NAME`], + baseURL: process.env[`HD_AUTH_GITLAB_${gitlabName}_BASE_URL`], + clientID: process.env[`HD_AUTH_GITLAB_${gitlabName}_CLIENT_ID`], + clientSecret: process.env[`HD_AUTH_GITLAB_${gitlabName}_CLIENT_SECRET`], + scope: process.env[`HD_AUTH_GITLAB_${gitlabName}_GITLAB_SCOPE`], + version: process.env[`HD_AUTH_GITLAB_${gitlabName}_GITLAB_VERSION`], + }; +}); + +const ldaps = ldapNames.map((ldapName) => { + return { + providerName: process.env[`HD_AUTH_LDAP_${ldapName}_PROVIDER_NAME`], + url: process.env[`HD_AUTH_LDAP_${ldapName}_URL`], + bindDn: process.env[`HD_AUTH_LDAP_${ldapName}_BIND_DN`], + bindCredentials: process.env[`HD_AUTH_LDAP_${ldapName}_BIND_CREDENTIALS`], + searchBase: process.env[`HD_AUTH_LDAP_${ldapName}_SEARCH_BASE`], + searchFilter: process.env[`HD_AUTH_LDAP_${ldapName}_SEARCH_FILTER`], + searchAttributes: toArrayConfig( + process.env[`HD_AUTH_LDAP_${ldapName}_SEARCH_ATTRIBUTES`], + ',', + ), + usernameField: process.env[`HD_AUTH_LDAP_${ldapName}_USERNAME_FIELD`], + useridField: process.env[`HD_AUTH_LDAP_${ldapName}_USERID_FIELD`], + tlsCa: toArrayConfig(process.env[`HD_AUTH_LDAP_${ldapName}_TLS_CA`], ','), + }; +}); + +const samls = samlNames.map((samlName) => { + return { + providerName: process.env[`HD_AUTH_SAML_${samlName}_PROVIDER_NAME`], + idpSsoUrl: process.env[`HD_AUTH_SAML_${samlName}_IDPSSOURL`], + idpCert: process.env[`HD_AUTH_SAML_${samlName}_IDPCERT`], + clientCert: process.env[`HD_AUTH_SAML_${samlName}_CLIENTCERT`], + issuer: process.env[`HD_AUTH_SAML_${samlName}_ISSUER`], + identifierFormat: process.env[`HD_AUTH_SAML_${samlName}_IDENTIFIERFORMAT`], + disableRequestedAuthnContext: + process.env[`HD_AUTH_SAML_${samlName}_DISABLEREQUESTEDAUTHNCONTEXT`], + groupAttribute: process.env[`HD_AUTH_SAML_${samlName}_GROUPATTRIBUTE`], + requiredGroups: toArrayConfig( + process.env[`HD_AUTH_SAML_${samlName}_REQUIREDGROUPS`], + '|', + ), + externalGroups: toArrayConfig( + process.env[`HD_AUTH_SAML_${samlName}_EXTERNALGROUPS`], + '|', + ), + attribute: { + id: process.env[`HD_AUTH_SAML_${samlName}_ATTRIBUTE_ID`], + username: process.env[`HD_AUTH_SAML_${samlName}_ATTRIBUTE_USERNAME`], + email: process.env[`HD_AUTH_SAML_${samlName}_ATTRIBUTE_USERNAME`], + }, + }; +}); + +const oauth2s = oauth2Names.map((oauth2Name) => { + return { + providerName: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_PROVIDER_NAME`], + baseURL: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_BASEURL`], + userProfileURL: + process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_URL`], + userProfileIdAttr: + process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_ID_ATTR`], + userProfileUsernameAttr: + process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_USERNAME_ATTR`], + userProfileDisplayNameAttr: + process.env[ + `HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_DISPLAY_NAME_ATTR` + ], + userProfileEmailAttr: + process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_EMAIL_ATTR`], + tokenURL: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_TOKEN_URL`], + authorizationURL: + process.env[`HD_AUTH_OAUTH2_${oauth2Name}_AUTHORIZATION_URL`], + clientID: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_ID`], + clientSecret: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_SECRET`], + scope: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_SCOPE`], + rolesClaim: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_ROLES_CLAIM`], + accessRole: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_ACCESS_ROLE`], + }; +}); + +export const appConfigAuth = { + email: { + enableLogin: process.env.HD_AUTH_EMAIL_ENABLE_LOGIN, + enableRegister: process.env.HD_AUTH_EMAIL_ENABLE_REGISTER, + }, + facebook: { + clientID: process.env.HD_AUTH_FACEBOOK_CLIENT_ID, + clientSecret: process.env.HD_AUTH_FACEBOOK_CLIENT_SECRET, + }, + twitter: { + consumerKey: process.env.HD_AUTH_TWITTER_CONSUMER_KEY, + consumerSecret: process.env.HD_AUTH_TWITTER_CONSUMER_SECRET, + }, + github: { + clientID: process.env.HD_AUTH_GITHUB_CLIENT_ID, + clientSecret: process.env.HD_AUTH_GITHUB_CLIENT_SECRET, + }, + dropbox: { + clientID: process.env.HD_AUTH_DROPBOX_CLIENT_ID, + clientSecret: process.env.HD_AUTH_DROPBOX_CLIENT_SECRET, + appKey: process.env.HD_AUTH_DROPBOX_APP_KEY, + }, + google: { + clientID: process.env.HD_AUTH_GOOGLE_CLIENT_ID, + clientSecret: process.env.HD_AUTH_GOOGLE_CLIENT_SECRET, + apiKey: process.env.HD_AUTH_GOOGLE_APP_KEY, + }, + gitlab: gitlabs, + ldap: ldaps, + saml: samls, + oauth2: oauth2s, +}; diff --git a/src/config/csp-config.ts b/src/config/csp-config.ts new file mode 100644 index 000000000..ab699673a --- /dev/null +++ b/src/config/csp-config.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Joi from 'joi'; + +export interface CspConfig { + enable: boolean; + maxAgeSeconds: number; + includeSubdomains: boolean; + preload: boolean; +} + +export const cspSchema = Joi.object({ + enable: Joi.boolean().default(true).optional(), + reportURI: Joi.string().optional(), +}); + +export const appConfigCsp = { + enable: process.env.HD_CSP_ENABLE || true, + reportURI: process.env.HD_CSP_REPORTURI, +}; diff --git a/src/config/database-config.ts b/src/config/database-config.ts new file mode 100644 index 000000000..59397321e --- /dev/null +++ b/src/config/database-config.ts @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Joi from 'joi'; +import { DatabaseDialect } from './database-dialect.enum'; + +export interface DatabaseConfig { + username: string; + password: string; + database: string; + host: string; + port: number; + storage: string; + dialect: DatabaseDialect; +} + +export const databaseSchema = Joi.object({ + username: Joi.string(), + password: Joi.string(), + database: Joi.string(), + host: Joi.string(), + port: Joi.number(), + storage: Joi.when('...dialect', { + is: Joi.valid(DatabaseDialect.SQLITE), + then: Joi.string(), + otherwise: Joi.optional(), + }), + dialect: Joi.string().valid(...Object.values(DatabaseDialect)), +}); + +export const appConfigDatabase = { + username: process.env.HD_DATABASE_USER, + password: process.env.HD_DATABASE_PASS, + database: process.env.HD_DATABASE_NAME, + host: process.env.HD_DATABASE_HOST, + port: parseInt(process.env.HD_DATABASE_PORT) || undefined, + storage: process.env.HD_DATABASE_STORAGE, + dialect: process.env.HD_DATABASE_DIALECT, +}; diff --git a/src/config/database-dialect.enum.ts b/src/config/database-dialect.enum.ts new file mode 100644 index 000000000..a629edad7 --- /dev/null +++ b/src/config/database-dialect.enum.ts @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export enum DatabaseDialect { + POSTGRES = 'postgres', + MYSQL = 'mysql', + MARIADB = 'mariadb', + SQLITE = 'sqlite', +} diff --git a/src/config/gitlab.enum.ts b/src/config/gitlab.enum.ts new file mode 100644 index 000000000..0ddb24130 --- /dev/null +++ b/src/config/gitlab.enum.ts @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export enum GitlabScope { + READ_USER = 'read_user', + API = 'api', +} + +// ToDo: Evaluate if V3 is really necessary anymore (it's deprecated since 2017) +export enum GitlabVersion { + V3 = 'v3', + V4 = 'v4', +} diff --git a/src/config/hsts-config.ts b/src/config/hsts-config.ts new file mode 100644 index 000000000..a15ccd32b --- /dev/null +++ b/src/config/hsts-config.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Joi from 'joi'; + +export interface HstsConfig { + enable: boolean; + maxAgeSeconds: number; + includeSubdomains: boolean; + preload: boolean; +} + +export const hstsSchema = Joi.object({ + enable: Joi.boolean().default(true).optional(), + maxAgeSeconds: Joi.number() + .default(60 * 60 * 24 * 365) + .optional(), + includeSubdomains: Joi.boolean().default(true).optional(), + preload: Joi.boolean().default(true).optional(), +}); + +export const appConfigHsts = { + enable: process.env.HD_HSTS_ENABLE, + maxAgeSeconds: parseInt(process.env.HD_HSTS_MAX_AGE) || undefined, + includeSubdomains: process.env.HD_HSTS_INCLUDE_SUBDOMAINS, + preload: process.env.HD_HSTS_PRELOAD, +}; diff --git a/src/config/linkify-header-style.enum.ts b/src/config/linkify-header-style.enum.ts new file mode 100644 index 000000000..7c7ab088c --- /dev/null +++ b/src/config/linkify-header-style.enum.ts @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export enum LinkifyHeaderStyle { + KEEP_CASE = 'keep-case', + LOWER_CASE = 'lower-case', + GFM = 'gfm', +} diff --git a/src/config/loglevel.enum.ts b/src/config/loglevel.enum.ts new file mode 100644 index 000000000..f150c6fab --- /dev/null +++ b/src/config/loglevel.enum.ts @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export enum Loglevel { + TRACE = 'trace', + DEBUG = 'debug', + INFO = 'info', + WARN = 'warn', + ERROR = 'error', +} diff --git a/src/config/media-config.ts b/src/config/media-config.ts new file mode 100644 index 000000000..d94d86def --- /dev/null +++ b/src/config/media-config.ts @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Joi from 'joi'; +import { BackendType } from '../media/backends/backend-type.enum'; + +export interface MediaConfig { + backend: { + use: BackendType; + filesystem: { + uploadPath: string; + }; + s3: { + accessKeyId: string; + secretAccessKey: string; + region: string; + bucket: string; + endPoint: string; + }; + azure: { + connectionString: string; + container: string; + }; + imgur: { + clientID: string; + }; + }; +} + +export const mediaSchema = Joi.object({ + backend: { + use: Joi.string().valid(...Object.values(BackendType)), + filesystem: { + uploadPath: Joi.when('...use', { + is: Joi.valid(BackendType.FILESYSTEM), + then: Joi.string(), + otherwise: Joi.optional(), + }), + }, + s3: Joi.when('...use', { + is: Joi.valid(BackendType.S3), + then: Joi.object({ + accessKey: Joi.string(), + secretKey: Joi.string(), + endPoint: Joi.string(), + secure: Joi.boolean(), + port: Joi.number(), + }), + otherwise: Joi.optional(), + }), + azure: Joi.when('...use', { + is: Joi.valid(BackendType.AZURE), + then: Joi.object({ + connectionString: Joi.string(), + container: Joi.string(), + }), + otherwise: Joi.optional(), + }), + imgur: Joi.when('...use', { + is: Joi.valid(BackendType.IMGUR), + then: Joi.object({ + clientID: Joi.string(), + }), + otherwise: Joi.optional(), + }), + }, +}); + +export const appConfigMedia = { + backend: { + use: process.env.HD_MEDIA_BACKEND, + filesystem: { + uploadPath: process.env.HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH, + }, + s3: { + accessKey: process.env.HD_MEDIA_BACKEND_S3_ACCESS_KEY, + secretKey: process.env.HD_MEDIA_BACKEND_S3_ACCESS_KEY, + endPoint: process.env.HD_MEDIA_BACKEND_S3_ENDPOINT, + secure: process.env.HD_MEDIA_BACKEND_S3_SECURE, + port: parseInt(process.env.HD_MEDIA_BACKEND_S3_PORT) || undefined, + }, + azure: { + connectionString: process.env.HD_MEDIA_BACKEND_AZURE_CONNECTION_STRING, + container: process.env.HD_MEDIA_BACKEND_AZURE_CONTAINER, + }, + imgur: { + clientID: process.env.HD_MEDIA_BACKEND_IMGUR_CLIENTID, + }, + }, +}; diff --git a/src/config/utils.ts b/src/config/utils.ts new file mode 100644 index 000000000..168a0e532 --- /dev/null +++ b/src/config/utils.ts @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const toArrayConfig = (configValue: string, separator = ',') => { + if (!configValue) { + return []; + } + + if (!configValue.includes(separator)) { + return [configValue.trim()]; + } + + return configValue.split(separator).map((arrayItem) => arrayItem.trim()); +}; diff --git a/src/media/backends/backend-type.enum.ts b/src/media/backends/backend-type.enum.ts index 92808b4cc..90407e2c9 100644 --- a/src/media/backends/backend-type.enum.ts +++ b/src/media/backends/backend-type.enum.ts @@ -5,7 +5,7 @@ */ export enum BackendType { - FILEYSTEM = 'filesystem', + FILESYSTEM = 'filesystem', S3 = 's3', IMGUR = 'imgur', AZURE = 'azure', diff --git a/src/media/media.service.ts b/src/media/media.service.ts index f79800475..e3c449095 100644 --- a/src/media/media.service.ts +++ b/src/media/media.service.ts @@ -122,13 +122,13 @@ export class MediaService { private chooseBackendType(): BackendType { switch (this.appConfig.media.backend.use) { case 'filesystem': - return BackendType.FILEYSTEM; + return BackendType.FILESYSTEM; } } private getBackendFromType(type: BackendType): MediaBackend { switch (type) { - case BackendType.FILEYSTEM: + case BackendType.FILESYSTEM: return this.moduleRef.get(FilesystemBackend); } }