mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-22 17:56:30 -05:00
config: Improve error messages
Add labels to most Joi objects Convert all auth variable insert names to upper case to prevent inconsistent naming of the variables Rewrite auth errors to correctly point out the problematic variable Add tests for the config utils functions Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
4afc75912a
commit
bc525633fc
8 changed files with 372 additions and 168 deletions
|
@ -7,6 +7,7 @@
|
|||
import { registerAs } from '@nestjs/config';
|
||||
import * as Joi from 'joi';
|
||||
import { Loglevel } from './loglevel.enum';
|
||||
import { buildErrorMessage } from './utils';
|
||||
|
||||
export interface AppConfig {
|
||||
domain: string;
|
||||
|
@ -15,12 +16,13 @@ export interface AppConfig {
|
|||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
domain: Joi.string(),
|
||||
port: Joi.number().default(3000).optional(),
|
||||
domain: Joi.string().label('HD_DOMAIN'),
|
||||
port: Joi.number().default(3000).optional().label('PORT'),
|
||||
loglevel: Joi.string()
|
||||
.valid(...Object.values(Loglevel))
|
||||
.default(Loglevel.WARN)
|
||||
.optional(),
|
||||
.optional()
|
||||
.label('HD_LOGLEVEL'),
|
||||
});
|
||||
|
||||
export default registerAs('appConfig', async () => {
|
||||
|
@ -36,7 +38,10 @@ export default registerAs('appConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (appConfig.error) {
|
||||
throw new Error(appConfig.error.toString());
|
||||
const errorMessages = await appConfig.error.details.map(
|
||||
(detail) => detail.message,
|
||||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return appConfig.value;
|
||||
});
|
||||
|
|
|
@ -6,7 +6,11 @@
|
|||
|
||||
import * as Joi from 'joi';
|
||||
import { GitlabScope, GitlabVersion } from './gitlab.enum';
|
||||
import { toArrayConfig } from './utils';
|
||||
import {
|
||||
buildErrorMessage,
|
||||
replaceAuthErrorsWithEnvironmentVariables,
|
||||
toArrayConfig,
|
||||
} from './utils';
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export interface AuthConfig {
|
||||
|
@ -102,38 +106,50 @@ export interface AuthConfig {
|
|||
|
||||
const authSchema = Joi.object({
|
||||
email: {
|
||||
enableLogin: Joi.boolean().default(false).optional(),
|
||||
enableRegister: Joi.boolean().default(false).optional(),
|
||||
enableLogin: Joi.boolean()
|
||||
.default(false)
|
||||
.optional()
|
||||
.label('HD_AUTH_EMAIL_ENABLE_LOGIN'),
|
||||
enableRegister: Joi.boolean()
|
||||
.default(false)
|
||||
.optional()
|
||||
.label('HD_AUTH_EMAIL_ENABLE_REGISTER'),
|
||||
},
|
||||
facebook: {
|
||||
clientID: Joi.string().optional(),
|
||||
clientSecret: Joi.string().optional(),
|
||||
clientID: Joi.string().optional().label('HD_AUTH_FACEBOOK_CLIENT_ID'),
|
||||
clientSecret: Joi.string()
|
||||
.optional()
|
||||
.label('HD_AUTH_FACEBOOK_CLIENT_SECRET'),
|
||||
},
|
||||
twitter: {
|
||||
consumerKey: Joi.string().optional(),
|
||||
consumerSecret: Joi.string().optional(),
|
||||
consumerKey: Joi.string().optional().label('HD_AUTH_TWITTER_CONSUMER_KEY'),
|
||||
consumerSecret: Joi.string()
|
||||
.optional()
|
||||
.label('HD_AUTH_TWITTER_CONSUMER_SECRET'),
|
||||
},
|
||||
github: {
|
||||
clientID: Joi.string().optional(),
|
||||
clientSecret: Joi.string().optional(),
|
||||
clientID: Joi.string().optional().label('HD_AUTH_GITHUB_CLIENT_ID'),
|
||||
clientSecret: Joi.string().optional().label('HD_AUTH_GITHUB_CLIENT_SECRET'),
|
||||
},
|
||||
dropbox: {
|
||||
clientID: Joi.string().optional(),
|
||||
clientSecret: Joi.string().optional(),
|
||||
appKey: Joi.string().optional(),
|
||||
clientID: Joi.string().optional().label('HD_AUTH_DROPBOX_CLIENT_ID'),
|
||||
clientSecret: Joi.string()
|
||||
.optional()
|
||||
.label('HD_AUTH_DROPBOX_CLIENT_SECRET'),
|
||||
appKey: Joi.string().optional().label('HD_AUTH_DROPBOX_APP_KEY'),
|
||||
},
|
||||
google: {
|
||||
clientID: Joi.string().optional(),
|
||||
clientSecret: Joi.string().optional(),
|
||||
apiKey: Joi.string().optional(),
|
||||
clientID: Joi.string().optional().label('HD_AUTH_GOOGLE_CLIENT_ID'),
|
||||
clientSecret: Joi.string().optional().label('HD_AUTH_GOOGLE_CLIENT_SECRET'),
|
||||
apiKey: Joi.string().optional().label('HD_AUTH_GOOGLE_APP_KEY'),
|
||||
},
|
||||
gitlab: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
providerName: Joi.string().default('Gitlab').optional(),
|
||||
baseURL: Joi.string().optional(),
|
||||
clientID: Joi.string().optional(),
|
||||
clientSecret: Joi.string().optional(),
|
||||
baseURL: Joi.string(),
|
||||
clientID: Joi.string(),
|
||||
clientSecret: Joi.string(),
|
||||
scope: Joi.string()
|
||||
.valid(...Object.values(GitlabScope))
|
||||
.default(GitlabScope.READ_USER)
|
||||
|
@ -142,7 +158,7 @@ const authSchema = Joi.object({
|
|||
.valid(...Object.values(GitlabVersion))
|
||||
.default(GitlabVersion.V4)
|
||||
.optional(),
|
||||
}),
|
||||
}).optional(),
|
||||
)
|
||||
.optional(),
|
||||
// ToDo: should searchfilter have a default?
|
||||
|
@ -150,83 +166,96 @@ const authSchema = Joi.object({
|
|||
.items(
|
||||
Joi.object({
|
||||
providerName: Joi.string().default('LDAP').optional(),
|
||||
url: Joi.string().optional(),
|
||||
url: Joi.string(),
|
||||
bindDn: Joi.string().optional(),
|
||||
bindCredentials: Joi.string().optional(),
|
||||
searchBase: Joi.string().optional(),
|
||||
searchBase: Joi.string(),
|
||||
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()),
|
||||
}),
|
||||
searchAttributes: Joi.array()
|
||||
.items(Joi.string())
|
||||
.default(['displayName', 'mail'])
|
||||
.optional(),
|
||||
usernameField: Joi.string().optional(),
|
||||
useridField: Joi.string(),
|
||||
tlsCa: Joi.array().items(Joi.string()).optional(),
|
||||
}).optional(),
|
||||
)
|
||||
.optional(),
|
||||
saml: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
providerName: Joi.string().default('SAML').optional(),
|
||||
idpSsoUrl: Joi.string().optional(),
|
||||
idpCert: Joi.string().optional(),
|
||||
idpSsoUrl: Joi.string(),
|
||||
idpCert: Joi.string(),
|
||||
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(),
|
||||
issuer: Joi.string().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()),
|
||||
requiredGroups: Joi.array().items(Joi.string()).optional(),
|
||||
externalGroups: Joi.array().items(Joi.string()).optional(),
|
||||
attribute: {
|
||||
id: Joi.string().default('NameId').optional(),
|
||||
username: Joi.string().default('NameId').optional(),
|
||||
email: Joi.string().default('NameId').optional(),
|
||||
},
|
||||
}),
|
||||
}).optional(),
|
||||
)
|
||||
.optional(),
|
||||
oauth2: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
providerName: Joi.string().default('OAuth2').optional(),
|
||||
baseURL: Joi.string().optional(),
|
||||
userProfileURL: Joi.string().optional(),
|
||||
baseURL: Joi.string(),
|
||||
userProfileURL: Joi.string(),
|
||||
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(),
|
||||
userProfileUsernameAttr: Joi.string(),
|
||||
userProfileDisplayNameAttr: Joi.string(),
|
||||
userProfileEmailAttr: Joi.string(),
|
||||
tokenURL: Joi.string(),
|
||||
authorizationURL: Joi.string(),
|
||||
clientID: Joi.string(),
|
||||
clientSecret: Joi.string(),
|
||||
scope: Joi.string().optional(),
|
||||
rolesClaim: Joi.string().optional(),
|
||||
accessRole: Joi.string().optional(),
|
||||
}),
|
||||
}).optional(),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// ToDo: Validate these with Joi to prevent duplicate entries?
|
||||
export default registerAs('authConfig', async () => {
|
||||
// ToDo: Validate these with Joi to prevent duplicate entries?
|
||||
const gitlabNames = toArrayConfig(
|
||||
process.env.HD_AUTH_GITLABS,
|
||||
',',
|
||||
).map((name) => name.toUpperCase());
|
||||
const ldapNames = toArrayConfig(process.env.HD_AUTH_LDAPS, ',').map((name) =>
|
||||
name.toUpperCase(),
|
||||
);
|
||||
const samlNames = toArrayConfig(process.env.HD_AUTH_SAMLS, ',').map((name) =>
|
||||
name.toUpperCase(),
|
||||
);
|
||||
const oauth2Names = toArrayConfig(
|
||||
process.env.HD_AUTH_OAUTH2S,
|
||||
',',
|
||||
).map((name) => name.toUpperCase());
|
||||
|
||||
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) => {
|
||||
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`],
|
||||
scope: process.env[`HD_AUTH_GITLAB_${gitlabName}_SCOPE`],
|
||||
version: process.env[`HD_AUTH_GITLAB_${gitlabName}_GITLAB_VERSION`],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const ldaps = ldapNames.map((ldapName) => {
|
||||
const ldaps = ldapNames.map((ldapName) => {
|
||||
return {
|
||||
providerName: process.env[`HD_AUTH_LDAP_${ldapName}_PROVIDER_NAME`],
|
||||
url: process.env[`HD_AUTH_LDAP_${ldapName}_URL`],
|
||||
|
@ -242,16 +271,17 @@ const ldaps = ldapNames.map((ldapName) => {
|
|||
useridField: process.env[`HD_AUTH_LDAP_${ldapName}_USERID_FIELD`],
|
||||
tlsCa: toArrayConfig(process.env[`HD_AUTH_LDAP_${ldapName}_TLS_CA`], ','),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const samls = samlNames.map((samlName) => {
|
||||
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`],
|
||||
identifierFormat:
|
||||
process.env[`HD_AUTH_SAML_${samlName}_IDENTIFIERFORMAT`],
|
||||
disableRequestedAuthnContext:
|
||||
process.env[`HD_AUTH_SAML_${samlName}_DISABLEREQUESTEDAUTHNCONTEXT`],
|
||||
groupAttribute: process.env[`HD_AUTH_SAML_${samlName}_GROUPATTRIBUTE`],
|
||||
|
@ -269,12 +299,12 @@ const samls = samlNames.map((samlName) => {
|
|||
email: process.env[`HD_AUTH_SAML_${samlName}_ATTRIBUTE_USERNAME`],
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const oauth2s = oauth2Names.map((oauth2Name) => {
|
||||
const oauth2s = oauth2Names.map((oauth2Name) => {
|
||||
return {
|
||||
providerName: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_PROVIDER_NAME`],
|
||||
baseURL: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_BASEURL`],
|
||||
baseURL: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_BASE_URL`],
|
||||
userProfileURL:
|
||||
process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_URL`],
|
||||
userProfileIdAttr:
|
||||
|
@ -293,12 +323,11 @@ const oauth2s = oauth2Names.map((oauth2Name) => {
|
|||
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`],
|
||||
rolesClaim: process.env[`HD_AUTH_OAUTH2_${oauth2Name}`],
|
||||
accessRole: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_ACCESS_ROLE`],
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
export default registerAs('authConfig', async () => {
|
||||
const authConfig = authSchema.validate(
|
||||
{
|
||||
email: {
|
||||
|
@ -338,7 +367,36 @@ export default registerAs('authConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (authConfig.error) {
|
||||
throw new Error(authConfig.error.toString());
|
||||
const errorMessages = await authConfig.error.details
|
||||
.map((detail) => detail.message)
|
||||
.map((error) => {
|
||||
error = replaceAuthErrorsWithEnvironmentVariables(
|
||||
error,
|
||||
'gitlab',
|
||||
'HD_AUTH_GITLAB_',
|
||||
gitlabNames,
|
||||
);
|
||||
error = replaceAuthErrorsWithEnvironmentVariables(
|
||||
error,
|
||||
'ldap',
|
||||
'HD_AUTH_LDAP_',
|
||||
ldapNames,
|
||||
);
|
||||
error = replaceAuthErrorsWithEnvironmentVariables(
|
||||
error,
|
||||
'saml',
|
||||
'HD_AUTH_SAML_',
|
||||
samlNames,
|
||||
);
|
||||
error = replaceAuthErrorsWithEnvironmentVariables(
|
||||
error,
|
||||
'oauth2',
|
||||
'HD_AUTH_OAUTH2_',
|
||||
oauth2Names,
|
||||
);
|
||||
return error;
|
||||
});
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return authConfig.value;
|
||||
});
|
||||
|
|
|
@ -6,17 +6,16 @@
|
|||
|
||||
import * as Joi from 'joi';
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { buildErrorMessage } from './utils';
|
||||
|
||||
export interface CspConfig {
|
||||
enable: boolean;
|
||||
maxAgeSeconds: number;
|
||||
includeSubdomains: boolean;
|
||||
preload: boolean;
|
||||
reportURI: string;
|
||||
}
|
||||
|
||||
const cspSchema = Joi.object({
|
||||
enable: Joi.boolean().default(true).optional(),
|
||||
reportURI: Joi.string().optional(),
|
||||
enable: Joi.boolean().default(true).optional().label('HD_CSP_ENABLE'),
|
||||
reportURI: Joi.string().optional().label('HD_CSP_REPORTURI'),
|
||||
});
|
||||
|
||||
export default registerAs('cspConfig', async () => {
|
||||
|
@ -31,7 +30,10 @@ export default registerAs('cspConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (cspConfig.error) {
|
||||
throw new Error(cspConfig.error.toString());
|
||||
const errorMessages = await cspConfig.error.details.map(
|
||||
(detail) => detail.message,
|
||||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return cspConfig.value;
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import * as Joi from 'joi';
|
||||
import { DatabaseDialect } from './database-dialect.enum';
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { buildErrorMessage } from './utils';
|
||||
|
||||
export interface DatabaseConfig {
|
||||
username: string;
|
||||
|
@ -23,33 +24,35 @@ const databaseSchema = Joi.object({
|
|||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_DATABASE_USER'),
|
||||
password: Joi.when('dialect', {
|
||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_DATABASE_PASS'),
|
||||
database: Joi.when('dialect', {
|
||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_DATABASE_NAME'),
|
||||
host: Joi.when('dialect', {
|
||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_DATABASE_HOST'),
|
||||
port: Joi.when('dialect', {
|
||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||
then: Joi.number(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_DATABASE_PORT'),
|
||||
storage: Joi.when('dialect', {
|
||||
is: Joi.valid(DatabaseDialect.SQLITE),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
dialect: Joi.string().valid(...Object.values(DatabaseDialect)),
|
||||
}).label('HD_DATABASE_STORAGE'),
|
||||
dialect: Joi.string()
|
||||
.valid(...Object.values(DatabaseDialect))
|
||||
.label('HD_DATABASE_DIALECT'),
|
||||
});
|
||||
|
||||
export default registerAs('databaseConfig', async () => {
|
||||
|
@ -69,7 +72,10 @@ export default registerAs('databaseConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (databaseConfig.error) {
|
||||
throw new Error(databaseConfig.error.toString());
|
||||
const errorMessages = await databaseConfig.error.details.map(
|
||||
(detail) => detail.message,
|
||||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return databaseConfig.value;
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import * as Joi from 'joi';
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { buildErrorMessage } from './utils';
|
||||
|
||||
export interface HstsConfig {
|
||||
enable: boolean;
|
||||
|
@ -15,12 +16,16 @@ export interface HstsConfig {
|
|||
}
|
||||
|
||||
const hstsSchema = Joi.object({
|
||||
enable: Joi.boolean().default(true).optional(),
|
||||
enable: Joi.boolean().default(true).optional().label('HD_HSTS_ENABLE'),
|
||||
maxAgeSeconds: Joi.number()
|
||||
.default(60 * 60 * 24 * 365)
|
||||
.optional(),
|
||||
includeSubdomains: Joi.boolean().default(true).optional(),
|
||||
preload: Joi.boolean().default(true).optional(),
|
||||
.optional()
|
||||
.label('HD_HSTS_MAX_AGE'),
|
||||
includeSubdomains: Joi.boolean()
|
||||
.default(true)
|
||||
.optional()
|
||||
.label('HD_HSTS_INCLUDE_SUBDOMAINS'),
|
||||
preload: Joi.boolean().default(true).optional().label('HD_HSTS_PRELOAD'),
|
||||
});
|
||||
|
||||
export default registerAs('hstsConfig', async () => {
|
||||
|
@ -37,7 +42,10 @@ export default registerAs('hstsConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (hstsConfig.error) {
|
||||
throw new Error(hstsConfig.error.toString());
|
||||
const errorMessages = await hstsConfig.error.details.map(
|
||||
(detail) => detail.message,
|
||||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return hstsConfig.value;
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import * as Joi from 'joi';
|
||||
import { BackendType } from '../media/backends/backend-type.enum';
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { buildErrorMessage } from './utils';
|
||||
|
||||
export interface MediaConfig {
|
||||
backend: {
|
||||
|
@ -33,37 +34,41 @@ export interface MediaConfig {
|
|||
|
||||
const mediaSchema = Joi.object({
|
||||
backend: {
|
||||
use: Joi.string().valid(...Object.values(BackendType)),
|
||||
use: Joi.string()
|
||||
.valid(...Object.values(BackendType))
|
||||
.label('HD_MEDIA_BACKEND'),
|
||||
filesystem: {
|
||||
uploadPath: Joi.when('...use', {
|
||||
is: Joi.valid(BackendType.FILESYSTEM),
|
||||
then: Joi.string(),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
}).label('HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH'),
|
||||
},
|
||||
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(),
|
||||
accessKey: Joi.string().label('HD_MEDIA_BACKEND_S3_ACCESS_KEY'),
|
||||
secretKey: Joi.string().label('HD_MEDIA_BACKEND_S3_SECRET_KEY'),
|
||||
endPoint: Joi.string().label('HD_MEDIA_BACKEND_S3_ENDPOINT'),
|
||||
secure: Joi.boolean().label('HD_MEDIA_BACKEND_S3_SECURE'),
|
||||
port: Joi.number().label('HD_MEDIA_BACKEND_S3_PORT'),
|
||||
}),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
azure: Joi.when('...use', {
|
||||
is: Joi.valid(BackendType.AZURE),
|
||||
then: Joi.object({
|
||||
connectionString: Joi.string(),
|
||||
container: Joi.string(),
|
||||
connectionString: Joi.string().label(
|
||||
'HD_MEDIA_BACKEND_AZURE_CONNECTION_STRING',
|
||||
),
|
||||
container: Joi.string().label('HD_MEDIA_BACKEND_AZURE_CONTAINER'),
|
||||
}),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
imgur: Joi.when('...use', {
|
||||
is: Joi.valid(BackendType.IMGUR),
|
||||
then: Joi.object({
|
||||
clientID: Joi.string(),
|
||||
clientID: Joi.string().label('HD_MEDIA_BACKEND_IMGUR_CLIENTID'),
|
||||
}),
|
||||
otherwise: Joi.optional(),
|
||||
}),
|
||||
|
@ -80,7 +85,7 @@ export default registerAs('mediaConfig', async () => {
|
|||
},
|
||||
s3: {
|
||||
accessKey: process.env.HD_MEDIA_BACKEND_S3_ACCESS_KEY,
|
||||
secretKey: process.env.HD_MEDIA_BACKEND_S3_ACCESS_KEY,
|
||||
secretKey: process.env.HD_MEDIA_BACKEND_S3_SECRET_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,
|
||||
|
@ -101,7 +106,10 @@ export default registerAs('mediaConfig', async () => {
|
|||
},
|
||||
);
|
||||
if (mediaConfig.error) {
|
||||
throw new Error(mediaConfig.error.toString());
|
||||
const errorMessages = await mediaConfig.error.details.map(
|
||||
(detail) => detail.message,
|
||||
);
|
||||
throw new Error(buildErrorMessage(errorMessages));
|
||||
}
|
||||
return mediaConfig.value;
|
||||
});
|
||||
|
|
43
src/config/utils.spec.ts
Normal file
43
src/config/utils.spec.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import {
|
||||
replaceAuthErrorsWithEnvironmentVariables,
|
||||
toArrayConfig,
|
||||
} from './utils';
|
||||
|
||||
describe('config utils', () => {
|
||||
describe('toArrayConfig', () => {
|
||||
it('empty', () => {
|
||||
expect(toArrayConfig('')).toEqual([]);
|
||||
});
|
||||
it('one element', () => {
|
||||
expect(toArrayConfig('one')).toEqual(['one']);
|
||||
});
|
||||
it('multiple elements', () => {
|
||||
expect(toArrayConfig('one, two, three')).toEqual(['one', 'two', 'three']);
|
||||
});
|
||||
it('non default seperator', () => {
|
||||
expect(toArrayConfig('one ; two ; three', ';')).toEqual([
|
||||
'one',
|
||||
'two',
|
||||
'three',
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('toArrayConfig', () => {
|
||||
it('"gitlab[0].scope', () => {
|
||||
expect(
|
||||
replaceAuthErrorsWithEnvironmentVariables(
|
||||
'"gitlab[0].scope',
|
||||
'gitlab',
|
||||
'HD_AUTH_GITLAB_',
|
||||
['test'],
|
||||
),
|
||||
).toEqual('"HD_AUTH_GITLAB_test_SCOPE');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -15,3 +15,77 @@ export const toArrayConfig = (configValue: string, separator = ',') => {
|
|||
|
||||
return configValue.split(separator).map((arrayItem) => arrayItem.trim());
|
||||
};
|
||||
|
||||
export const buildErrorMessage = (errorMessages: string[]): string => {
|
||||
let totalErrorMessage = 'There were some errors with your configuration:';
|
||||
for (const message of errorMessages) {
|
||||
totalErrorMessage += '\n - ';
|
||||
totalErrorMessage += message;
|
||||
}
|
||||
totalErrorMessage +=
|
||||
'\nFor further information, have a look at our configuration docs at https://docs.hedgedoc.org/configuration';
|
||||
return totalErrorMessage;
|
||||
};
|
||||
|
||||
export const replaceAuthErrorsWithEnvironmentVariables = (
|
||||
message: string,
|
||||
name: string,
|
||||
replacement: string,
|
||||
arrayOfNames: string[],
|
||||
): string => {
|
||||
// this builds a regex like /"gitlab\[(\d+)]\./ to extract the position in the arrayOfNames
|
||||
const regex = new RegExp('"' + name + '\\[(\\d+)]\\.', 'g');
|
||||
message = message.replace(
|
||||
regex,
|
||||
(_, index) => `"${replacement}${arrayOfNames[index]}.`,
|
||||
);
|
||||
message = message.replace('.providerName', '_PROVIDER_NAME');
|
||||
message = message.replace('.baseURL', '_BASE_URL');
|
||||
message = message.replace('.clientID', '_CLIENT_ID');
|
||||
message = message.replace('.clientSecret', '_CLIENT_SECRET');
|
||||
message = message.replace('.scope', '_SCOPE');
|
||||
message = message.replace('.version', '_GITLAB_VERSION');
|
||||
message = message.replace('.url', '_URL');
|
||||
message = message.replace('.bindDn', '_BIND_DN');
|
||||
message = message.replace('.bindCredentials', '_BIND_CREDENTIALS');
|
||||
message = message.replace('.searchBase', '_SEARCH_BASE');
|
||||
message = message.replace('.searchFilter', '_SEARCH_FILTER');
|
||||
message = message.replace('.searchAttributes', '_SEARCH_ATTRIBUTES');
|
||||
message = message.replace('.usernameField', '_USERNAME_FIELD');
|
||||
message = message.replace('.useridField', '_USERID_FIELD');
|
||||
message = message.replace('.tlsCa', '_TLS_CA');
|
||||
message = message.replace('.idpSsoUrl', '_IDPSSOURL');
|
||||
message = message.replace('.idpCert', '_IDPCERT');
|
||||
message = message.replace('.clientCert', '_CLIENTCERT');
|
||||
message = message.replace('.issuer', '_ISSUER');
|
||||
message = message.replace('.identifierFormat', '_IDENTIFIERFORMAT');
|
||||
message = message.replace(
|
||||
'.disableRequestedAuthnContext',
|
||||
'_DISABLEREQUESTEDAUTHNCONTEXT',
|
||||
);
|
||||
message = message.replace('.groupAttribute', '_GROUPATTRIBUTE');
|
||||
message = message.replace('.requiredGroups', '_REQUIREDGROUPS');
|
||||
message = message.replace('.externalGroups', '_EXTERNALGROUPS');
|
||||
message = message.replace('.attribute.id', '_ATTRIBUTE_ID');
|
||||
message = message.replace('.attribute.username', '_ATTRIBUTE_USERNAME');
|
||||
message = message.replace('.attribute.email', '_ATTRIBUTE_USERNAME');
|
||||
message = message.replace('.userProfileURL', '_USER_PROFILE_URL');
|
||||
message = message.replace('.userProfileIdAttr', '_USER_PROFILE_ID_ATTR');
|
||||
message = message.replace(
|
||||
'.userProfileUsernameAttr',
|
||||
'_USER_PROFILE_USERNAME_ATTR',
|
||||
);
|
||||
message = message.replace(
|
||||
'.userProfileDisplayNameAttr',
|
||||
'_USER_PROFILE_DISPLAY_NAME_ATTR',
|
||||
);
|
||||
message = message.replace(
|
||||
'.userProfileEmailAttr',
|
||||
'_USER_PROFILE_EMAIL_ATTR',
|
||||
);
|
||||
message = message.replace('.tokenURL', '_TOKEN_URL');
|
||||
message = message.replace('.authorizationURL', '_AUTHORIZATION_URL');
|
||||
message = message.replace('.rolesClaim', '_ROLES_CLAIM');
|
||||
message = message.replace('.accessRole', '_ACCESS_ROLE');
|
||||
return message;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue