mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-19 17:55:17 -04: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
5cb1f29a2c
commit
454a883f17
8 changed files with 372 additions and 168 deletions
|
@ -7,6 +7,7 @@
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { Loglevel } from './loglevel.enum';
|
import { Loglevel } from './loglevel.enum';
|
||||||
|
import { buildErrorMessage } from './utils';
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
domain: string;
|
domain: string;
|
||||||
|
@ -15,12 +16,13 @@ export interface AppConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = Joi.object({
|
const schema = Joi.object({
|
||||||
domain: Joi.string(),
|
domain: Joi.string().label('HD_DOMAIN'),
|
||||||
port: Joi.number().default(3000).optional(),
|
port: Joi.number().default(3000).optional().label('PORT'),
|
||||||
loglevel: Joi.string()
|
loglevel: Joi.string()
|
||||||
.valid(...Object.values(Loglevel))
|
.valid(...Object.values(Loglevel))
|
||||||
.default(Loglevel.WARN)
|
.default(Loglevel.WARN)
|
||||||
.optional(),
|
.optional()
|
||||||
|
.label('HD_LOGLEVEL'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default registerAs('appConfig', async () => {
|
export default registerAs('appConfig', async () => {
|
||||||
|
@ -36,7 +38,10 @@ export default registerAs('appConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (appConfig.error) {
|
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;
|
return appConfig.value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { GitlabScope, GitlabVersion } from './gitlab.enum';
|
import { GitlabScope, GitlabVersion } from './gitlab.enum';
|
||||||
import { toArrayConfig } from './utils';
|
import {
|
||||||
|
buildErrorMessage,
|
||||||
|
replaceAuthErrorsWithEnvironmentVariables,
|
||||||
|
toArrayConfig,
|
||||||
|
} from './utils';
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
export interface AuthConfig {
|
export interface AuthConfig {
|
||||||
|
@ -102,38 +106,50 @@ export interface AuthConfig {
|
||||||
|
|
||||||
const authSchema = Joi.object({
|
const authSchema = Joi.object({
|
||||||
email: {
|
email: {
|
||||||
enableLogin: Joi.boolean().default(false).optional(),
|
enableLogin: Joi.boolean()
|
||||||
enableRegister: Joi.boolean().default(false).optional(),
|
.default(false)
|
||||||
|
.optional()
|
||||||
|
.label('HD_AUTH_EMAIL_ENABLE_LOGIN'),
|
||||||
|
enableRegister: Joi.boolean()
|
||||||
|
.default(false)
|
||||||
|
.optional()
|
||||||
|
.label('HD_AUTH_EMAIL_ENABLE_REGISTER'),
|
||||||
},
|
},
|
||||||
facebook: {
|
facebook: {
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string().optional().label('HD_AUTH_FACEBOOK_CLIENT_ID'),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string()
|
||||||
|
.optional()
|
||||||
|
.label('HD_AUTH_FACEBOOK_CLIENT_SECRET'),
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
consumerKey: Joi.string().optional(),
|
consumerKey: Joi.string().optional().label('HD_AUTH_TWITTER_CONSUMER_KEY'),
|
||||||
consumerSecret: Joi.string().optional(),
|
consumerSecret: Joi.string()
|
||||||
|
.optional()
|
||||||
|
.label('HD_AUTH_TWITTER_CONSUMER_SECRET'),
|
||||||
},
|
},
|
||||||
github: {
|
github: {
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string().optional().label('HD_AUTH_GITHUB_CLIENT_ID'),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string().optional().label('HD_AUTH_GITHUB_CLIENT_SECRET'),
|
||||||
},
|
},
|
||||||
dropbox: {
|
dropbox: {
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string().optional().label('HD_AUTH_DROPBOX_CLIENT_ID'),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string()
|
||||||
appKey: Joi.string().optional(),
|
.optional()
|
||||||
|
.label('HD_AUTH_DROPBOX_CLIENT_SECRET'),
|
||||||
|
appKey: Joi.string().optional().label('HD_AUTH_DROPBOX_APP_KEY'),
|
||||||
},
|
},
|
||||||
google: {
|
google: {
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string().optional().label('HD_AUTH_GOOGLE_CLIENT_ID'),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string().optional().label('HD_AUTH_GOOGLE_CLIENT_SECRET'),
|
||||||
apiKey: Joi.string().optional(),
|
apiKey: Joi.string().optional().label('HD_AUTH_GOOGLE_APP_KEY'),
|
||||||
},
|
},
|
||||||
gitlab: Joi.array()
|
gitlab: Joi.array()
|
||||||
.items(
|
.items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
providerName: Joi.string().default('Gitlab').optional(),
|
providerName: Joi.string().default('Gitlab').optional(),
|
||||||
baseURL: Joi.string().optional(),
|
baseURL: Joi.string(),
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string(),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string(),
|
||||||
scope: Joi.string()
|
scope: Joi.string()
|
||||||
.valid(...Object.values(GitlabScope))
|
.valid(...Object.values(GitlabScope))
|
||||||
.default(GitlabScope.READ_USER)
|
.default(GitlabScope.READ_USER)
|
||||||
|
@ -142,7 +158,7 @@ const authSchema = Joi.object({
|
||||||
.valid(...Object.values(GitlabVersion))
|
.valid(...Object.values(GitlabVersion))
|
||||||
.default(GitlabVersion.V4)
|
.default(GitlabVersion.V4)
|
||||||
.optional(),
|
.optional(),
|
||||||
}),
|
}).optional(),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
// ToDo: should searchfilter have a default?
|
// ToDo: should searchfilter have a default?
|
||||||
|
@ -150,70 +166,83 @@ const authSchema = Joi.object({
|
||||||
.items(
|
.items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
providerName: Joi.string().default('LDAP').optional(),
|
providerName: Joi.string().default('LDAP').optional(),
|
||||||
url: Joi.string().optional(),
|
url: Joi.string(),
|
||||||
bindDn: Joi.string().optional(),
|
bindDn: Joi.string().optional(),
|
||||||
bindCredentials: Joi.string().optional(),
|
bindCredentials: Joi.string().optional(),
|
||||||
searchBase: Joi.string().optional(),
|
searchBase: Joi.string(),
|
||||||
searchFilter: Joi.string().default('(uid={{username}})').optional(),
|
searchFilter: Joi.string().default('(uid={{username}})').optional(),
|
||||||
searchAttributes: Joi.array().items(Joi.string()),
|
searchAttributes: Joi.array()
|
||||||
usernameField: Joi.string().default('userid').optional(),
|
.items(Joi.string())
|
||||||
useridField: Joi.string().optional(),
|
.default(['displayName', 'mail'])
|
||||||
tlsCa: Joi.array().items(Joi.string()),
|
.optional(),
|
||||||
}),
|
usernameField: Joi.string().optional(),
|
||||||
|
useridField: Joi.string(),
|
||||||
|
tlsCa: Joi.array().items(Joi.string()).optional(),
|
||||||
|
}).optional(),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
saml: Joi.array()
|
saml: Joi.array()
|
||||||
.items(
|
.items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
providerName: Joi.string().default('SAML').optional(),
|
providerName: Joi.string().default('SAML').optional(),
|
||||||
idpSsoUrl: Joi.string().optional(),
|
idpSsoUrl: Joi.string(),
|
||||||
idpCert: Joi.string().optional(),
|
idpCert: Joi.string(),
|
||||||
clientCert: 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.
|
// 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()
|
identifierFormat: Joi.string()
|
||||||
.default('urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')
|
.default('urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')
|
||||||
.optional(),
|
.optional(),
|
||||||
disableRequestedAuthnContext: Joi.boolean().default(false).optional(),
|
disableRequestedAuthnContext: Joi.boolean().default(false).optional(),
|
||||||
groupAttribute: Joi.string().optional(),
|
groupAttribute: Joi.string().optional(),
|
||||||
requiredGroups: Joi.array().items(Joi.string()),
|
requiredGroups: Joi.array().items(Joi.string()).optional(),
|
||||||
externalGroups: Joi.array().items(Joi.string()),
|
externalGroups: Joi.array().items(Joi.string()).optional(),
|
||||||
attribute: {
|
attribute: {
|
||||||
id: Joi.string().default('NameId').optional(),
|
id: Joi.string().default('NameId').optional(),
|
||||||
username: Joi.string().default('NameId').optional(),
|
username: Joi.string().default('NameId').optional(),
|
||||||
email: Joi.string().default('NameId').optional(),
|
email: Joi.string().default('NameId').optional(),
|
||||||
},
|
},
|
||||||
}),
|
}).optional(),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
oauth2: Joi.array()
|
oauth2: Joi.array()
|
||||||
.items(
|
.items(
|
||||||
Joi.object({
|
Joi.object({
|
||||||
providerName: Joi.string().default('OAuth2').optional(),
|
providerName: Joi.string().default('OAuth2').optional(),
|
||||||
baseURL: Joi.string().optional(),
|
baseURL: Joi.string(),
|
||||||
userProfileURL: Joi.string().optional(),
|
userProfileURL: Joi.string(),
|
||||||
userProfileIdAttr: Joi.string().optional(),
|
userProfileIdAttr: Joi.string().optional(),
|
||||||
userProfileUsernameAttr: Joi.string().optional(),
|
userProfileUsernameAttr: Joi.string(),
|
||||||
userProfileDisplayNameAttr: Joi.string().optional(),
|
userProfileDisplayNameAttr: Joi.string(),
|
||||||
userProfileEmailAttr: Joi.string().optional(),
|
userProfileEmailAttr: Joi.string(),
|
||||||
tokenURL: Joi.string().optional(),
|
tokenURL: Joi.string(),
|
||||||
authorizationURL: Joi.string().optional(),
|
authorizationURL: Joi.string(),
|
||||||
clientID: Joi.string().optional(),
|
clientID: Joi.string(),
|
||||||
clientSecret: Joi.string().optional(),
|
clientSecret: Joi.string(),
|
||||||
scope: Joi.string().optional(),
|
scope: Joi.string().optional(),
|
||||||
rolesClaim: Joi.string().optional(),
|
rolesClaim: Joi.string().optional(),
|
||||||
accessRole: Joi.string().optional(),
|
accessRole: Joi.string().optional(),
|
||||||
}),
|
}).optional(),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default registerAs('authConfig', async () => {
|
||||||
// ToDo: Validate these with Joi to prevent duplicate entries?
|
// ToDo: Validate these with Joi to prevent duplicate entries?
|
||||||
|
const gitlabNames = toArrayConfig(
|
||||||
const gitlabNames = toArrayConfig(process.env.HD_AUTH_GITLABS, ',');
|
process.env.HD_AUTH_GITLABS,
|
||||||
const ldapNames = toArrayConfig(process.env.HD_AUTH_LDAPS, ',');
|
',',
|
||||||
const samlNames = toArrayConfig(process.env.HD_AUTH_SAMLS, ',');
|
).map((name) => name.toUpperCase());
|
||||||
const oauth2Names = toArrayConfig(process.env.HD_AUTH_OAUTH2S, ',');
|
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 gitlabs = gitlabNames.map((gitlabName) => {
|
const gitlabs = gitlabNames.map((gitlabName) => {
|
||||||
return {
|
return {
|
||||||
|
@ -221,7 +250,7 @@ const gitlabs = gitlabNames.map((gitlabName) => {
|
||||||
baseURL: process.env[`HD_AUTH_GITLAB_${gitlabName}_BASE_URL`],
|
baseURL: process.env[`HD_AUTH_GITLAB_${gitlabName}_BASE_URL`],
|
||||||
clientID: process.env[`HD_AUTH_GITLAB_${gitlabName}_CLIENT_ID`],
|
clientID: process.env[`HD_AUTH_GITLAB_${gitlabName}_CLIENT_ID`],
|
||||||
clientSecret: process.env[`HD_AUTH_GITLAB_${gitlabName}_CLIENT_SECRET`],
|
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`],
|
version: process.env[`HD_AUTH_GITLAB_${gitlabName}_GITLAB_VERSION`],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -251,7 +280,8 @@ const samls = samlNames.map((samlName) => {
|
||||||
idpCert: process.env[`HD_AUTH_SAML_${samlName}_IDPCERT`],
|
idpCert: process.env[`HD_AUTH_SAML_${samlName}_IDPCERT`],
|
||||||
clientCert: process.env[`HD_AUTH_SAML_${samlName}_CLIENTCERT`],
|
clientCert: process.env[`HD_AUTH_SAML_${samlName}_CLIENTCERT`],
|
||||||
issuer: process.env[`HD_AUTH_SAML_${samlName}_ISSUER`],
|
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:
|
disableRequestedAuthnContext:
|
||||||
process.env[`HD_AUTH_SAML_${samlName}_DISABLEREQUESTEDAUTHNCONTEXT`],
|
process.env[`HD_AUTH_SAML_${samlName}_DISABLEREQUESTEDAUTHNCONTEXT`],
|
||||||
groupAttribute: process.env[`HD_AUTH_SAML_${samlName}_GROUPATTRIBUTE`],
|
groupAttribute: process.env[`HD_AUTH_SAML_${samlName}_GROUPATTRIBUTE`],
|
||||||
|
@ -274,7 +304,7 @@ const samls = samlNames.map((samlName) => {
|
||||||
const oauth2s = oauth2Names.map((oauth2Name) => {
|
const oauth2s = oauth2Names.map((oauth2Name) => {
|
||||||
return {
|
return {
|
||||||
providerName: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_PROVIDER_NAME`],
|
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:
|
userProfileURL:
|
||||||
process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_URL`],
|
process.env[`HD_AUTH_OAUTH2_${oauth2Name}_USER_PROFILE_URL`],
|
||||||
userProfileIdAttr:
|
userProfileIdAttr:
|
||||||
|
@ -293,12 +323,11 @@ const oauth2s = oauth2Names.map((oauth2Name) => {
|
||||||
clientID: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_ID`],
|
clientID: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_ID`],
|
||||||
clientSecret: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_SECRET`],
|
clientSecret: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_CLIENT_SECRET`],
|
||||||
scope: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_SCOPE`],
|
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`],
|
accessRole: process.env[`HD_AUTH_OAUTH2_${oauth2Name}_ACCESS_ROLE`],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export default registerAs('authConfig', async () => {
|
|
||||||
const authConfig = authSchema.validate(
|
const authConfig = authSchema.validate(
|
||||||
{
|
{
|
||||||
email: {
|
email: {
|
||||||
|
@ -338,7 +367,36 @@ export default registerAs('authConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (authConfig.error) {
|
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;
|
return authConfig.value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,17 +6,16 @@
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { buildErrorMessage } from './utils';
|
||||||
|
|
||||||
export interface CspConfig {
|
export interface CspConfig {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
maxAgeSeconds: number;
|
reportURI: string;
|
||||||
includeSubdomains: boolean;
|
|
||||||
preload: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cspSchema = Joi.object({
|
const cspSchema = Joi.object({
|
||||||
enable: Joi.boolean().default(true).optional(),
|
enable: Joi.boolean().default(true).optional().label('HD_CSP_ENABLE'),
|
||||||
reportURI: Joi.string().optional(),
|
reportURI: Joi.string().optional().label('HD_CSP_REPORTURI'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default registerAs('cspConfig', async () => {
|
export default registerAs('cspConfig', async () => {
|
||||||
|
@ -31,7 +30,10 @@ export default registerAs('cspConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (cspConfig.error) {
|
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;
|
return cspConfig.value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { DatabaseDialect } from './database-dialect.enum';
|
import { DatabaseDialect } from './database-dialect.enum';
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { buildErrorMessage } from './utils';
|
||||||
|
|
||||||
export interface DatabaseConfig {
|
export interface DatabaseConfig {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -23,33 +24,35 @@ const databaseSchema = Joi.object({
|
||||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_USER'),
|
||||||
password: Joi.when('dialect', {
|
password: Joi.when('dialect', {
|
||||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_PASS'),
|
||||||
database: Joi.when('dialect', {
|
database: Joi.when('dialect', {
|
||||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_NAME'),
|
||||||
host: Joi.when('dialect', {
|
host: Joi.when('dialect', {
|
||||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_HOST'),
|
||||||
port: Joi.when('dialect', {
|
port: Joi.when('dialect', {
|
||||||
is: Joi.invalid(DatabaseDialect.SQLITE),
|
is: Joi.invalid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.number(),
|
then: Joi.number(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_PORT'),
|
||||||
storage: Joi.when('dialect', {
|
storage: Joi.when('dialect', {
|
||||||
is: Joi.valid(DatabaseDialect.SQLITE),
|
is: Joi.valid(DatabaseDialect.SQLITE),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_DATABASE_STORAGE'),
|
||||||
dialect: Joi.string().valid(...Object.values(DatabaseDialect)),
|
dialect: Joi.string()
|
||||||
|
.valid(...Object.values(DatabaseDialect))
|
||||||
|
.label('HD_DATABASE_DIALECT'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default registerAs('databaseConfig', async () => {
|
export default registerAs('databaseConfig', async () => {
|
||||||
|
@ -69,7 +72,10 @@ export default registerAs('databaseConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (databaseConfig.error) {
|
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;
|
return databaseConfig.value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { buildErrorMessage } from './utils';
|
||||||
|
|
||||||
export interface HstsConfig {
|
export interface HstsConfig {
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
@ -15,12 +16,16 @@ export interface HstsConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
const hstsSchema = Joi.object({
|
const hstsSchema = Joi.object({
|
||||||
enable: Joi.boolean().default(true).optional(),
|
enable: Joi.boolean().default(true).optional().label('HD_HSTS_ENABLE'),
|
||||||
maxAgeSeconds: Joi.number()
|
maxAgeSeconds: Joi.number()
|
||||||
.default(60 * 60 * 24 * 365)
|
.default(60 * 60 * 24 * 365)
|
||||||
.optional(),
|
.optional()
|
||||||
includeSubdomains: Joi.boolean().default(true).optional(),
|
.label('HD_HSTS_MAX_AGE'),
|
||||||
preload: Joi.boolean().default(true).optional(),
|
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 () => {
|
export default registerAs('hstsConfig', async () => {
|
||||||
|
@ -37,7 +42,10 @@ export default registerAs('hstsConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (hstsConfig.error) {
|
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;
|
return hstsConfig.value;
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { BackendType } from '../media/backends/backend-type.enum';
|
import { BackendType } from '../media/backends/backend-type.enum';
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
import { buildErrorMessage } from './utils';
|
||||||
|
|
||||||
export interface MediaConfig {
|
export interface MediaConfig {
|
||||||
backend: {
|
backend: {
|
||||||
|
@ -33,37 +34,41 @@ export interface MediaConfig {
|
||||||
|
|
||||||
const mediaSchema = Joi.object({
|
const mediaSchema = Joi.object({
|
||||||
backend: {
|
backend: {
|
||||||
use: Joi.string().valid(...Object.values(BackendType)),
|
use: Joi.string()
|
||||||
|
.valid(...Object.values(BackendType))
|
||||||
|
.label('HD_MEDIA_BACKEND'),
|
||||||
filesystem: {
|
filesystem: {
|
||||||
uploadPath: Joi.when('...use', {
|
uploadPath: Joi.when('...use', {
|
||||||
is: Joi.valid(BackendType.FILESYSTEM),
|
is: Joi.valid(BackendType.FILESYSTEM),
|
||||||
then: Joi.string(),
|
then: Joi.string(),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}).label('HD_MEDIA_BACKEND_FILESYSTEM_UPLOAD_PATH'),
|
||||||
},
|
},
|
||||||
s3: Joi.when('...use', {
|
s3: Joi.when('...use', {
|
||||||
is: Joi.valid(BackendType.S3),
|
is: Joi.valid(BackendType.S3),
|
||||||
then: Joi.object({
|
then: Joi.object({
|
||||||
accessKey: Joi.string(),
|
accessKey: Joi.string().label('HD_MEDIA_BACKEND_S3_ACCESS_KEY'),
|
||||||
secretKey: Joi.string(),
|
secretKey: Joi.string().label('HD_MEDIA_BACKEND_S3_SECRET_KEY'),
|
||||||
endPoint: Joi.string(),
|
endPoint: Joi.string().label('HD_MEDIA_BACKEND_S3_ENDPOINT'),
|
||||||
secure: Joi.boolean(),
|
secure: Joi.boolean().label('HD_MEDIA_BACKEND_S3_SECURE'),
|
||||||
port: Joi.number(),
|
port: Joi.number().label('HD_MEDIA_BACKEND_S3_PORT'),
|
||||||
}),
|
}),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}),
|
||||||
azure: Joi.when('...use', {
|
azure: Joi.when('...use', {
|
||||||
is: Joi.valid(BackendType.AZURE),
|
is: Joi.valid(BackendType.AZURE),
|
||||||
then: Joi.object({
|
then: Joi.object({
|
||||||
connectionString: Joi.string(),
|
connectionString: Joi.string().label(
|
||||||
container: Joi.string(),
|
'HD_MEDIA_BACKEND_AZURE_CONNECTION_STRING',
|
||||||
|
),
|
||||||
|
container: Joi.string().label('HD_MEDIA_BACKEND_AZURE_CONTAINER'),
|
||||||
}),
|
}),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}),
|
||||||
imgur: Joi.when('...use', {
|
imgur: Joi.when('...use', {
|
||||||
is: Joi.valid(BackendType.IMGUR),
|
is: Joi.valid(BackendType.IMGUR),
|
||||||
then: Joi.object({
|
then: Joi.object({
|
||||||
clientID: Joi.string(),
|
clientID: Joi.string().label('HD_MEDIA_BACKEND_IMGUR_CLIENTID'),
|
||||||
}),
|
}),
|
||||||
otherwise: Joi.optional(),
|
otherwise: Joi.optional(),
|
||||||
}),
|
}),
|
||||||
|
@ -80,7 +85,7 @@ export default registerAs('mediaConfig', async () => {
|
||||||
},
|
},
|
||||||
s3: {
|
s3: {
|
||||||
accessKey: process.env.HD_MEDIA_BACKEND_S3_ACCESS_KEY,
|
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,
|
endPoint: process.env.HD_MEDIA_BACKEND_S3_ENDPOINT,
|
||||||
secure: process.env.HD_MEDIA_BACKEND_S3_SECURE,
|
secure: process.env.HD_MEDIA_BACKEND_S3_SECURE,
|
||||||
port: parseInt(process.env.HD_MEDIA_BACKEND_S3_PORT) || undefined,
|
port: parseInt(process.env.HD_MEDIA_BACKEND_S3_PORT) || undefined,
|
||||||
|
@ -101,7 +106,10 @@ export default registerAs('mediaConfig', async () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (mediaConfig.error) {
|
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;
|
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());
|
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…
Add table
Add a link
Reference in a new issue