webgme-engine
Version:
WebGME server and Client API without a GUI
432 lines (381 loc) • 20.4 kB
JavaScript
/*eslint-env node*/
/*eslint no-console: 0*/
/**
* @author lattmann / https://github.com/lattmann
* @author pmeijer / https://github.com/pmeijer
*/
;
var configFileName;
const fs = require('fs');
function warnDeprecated(name, value, hint) {
if (typeof value !== 'undefined') {
if (hint) {
console.warn('WARNING! Deprecated configuration key', name + '.', hint);
} else {
console.warn('WARNING! Deprecated configuration key', name);
}
}
}
function throwValidationError(name, msg) {
let prefix;
if (configFileName) {
prefix = 'In ' + configFileName;
} else {
prefix = 'In configuration';
}
prefix += ': ' + name + ' ';
throw new Error(prefix + msg);
}
function throwTypeMiss(name, value, typeStr) {
const msg = 'must be a(n) ' + typeStr + '. Got: "' + value + '".';
throwValidationError(name, msg);
}
function assertTypeOf(name, value, type, orFalsy) {
if (orFalsy && !value) {
return;
}
if (typeof value !== type) {
throwTypeMiss(name, value, type);
}
}
function assertObject(name, value, orFalsy) {
assertTypeOf(name, value, 'object', orFalsy);
}
function assertString(name, value, orFalsy) {
assertTypeOf(name, value, 'string', orFalsy);
}
function assertNumber(name, value, orFalsy) {
assertTypeOf(name, value, 'number', orFalsy);
}
function assertBoolean(name, value, orFalsy) {
assertTypeOf(name, value, 'boolean', orFalsy);
}
function assertArray(name, value) {
if (value instanceof Array === false) {
throwTypeMiss(name, value, 'array');
}
}
function assertEnum(name, value) {
var validValues = Array.prototype.slice.call(arguments).splice(2),
msg;
if (validValues.indexOf(value) === -1) {
if (configFileName) {
msg = 'In ' + configFileName;
} else {
msg = 'In configuration';
}
msg += ': ' + name + ' must be one of: ' + validValues.toString() + '. Got: "' + value + '".';
throw new Error(msg);
}
}
function assertBooleanOrString(name, value, orFalsy) {
try {
assertTypeOf(name, value, 'boolean', orFalsy);
} catch (e) {
assertTypeOf(name, value, 'string', orFalsy);
}
}
function assertFileExists(name, path) {
try {
fs.statSync(path);
} catch (err) {
throwValidationError(
name,
'must be a path to an existing file. Got: ' + path
);
}
}
// We will fail as early as possible
function validateConfig(configOrFileName) {
var expectedKeys = [],
mountPoints = {},
mountPoint,
config,
errMsg,
key;
if (typeof configOrFileName === 'string') {
configFileName = configOrFileName;
config = require(configFileName);
} else {
config = configOrFileName;
}
assertObject('config', config);
// addOn
expectedKeys.push('addOn');
assertObject('config.addOn', config.addOn);
assertBoolean('config.addOn.enable', config.addOn.enable);
assertArray('config.addOn.basePaths', config.addOn.basePaths);
// authentication
expectedKeys.push('authentication');
assertObject('config.authentication', config.authentication);
assertBoolean('config.authentication.enable', config.authentication.enable);
assertBoolean('config.authentication.allowGuests', config.authentication.allowGuests);
assertBooleanOrString('config.authentication.allowUserRegistration', config.authentication.allowUserRegistration);
assertBoolean('config.authentication.registeredUsersCanCreate', config.authentication.registeredUsersCanCreate);
assertBoolean('config.authentication.inferredUsersCanCreate', config.authentication.inferredUsersCanCreate);
assertBoolean('config.authentication.newUserNeedsVerification', config.authentication.newUserNeedsVerification);
assertBoolean('config.authentication.guestCanCreate', config.authentication.guestCanCreate);
assertString('config.authentication.guestAccount', config.authentication.guestAccount);
assertString('config.authentication.logOutUrl', config.authentication.logOutUrl);
assertNumber('config.authentication.salts', config.authentication.salts);
assertObject('config.authentication.jwt', config.authentication.jwt);
assertNumber('config.authentication.jwt.expiresIn', config.authentication.jwt.expiresIn);
assertString('config.authentication.jwt.privateKey', config.authentication.jwt.privateKey);
assertString('config.authentication.jwt.publicKey', config.authentication.jwt.publicKey);
if (config.authentication.enable && config.authentication.jwt.allowInsecureKeySizes) {
console.warn('WARNING! config.authentication.jwt.allowInsecureKeySizes is true by default to avoid breaking ' +
'changes due to: https://github.com/auth0/node-jsonwebtoken/wiki/Migration-Notes:-v8-to-v9.');
console.warn('If you know your RSA key size is 2048 bits or greater you can avoid this warning by setting ' +
'allowInsecureKeySizes to false.');
}
assertString('config.authentication.jwt.logOutUrlField', config.authentication.jwt.logOutUrlField, true);
assertArray('config.authentication.publicOrganizations', config.authentication.publicOrganizations);
config.authentication.publicOrganizations.forEach(function (publicOrg, idx) {
assertString('config.authentication.publicOrganizations[' + idx, ']', publicOrg);
});
assertObject('config.authentication.encryption', config.authentication.encryption);
assertString('config.authentication.encryption.algorithm', config.authentication.encryption.algorithm);
assertFileExists('config.authentication.encryption.key', config.authentication.encryption.key);
assertBoolean('config.authentication.allowPasswordReset', config.authentication.allowPasswordReset);
assertNumber('config.authentication.allowedResetInterval', config.authentication.allowedResetInterval);
assertNumber('config.authentication.resetTimeout', config.authentication.resetTimeout);
assertBoolean('config.authentication.useEmailforId', config.authentication.useEmailForId);
if (config.authentication.adminAccount) {
assertString('config.authentication.adminAccount', config.authentication.adminAccount);
}
assertObject('config.authentication.azureActiveDirectory',
config.authentication.azureActiveDirectory);
assertBoolean('config.authentication.azureActiveDirectory.enable',
config.authentication.azureActiveDirectory.enable);
if (config.authentication.azureActiveDirectory.enable) {
assertString('config.authentication.azureActiveDirectory.clientId',
config.authentication.azureActiveDirectory.clientId);
assertString('config.authentication.azureActiveDirectory.authority',
config.authentication.azureActiveDirectory.authority);
assertString('config.authentication.azureActiveDirectory.clientSecret',
config.authentication.azureActiveDirectory.clientSecret);
assertString('config.authentication.azureActiveDirectory.redirectUri',
config.authentication.azureActiveDirectory.redirectUri);
if (config.authentication.azureActiveDirectory.accessScope) {
assertString('config.authentication.azureActiveDirectory.accessScope',
config.authentication.azureActiveDirectory.accessScope);
assertString('config.authentication.azureActiveDirectory.issuer',
config.authentication.azureActiveDirectory.issuer);
assertString('config.authentication.azureActiveDirectory.audience',
config.authentication.azureActiveDirectory.audience);
assertString('config.authentication.azureActiveDirectory.cookieId',
config.authentication.azureActiveDirectory.cookieId);
assertString('config.authentication.azureActiveDirectory.jwksUri',
config.authentication.azureActiveDirectory.jwksUri);
}
}
// api options
expectedKeys.push('api');
assertBoolean('config.api.useEnhancedStarterPage', config.api.useEnhancedStarterPage);
// bin scripts
expectedKeys.push('bin');
assertObject('config.bin', config.bin);
assertObject('config.bin.log', config.bin.log);
// blob
expectedKeys.push('blob');
assertObject('config.blob', config.blob);
assertNumber('config.blob.compressionLevel', config.blob.compressionLevel);
if (config.blob.compressionLevel < 0 || config.blob.compressionLevel > 9) {
throw new Error('config.blob.compressionLevel must be an integer between 0 and 9. Got: ' +
config.blob.compressionLevel);
}
assertString('config.blob.type', config.blob.type);
assertString('config.blob.fsDir', config.blob.fsDir);
assertObject('config.blob.s3', config.blob.s3);
// client
expectedKeys.push('client');
assertObject('config.client', config.client);
assertString('config.client.appDir', config.client.appDir);
assertString('config.client.appVersion', config.client.appVersion);
assertString('config.client.faviconPath', config.client.faviconPath);
assertString('config.client.pageTitle', config.client.pageTitle, true);
assertObject('config.client.log', config.client.log);
assertString('config.client.log.level', config.client.log.level);
// core
expectedKeys.push('core');
assertBoolean('config.core.enableCustomConstraints', config.core.enableCustomConstraints);
assertNumber('config.core.overlayShardSize', config.core.overlayShardSize);
if (config.core.overlayShardSize < 1000) {
throw new Error('Overlay shard size must be at least 1000.');
}
// debug
expectedKeys.push('debug');
assertBoolean('config.debug', config.debug);
expectedKeys.push('documentEditing');
assertObject('config.documentEditing', config.documentEditing);
assertBoolean('config.documentEditing.enable', config.documentEditing.enable, true);
assertNumber('config.documentEditing.disconnectTimeout', config.documentEditing.disconnectTimeout);
// executor
expectedKeys.push('executor');
assertObject('config.executor', config.executor);
assertBoolean('config.executor.enable', config.executor.enable);
assertBoolean('config.executor.authentication.enable', config.executor.authentication.enable);
assertBoolean('config.executor.authentication.allowGuests', config.executor.authentication.allowGuests);
assertString('config.executor.nonce', config.executor.nonce, true);
warnDeprecated('config.executor.outputDir', config.executor.outputDir);
assertString('config.executor.labelJobs', config.executor.labelJobs);
assertNumber('config.executor.workerRefreshInterval', config.executor.workerRefreshInterval);
assertNumber('config.executor.clearOutputTimeout', config.executor.clearOutputTimeout);
assertBoolean('config.executor.clearOldDataAtStartUp', config.executor.clearOldDataAtStartUp);
//mailer
expectedKeys.push('mailer');
assertObject('config.mailer', config.mailer);
assertBoolean('config.mailer.enable', config.mailer.enable);
assertBoolean('config.mailer.secure', config.mailer.secure);
assertString('config.mailer.service', config.mailer.service);
assertString('config.mailer.host', config.mailer.host);
assertString('config.mailer.user', config.mailer.user);
assertString('config.mailer.pwd', config.mailer.pwd);
assertNumber('config.mailer.port', config.mailer.port);
assertBoolean('config.mailer.sendPasswordReset', config.mailer.sendPasswordReset);
// mongo configuration
expectedKeys.push('mongo');
assertObject('config.mongo', config.mongo);
assertString('config.mongo.uri', config.mongo.uri);
assertObject('config.mongo.options', config.mongo.options);
// plugin
expectedKeys.push('plugin');
assertObject('config.plugin', config.plugin);
assertBoolean('config.plugin.allowServerExecution', config.plugin.allowServerExecution);
assertArray('config.plugin.basePaths', config.plugin.basePaths);
assertBoolean('config.plugin.displayAll', config.plugin.displayAll);
// requirejsPaths
expectedKeys.push('requirejsPaths');
assertObject('config.requirejsPaths', config.requirejsPaths);
// rest
expectedKeys.push('rest');
assertObject('config.rest', config.rest);
assertObject('config.rest.components', config.rest.components);
for (key in config.rest.components) {
if (typeof config.rest.components[key] === 'string') {
mountPoint = key;
// TODO: Add this warn when the cli tool has been updated.
// console.warn('config.rest.components[' + key + '] is a string a not an object. ' +
// 'It is recommended to change to the format described in #xxxx');
} else {
assertObject('config.rest.components[' + key + ']', config.rest.components[key]);
assertString('config.rest.components[' + key + '].mount', config.rest.components[key].mount);
assertString('config.rest.components[' + key + '].src', config.rest.components[key].src);
assertObject('config.rest.components[' + key + '].options', config.rest.components[key].options, true);
mountPoint = config.rest.components[key].mount;
}
if (mountPoints[mountPoint] === true) {
throw new Error('Same mount point [' + mountPoint + '] specified more than once ' +
'in config.rest.components.');
} else {
mountPoints[mountPoint] = true;
}
}
//seedProjects
expectedKeys.push('seedProjects');
assertBoolean('config.seedProjects.enable', config.seedProjects.enable);
assertString('config.seedProjects.defaultProject', config.seedProjects.defaultProject);
assertArray('config.seedProjects.basePaths', config.seedProjects.basePaths);
assertArray('config.seedProjects.createAtStartup', config.seedProjects.createAtStartup);
config.seedProjects.createAtStartup.forEach(function (seedInfo, index) {
assertObject('config.seedProjects.createAtStartup[' + index + ']', config.seedProjects.createAtStartup[index]);
assertString('config.seedProjects.createAtStartup[' + index + '].seedId',
config.seedProjects.createAtStartup[index].seedId);
assertString('config.seedProjects.createAtStartup[' + index + '].projectName', seedInfo.projectName);
assertObject('config.seedProjects.createAtStartup[' + index + '].rights', seedInfo.rights, true);
if (seedInfo.creatorId) {
assertString('config.seedProjects.createAtStartup[' + index + '].creatorId', seedInfo.creatorId);
} else if (typeof config.authentication.adminAccount !== 'string') {
throw new Error('Either config.seedProjects.createAtStartup[' + index +
'].creatorId or config.authentication.adminAccount should exists!');
}
assertString('config.seedProjects.createAtStartup[' + index + '].ownerId', seedInfo.ownerId, true);
});
// server configuration
expectedKeys.push('server');
assertObject('config.server', config.server);
assertNumber('config.server.port', config.server.port);
assertNumber('config.server.timeout', config.server.timeout);
assertObject('config.server.handle', config.server.handle);
assertObject('config.server.workerManager', config.server.workerManager);
assertString('config.server.workerManager.path', config.server.workerManager.path);
assertObject('config.server.workerManager.options', config.server.workerManager.options);
assertNumber('config.server.maxWorkers', config.server.maxWorkers);
warnDeprecated('config.server.sessionStore', config.server.sessionStore,
'JWTokens are used for authentication, see config.authentication.jwt');
// server log
assertObject('config.server.log', config.server.log);
assertArray('config.server.log.transports', config.server.log.transports);
// server extlib
assertArray('config.server.extlibExcludes', config.server.extlibExcludes);
// server bodyParser config
assertObject('config.server.bodyParser', config.server.bodyParser);
assertObject('config.server.bodyParser.json', config.server.bodyParser.json);
// socketIO
expectedKeys.push('socketIO');
assertObject('config.socketIO', config.socketIO);
assertObject('config.socketIO.clientOptions', config.socketIO.clientOptions);
assertObject('config.socketIO.serverOptions', config.socketIO.serverOptions);
assertObject('config.socketIO.adapter', config.socketIO.adapter);
assertEnum('config.socketIO.adapter.type', config.socketIO.adapter.type.toLowerCase(), 'memory', 'redis');
// storage
expectedKeys.push('storage');
assertObject('config.storage', config.storage);
assertBoolean('config.storage.broadcastProjectEvents', config.storage.broadcastProjectEvents);
warnDeprecated('config.storage.emitCommittedCoreObjects', config.storage.emitCommittedCoreObjects,
'see new config at config.storage.maxEmittedCoreObjects');
assertNumber('config.storage.maxEmittedCoreObjects', config.storage.maxEmittedCoreObjects);
warnDeprecated('config.storage.patchRootCommunicationEnabled', config.storage.patchRootCommunicationEnabled,
'Since 1.7.0 all node changes are transmitted as patch objects (unless newly created).');
assertNumber('config.storage.cache', config.storage.cache);
assertNumber('config.storage.loadBucketSize', config.storage.loadBucketSize);
assertNumber('config.storage.loadBucketTimer', config.storage.loadBucketTimer);
assertEnum('config.storage.keyType', config.storage.keyType, 'rand160Bits', 'ZSSHA', 'plainSHA1', 'rustSHA1');
assertObject('config.storage.database', config.storage.database);
assertEnum('config.storage.database.type', config.storage.database.type.toLowerCase(), 'mongo', 'redis', 'memory');
assertObject('config.storage.database.options', config.storage.database.options);
assertBoolean('config.storage.disableHashChecks', config.storage.disableHashChecks);
assertBoolean('config.storage.requireHashesToMatch', config.storage.requireHashesToMatch);
if (config.storage.disableHashChecks && config.storage.requireHashesToMatch) {
throw new Error('Cannot set config.storage.disableHashChecks and requireHashesToMatch ' +
'to true at the same time!');
}
//visualization
expectedKeys.push('visualization');
assertObject('config.visualization', config.visualization);
assertArray('config.visualization.decoratorPaths', config.visualization.decoratorPaths);
assertArray('config.visualization.svgDirs', config.visualization.svgDirs);
assertArray('config.visualization.panelPaths', config.visualization.panelPaths);
assertArray('config.visualization.visualizerDescriptors', config.visualization.visualizerDescriptors);
assertObject('config.visualization.layout', config.visualization.layout);
assertArray('config.visualization.layout.basePaths', config.visualization.layout.basePaths);
//webhooks
expectedKeys.push('webhooks');
assertBoolean('config.webhooks.enable', config.webhooks.enable);
assertEnum('config.webhooks.manager', config.webhooks.manager.toLowerCase(), 'memory', 'redis');
if (config.webhooks.manager.toLowerCase() === 'redis' && config.socketIO.adapter.type.toLowerCase() !== 'redis') {
throw new Error('config.webhooks.manager can only be ' +
'\'redis\' if config.socketIO.adapter.type is \'redis\' as well');
}
if (Object.keys(config).length !== expectedKeys.length) {
errMsg = 'Configuration had unexpected key(s):';
for (key in config) {
if (expectedKeys.indexOf(key) < 0) {
errMsg += ' "' + key + '"';
}
}
throw new Error(errMsg);
}
return config;
}
module.exports = {
warnDeprecated: warnDeprecated,
assertObject: assertObject,
assertString: assertString,
assertNumber: assertNumber,
assertBoolean: assertBoolean,
assertArray: assertArray,
assertEnum: assertEnum,
assertBooleanOrString: assertBooleanOrString,
validateConfig: validateConfig
};