diff --git a/Gruntfile.js b/Gruntfile.js index bee096789c..9cee93ccb6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,254 +16,292 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const coffee = require("coffee-script"); -const fs = require("fs"); -const { - spawn -} = require("child_process"); -const { - exec -} = require("child_process"); -const rimraf = require("rimraf"); -const Path = require("path"); -const semver = require("semver"); -const knox = require("knox"); -const crypto = require("crypto"); -const async = require("async"); -const settings = require("settings-sharelatex"); -const _ = require("underscore"); +const coffee = require('coffee-script') +const fs = require('fs') +const { spawn } = require('child_process') +const { exec } = require('child_process') +const rimraf = require('rimraf') +const Path = require('path') +const semver = require('semver') +const knox = require('knox') +const crypto = require('crypto') +const async = require('async') +const settings = require('settings-sharelatex') +const _ = require('underscore') +const SERVICES = require('./config/services') -const SERVICES = require("./config/services"); +module.exports = function (grunt) { + let Helpers + let service + grunt.loadNpmTasks('grunt-bunyan') + grunt.loadNpmTasks('grunt-execute') + grunt.loadNpmTasks('grunt-available-tasks') + grunt.loadNpmTasks('grunt-concurrent') + grunt.loadNpmTasks('grunt-contrib-coffee') + grunt.loadNpmTasks('grunt-shell') -module.exports = function(grunt) { - let Helpers; - let service; - grunt.loadNpmTasks('grunt-bunyan'); - grunt.loadNpmTasks('grunt-execute'); - grunt.loadNpmTasks('grunt-available-tasks'); - grunt.loadNpmTasks('grunt-concurrent'); - grunt.loadNpmTasks("grunt-contrib-coffee"); - grunt.loadNpmTasks("grunt-shell"); + grunt.task.loadTasks('./tasks') - grunt.task.loadTasks("./tasks"); + const execute = {} + for (service of Array.from(SERVICES)) { + execute[service.name] = { src: `${service.name}/app.js` } + } - const execute = {}; - for (service of Array.from(SERVICES)) { - execute[service.name] = - {src: `${service.name}/app.js`}; - } + grunt.initConfig({ + execute, - grunt.initConfig({ - execute, + concurrent: { + all: { + tasks: (() => { + const result = [] + for (service of Array.from(SERVICES)) { + result.push(`run:${service.name}`) + } + return result + })(), + options: { + limit: SERVICES.length, + logConcurrentOutput: true, + }, + }, + }, - concurrent: { - all: { - tasks: (((() => { - const result = []; - for (service of Array.from(SERVICES)) { result.push(`run:${service.name}`); - } - return result; - })())), - options: { - limit: SERVICES.length, - logConcurrentOutput: true + availabletasks: { + tasks: { + options: { + filter: 'exclude', + tasks: ['concurrent', 'execute', 'bunyan', 'availabletasks'], + groups: { + 'Run tasks': ['run', 'run:all', 'default'].concat( + (() => { + const result1 = [] + for (service of Array.from(SERVICES)) { + result1.push(`run:${service.name}`) + } + return result1 + })() + ), + Misc: ['help'], + 'Install tasks': (() => { + const result2 = [] + for (service of Array.from(SERVICES)) { + result2.push(`install:${service.name}`) + } + return result2 + })().concat(['install:all', 'install']), + 'Update tasks': (() => { + const result3 = [] + for (service of Array.from(SERVICES)) { + result3.push(`update:${service.name}`) + } + return result3 + })().concat(['update:all', 'update']), + Checks: [ + 'check', + 'check:redis', + 'check:latexmk', + 'check:s3', + 'check:make', + 'check:mongo', + ], + }, + }, + }, + }, + }) - availabletasks: { - tasks: { - options: { - filter: 'exclude', - tasks: [ - 'concurrent', - 'execute', - 'bunyan', - 'availabletasks' - ], - groups: { - "Run tasks": [ - "run", - "run:all", - "default" - ].concat(((() => { - const result1 = []; - for (service of Array.from(SERVICES)) { result1.push(`run:${service.name}`); - } - return result1; - })())), - "Misc": [ - "help" - ], - "Install tasks": ((() => { - const result2 = []; - for (service of Array.from(SERVICES)) { result2.push(`install:${service.name}`); - } - return result2; - })()).concat(["install:all", "install"]), - "Update tasks": ((() => { - const result3 = []; - for (service of Array.from(SERVICES)) { result3.push(`update:${service.name}`); - } - return result3; - })()).concat(["update:all", "update"]), - "Checks": ["check", "check:redis", "check:latexmk", "check:s3", "check:make", "check:mongo"] - } - } - } - }}); + for (service of Array.from(SERVICES)) { + ;(service => + grunt.registerTask( + `install:${service.name}`, + `Download and set up the ${service.name} service`, + function () { + const done = this.async() + return Helpers.installService(service, done) + } + ))(service) + } - for (service of Array.from(SERVICES)) { - ((service => grunt.registerTask(`install:${service.name}`, `Download and set up the ${service.name} service`, function() { - const done = this.async(); - return Helpers.installService(service, done); - })))(service); - } + grunt.registerTask( + 'install:all', + 'Download and set up all ShareLaTeX services', + [] + .concat( + (() => { + const result4 = [] + for (service of Array.from(SERVICES)) { + result4.push(`install:${service.name}`) + } + return result4 + })() + ) + .concat(['postinstall']) + ) + grunt.registerTask('install', 'install:all') + grunt.registerTask('postinstall', 'Explain postinstall steps', function () { + return Helpers.postinstallMessage(this.async()) + }) + grunt.registerTask( + 'update:all', + 'Checkout and update all ShareLaTeX services', + ['check:make'].concat( + (() => { + const result5 = [] + for (service of Array.from(SERVICES)) { + result5.push(`update:${service.name}`) + } + return result5 + })() + ) + ) + grunt.registerTask('update', 'update:all') + grunt.registerTask('run', 'Run all of the sharelatex processes', [ + 'concurrent:all', + ]) + grunt.registerTask('run:all', 'run') - grunt.registerTask('install:all', "Download and set up all ShareLaTeX services", - [].concat( - ((() => { - const result4 = []; - for (service of Array.from(SERVICES)) { result4.push(`install:${service.name}`); - } - return result4; - })()) - ).concat(['postinstall']) - ); + grunt.registerTask('help', 'Display this help list', 'availabletasks') + grunt.registerTask('default', 'run') - grunt.registerTask('install', 'install:all'); - grunt.registerTask('postinstall', 'Explain postinstall steps', function() { - return Helpers.postinstallMessage(this.async()); - }); + grunt.registerTask( + 'check:redis', + 'Check that redis is installed and running', + function () { + return Helpers.checkRedisConnect(this.async()) + } + ) - grunt.registerTask('update:all', "Checkout and update all ShareLaTeX services", - ["check:make"].concat( - ((() => { - const result5 = []; - for (service of Array.from(SERVICES)) { result5.push(`update:${service.name}`); - } - return result5; - })()) - ) - ); - grunt.registerTask('update', 'update:all'); - grunt.registerTask('run', "Run all of the sharelatex processes", ['concurrent:all']); - grunt.registerTask('run:all', 'run'); + grunt.registerTask( + 'check:mongo', + 'Check that mongo is installed', + function () { + return Helpers.checkMongoConnect(this.async()) + } + ) - grunt.registerTask('help', 'Display this help list', 'availabletasks'); - grunt.registerTask('default', 'run'); + grunt.registerTask( + 'check', + 'Check that you have the required dependencies installed', + ['check:redis', 'check:mongo', 'check:make'] + ) - grunt.registerTask("check:redis", "Check that redis is installed and running", function() { - return Helpers.checkRedisConnect(this.async()); - }); + grunt.registerTask('check:make', 'Check that make is installed', function () { + return Helpers.checkMake(this.async()) + }) - grunt.registerTask("check:mongo", "Check that mongo is installed", function() { - return Helpers.checkMongoConnect(this.async()); - }); + return (Helpers = { + installService(service, callback) { + if (callback == null) { + callback = function (error) {} + } + console.log(`Installing ${service.name}`) + return Helpers.cloneGitRepo(service, function (error) { + if (error != null) { + return callback(error) + } else { + return callback() + } + }) + }, - grunt.registerTask("check", "Check that you have the required dependencies installed", ["check:redis", "check:mongo", "check:make"]); + cloneGitRepo(service, callback) { + if (callback == null) { + callback = function (error) {} + } + const repo_src = service.repo + const dir = service.name + if (!fs.existsSync(dir)) { + const proc = spawn('git', ['clone', repo_src, dir], { + stdio: 'inherit', + }) + return proc.on('close', () => + Helpers.checkoutVersion(service, callback) + ) + } else { + console.log(`${dir} already installed, skipping.`) + return callback() + } + }, - grunt.registerTask("check:make", "Check that make is installed", function() { - return Helpers.checkMake(this.async()); - }); + checkoutVersion(service, callback) { + if (callback == null) { + callback = function (error) {} + } + const dir = service.name + grunt.log.write(`checking out ${service.name} ${service.version}`) + const proc = spawn('git', ['checkout', service.version], { + stdio: 'inherit', + cwd: dir, + }) + return proc.on('close', () => callback()) + }, - - return Helpers = { - installService(service, callback) { - if (callback == null) { callback = function(error) {}; } - console.log(`Installing ${service.name}`); - return Helpers.cloneGitRepo(service, function(error) { - if (error != null) { - return callback(error); - } else { - return callback(); - } - }); - }, - - cloneGitRepo(service, callback) { - if (callback == null) { callback = function(error) {}; } - const repo_src = service.repo; - const dir = service.name; - if (!fs.existsSync(dir)) { - const proc = spawn("git", [ - "clone", - repo_src, - dir - ], {stdio: "inherit"}); - return proc.on("close", () => Helpers.checkoutVersion(service, callback)); - } else { - console.log(`${dir} already installed, skipping.`); - return callback(); - } - }, - - checkoutVersion(service, callback) { - if (callback == null) { callback = function(error) {}; } - const dir = service.name; - grunt.log.write(`checking out ${service.name} ${service.version}`); - const proc = spawn("git", ["checkout", service.version], {stdio: "inherit", cwd: dir}); - return proc.on("close", () => callback()); - }, - - postinstallMessage(callback) { - if (callback == null) { callback = function(error) {}; } - grunt.log.write(`\ + postinstallMessage(callback) { + if (callback == null) { + callback = function (error) {} + } + grunt.log.write(`\ Services cloned: ${(() => { - const result6 = []; - for (service of Array.from(SERVICES)) { result6.push(service.name); - } - return result6; - })()} + const result6 = [] + for (service of Array.from(SERVICES)) { + result6.push(service.name) + } + return result6 + })()} To install services run: $ source bin/install-services This will install the required node versions and run \`npm install\` for each service. See https://github.com/sharelatex/sharelatex/pull/549 for more info.\ -` - ); - return callback(); - }, +`) + return callback() + }, - checkMake(callback) { - if (callback == null) { callback = function(error) {}; } - grunt.log.write("Checking make is installed... "); - return exec("make --version", function(error, stdout, stderr) { - if ((error != null) && error.message.match("not found")) { - grunt.log.error("FAIL."); - grunt.log.errorlns(`\ + checkMake(callback) { + if (callback == null) { + callback = function (error) {} + } + grunt.log.write('Checking make is installed... ') + return exec('make --version', function (error, stdout, stderr) { + if (error != null && error.message.match('not found')) { + grunt.log.error('FAIL.') + grunt.log.errorlns(`\ Either make is not installed or is not in your path. On Ubuntu you can install make with: sudo apt-get install build-essential \ -` - ); - return callback(error); - } else if (error != null) { - return callback(error); - } else { - grunt.log.write("OK."); - return callback(); - } - }); - }, - checkMongoConnect(callback) { - if (callback == null) { callback = function(error) {}; } - grunt.log.write("Checking can connect to mongo"); - const mongojs = require("mongojs"); - const db = mongojs(settings.mongo.url, ["tags"]); - db.runCommand({ ping: 1 }, function(err, res) { - if (!err && res.ok) { - grunt.log.write("OK."); - } - return callback(); - }); - return db.on('error', function(err){ - err = "Can not connect to mongodb"; - grunt.log.error("FAIL."); - grunt.log.errorlns(`\ +`) + return callback(error) + } else if (error != null) { + return callback(error) + } else { + grunt.log.write('OK.') + return callback() + } + }) + }, + checkMongoConnect(callback) { + if (callback == null) { + callback = function (error) {} + } + grunt.log.write('Checking can connect to mongo') + const mongojs = require('mongojs') + const db = mongojs(settings.mongo.url, ['tags']) + db.runCommand({ ping: 1 }, function (err, res) { + if (!err && res.ok) { + grunt.log.write('OK.') + } + return callback() + }) + return db.on('error', function (err) { + err = 'Can not connect to mongodb' + grunt.log.error('FAIL.') + grunt.log.errorlns(`\ !!!!!!!!!!!!!! MONGO ERROR !!!!!!!!!!!!!! ShareLaTeX can not talk to the mongodb instance @@ -271,30 +309,31 @@ ShareLaTeX can not talk to the mongodb instance Check the mongodb instance is running and accessible on env var SHARELATEX_MONGO_URL !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\ -` - ); - throw new Error("Can not connect to Mongodb"); - return callback(err); - }); - }, +`) + throw new Error('Can not connect to Mongodb') + return callback(err) + }) + }, - checkRedisConnect(callback) { - if (callback == null) { callback = function(error) {}; } - grunt.log.write("Checking can connect to redis\n"); - const rclient = require("redis").createClient(settings.redis.web); + checkRedisConnect(callback) { + if (callback == null) { + callback = function (error) {} + } + grunt.log.write('Checking can connect to redis\n') + const rclient = require('redis').createClient(settings.redis.web) - rclient.ping(function(err, res) { - if ((err == null)) { - grunt.log.write("OK."); - } else { - throw new Error("Can not connect to redis"); - } - return callback(); - }); - const errorHandler = _.once(function(err){ - err = "Can not connect to redis"; - grunt.log.error("FAIL."); - grunt.log.errorlns(`\ + rclient.ping(function (err, res) { + if (err == null) { + grunt.log.write('OK.') + } else { + throw new Error('Can not connect to redis') + } + return callback() + }) + const errorHandler = _.once(function (err) { + err = 'Can not connect to redis' + grunt.log.error('FAIL.') + grunt.log.errorlns(`\ !!!!!!!!!!!!!! REDIS ERROR !!!!!!!!!!!!!! ShareLaTeX can not talk to the redis instance @@ -302,19 +341,17 @@ ShareLaTeX can not talk to the redis instance Check the redis instance is running and accessible on env var SHARELATEX_REDIS_HOST !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\ -` - ); - throw new Error("Can not connect to redis"); - return callback(err); - }); - return rclient.on('error', errorHandler); - } - }; -}; - - - +`) + throw new Error('Can not connect to redis') + return callback(err) + }) + return rclient.on('error', errorHandler) + }, + }) +} function __guard__(value, transform) { - return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined } diff --git a/settings.js b/settings.js index 09d21d0015..25de871789 100644 --- a/settings.js +++ b/settings.js @@ -12,308 +12,349 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -let allTexLiveDockerImageNames, allTexLiveDockerImages, redisConfig, siteUrl; -let e; -const Path = require('path'); +let allTexLiveDockerImageNames, allTexLiveDockerImages, redisConfig, siteUrl +let e +const Path = require('path') // These credentials are used for authenticating api requests // between services that may need to go over public channels -const httpAuthUser = "sharelatex"; -const httpAuthPass = process.env.WEB_API_PASSWORD; -const httpAuthUsers = {}; -httpAuthUsers[httpAuthUser] = httpAuthPass; +const httpAuthUser = 'sharelatex' +const httpAuthPass = process.env.WEB_API_PASSWORD +const httpAuthUsers = {} +httpAuthUsers[httpAuthUser] = httpAuthPass -const parse = function(option){ - if (option != null) { - try { - const opt = JSON.parse(option); - return opt; - } catch (err) { - throw new Error(`problem parsing ${option}, invalid JSON`); - } - } -}; +const parse = function (option) { + if (option != null) { + try { + const opt = JSON.parse(option) + return opt + } catch (err) { + throw new Error(`problem parsing ${option}, invalid JSON`) + } + } +} -const parseIntOrFail = function(value){ - const parsedValue = parseInt(value, 10); - if (isNaN(parsedValue)) { - throw new Error(`'${value}' is an invalid integer`); - } - return parsedValue; -}; +const parseIntOrFail = function (value) { + const parsedValue = parseInt(value, 10) + if (isNaN(parsedValue)) { + throw new Error(`'${value}' is an invalid integer`) + } + return parsedValue +} -const DATA_DIR = '/var/lib/sharelatex/data'; -const TMP_DIR = '/var/lib/sharelatex/tmp'; +const DATA_DIR = '/var/lib/sharelatex/data' +const TMP_DIR = '/var/lib/sharelatex/tmp' const settings = { + clsi: { + optimiseInDocker: process.env.OPTIMISE_PDF === 'true', + }, - clsi: { - optimiseInDocker: process.env.OPTIMISE_PDF === 'true' - }, + brandPrefix: '', - brandPrefix: "", + allowAnonymousReadAndWriteSharing: + process.env.SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING === 'true', - allowAnonymousReadAndWriteSharing: - process.env.SHARELATEX_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING === 'true', + // Databases + // --------- - // Databases - // --------- + // ShareLaTeX's main persistent data store is MongoDB (http://www.mongodb.org/) + // Documentation about the URL connection string format can be found at: + // + // http://docs.mongodb.org/manual/reference/connection-string/ + // + // The following works out of the box with Mongo's default settings: + mongo: { + url: process.env.SHARELATEX_MONGO_URL || 'mongodb://dockerhost/sharelatex', + }, - // ShareLaTeX's main persistent data store is MongoDB (http://www.mongodb.org/) - // Documentation about the URL connection string format can be found at: - // - // http://docs.mongodb.org/manual/reference/connection-string/ - // - // The following works out of the box with Mongo's default settings: - mongo: { - url : process.env.SHARELATEX_MONGO_URL || 'mongodb://dockerhost/sharelatex' - }, + // Redis is used in ShareLaTeX for high volume queries, like real-time + // editing, and session management. + // + // The following config will work with Redis's default settings: + redis: { + web: (redisConfig = { + host: process.env.SHARELATEX_REDIS_HOST || 'dockerhost', + port: process.env.SHARELATEX_REDIS_PORT || '6379', + password: process.env.SHARELATEX_REDIS_PASS || undefined, + key_schema: { + // document-updater + blockingKey({ doc_id }) { + return `Blocking:${doc_id}` + }, + docLines({ doc_id }) { + return `doclines:${doc_id}` + }, + docOps({ doc_id }) { + return `DocOps:${doc_id}` + }, + docVersion({ doc_id }) { + return `DocVersion:${doc_id}` + }, + docHash({ doc_id }) { + return `DocHash:${doc_id}` + }, + projectKey({ doc_id }) { + return `ProjectId:${doc_id}` + }, + docsInProject({ project_id }) { + return `DocsIn:${project_id}` + }, + ranges({ doc_id }) { + return `Ranges:${doc_id}` + }, + // document-updater:realtime + pendingUpdates({ doc_id }) { + return `PendingUpdates:${doc_id}` + }, + // document-updater:history + uncompressedHistoryOps({ doc_id }) { + return `UncompressedHistoryOps:${doc_id}` + }, + docsWithHistoryOps({ project_id }) { + return `DocsWithHistoryOps:${project_id}` + }, + // document-updater:lock + blockingKey({ doc_id }) { + return `Blocking:${doc_id}` + }, + // track-changes:lock + historyLock({ doc_id }) { + return `HistoryLock:${doc_id}` + }, + historyIndexLock({ project_id }) { + return `HistoryIndexLock:${project_id}` + }, + // track-changes:history + uncompressedHistoryOps({ doc_id }) { + return `UncompressedHistoryOps:${doc_id}` + }, + docsWithHistoryOps({ project_id }) { + return `DocsWithHistoryOps:${project_id}` + }, + // realtime + clientsInProject({ project_id }) { + return `clients_in_project:${project_id}` + }, + connectedUser({ project_id, client_id }) { + return `connected_user:${project_id}:${client_id}` + }, + }, + }), + fairy: redisConfig, + // track-changes and document-updater + realtime: redisConfig, + documentupdater: redisConfig, + lock: redisConfig, + history: redisConfig, + websessions: redisConfig, + api: redisConfig, + pubsub: redisConfig, + project_history: redisConfig, + }, - // Redis is used in ShareLaTeX for high volume queries, like real-time - // editing, and session management. - // - // The following config will work with Redis's default settings: - redis: { - web: (redisConfig = { - host: process.env.SHARELATEX_REDIS_HOST || "dockerhost", - port: process.env.SHARELATEX_REDIS_PORT || "6379", - password: process.env.SHARELATEX_REDIS_PASS || undefined, - key_schema: { - // document-updater - blockingKey({doc_id}) { return `Blocking:${doc_id}`; }, - docLines({doc_id}) { return `doclines:${doc_id}`; }, - docOps({doc_id}) { return `DocOps:${doc_id}`; }, - docVersion({doc_id}) { return `DocVersion:${doc_id}`; }, - docHash({doc_id}) { return `DocHash:${doc_id}`; }, - projectKey({doc_id}) { return `ProjectId:${doc_id}`; }, - docsInProject({project_id}) { return `DocsIn:${project_id}`; }, - ranges({doc_id}) { return `Ranges:${doc_id}`; }, - // document-updater:realtime - pendingUpdates({doc_id}) { return `PendingUpdates:${doc_id}`; }, - // document-updater:history - uncompressedHistoryOps({doc_id}) { return `UncompressedHistoryOps:${doc_id}`; }, - docsWithHistoryOps({project_id}) { return `DocsWithHistoryOps:${project_id}`; }, - // document-updater:lock - blockingKey({doc_id}) { return `Blocking:${doc_id}`; }, - // track-changes:lock - historyLock({doc_id}) { return `HistoryLock:${doc_id}`; }, - historyIndexLock({project_id}) { return `HistoryIndexLock:${project_id}`; }, - // track-changes:history - uncompressedHistoryOps({doc_id}) { return `UncompressedHistoryOps:${doc_id}`; }, - docsWithHistoryOps({project_id}) { return `DocsWithHistoryOps:${project_id}`; }, - // realtime - clientsInProject({project_id}) { return `clients_in_project:${project_id}`; }, - connectedUser({project_id, client_id}){ return `connected_user:${project_id}:${client_id}`; } - } - }), - fairy: redisConfig, - // track-changes and document-updater - realtime: redisConfig, - documentupdater: redisConfig, - lock: redisConfig, - history: redisConfig, - websessions: redisConfig, - api: redisConfig, - pubsub: redisConfig, - project_history: redisConfig - }, + // The compile server (the clsi) uses a SQL database to cache files and + // meta-data. sqlite is the default, and the load is low enough that this will + // be fine in production (we use sqlite at sharelatex.com). + // + // If you want to configure a different database, see the Sequelize documentation + // for available options: + // + // https://github.com/sequelize/sequelize/wiki/API-Reference-Sequelize#example-usage + // + mysql: { + clsi: { + database: 'clsi', + username: 'clsi', + password: '', + dialect: 'sqlite', + storage: Path.join(DATA_DIR, 'db.sqlite'), + }, + }, - // The compile server (the clsi) uses a SQL database to cache files and - // meta-data. sqlite is the default, and the load is low enough that this will - // be fine in production (we use sqlite at sharelatex.com). - // - // If you want to configure a different database, see the Sequelize documentation - // for available options: - // - // https://github.com/sequelize/sequelize/wiki/API-Reference-Sequelize#example-usage - // - mysql: { - clsi: { - database: "clsi", - username: "clsi", - password: "", - dialect: "sqlite", - storage: Path.join(DATA_DIR, "db.sqlite") - } - }, + // File storage + // ------------ - // File storage - // ------------ + // ShareLaTeX can store binary files like images either locally or in Amazon + // S3. The default is locally: + filestore: { + backend: 'fs', + stores: { + user_files: Path.join(DATA_DIR, 'user_files'), + template_files: Path.join(DATA_DIR, 'template_files'), + }, + }, - // ShareLaTeX can store binary files like images either locally or in Amazon - // S3. The default is locally: - filestore: { - backend: "fs", - stores: { - user_files: Path.join(DATA_DIR, "user_files"), - template_files: Path.join(DATA_DIR, "template_files") - } - }, + // To use Amazon S3 as a storage backend, comment out the above config, and + // uncomment the following, filling in your key, secret, and bucket name: + // + // filestore: + // backend: "s3" + // stores: + // user_files: "BUCKET_NAME" + // s3: + // key: "AWS_KEY" + // secret: "AWS_SECRET" + // - // To use Amazon S3 as a storage backend, comment out the above config, and - // uncomment the following, filling in your key, secret, and bucket name: - // - // filestore: - // backend: "s3" - // stores: - // user_files: "BUCKET_NAME" - // s3: - // key: "AWS_KEY" - // secret: "AWS_SECRET" - // + trackchanges: { + continueOnError: true, + }, - trackchanges: { - continueOnError: true - }, + // Local disk caching + // ------------------ + path: { + // If we ever need to write something to disk (e.g. incoming requests + // that need processing but may be too big for memory), then write + // them to disk here: + dumpFolder: Path.join(TMP_DIR, 'dumpFolder'), + // Where to write uploads before they are processed + uploadFolder: Path.join(TMP_DIR, 'uploads'), + // Where to write the project to disk before running LaTeX on it + compilesDir: Path.join(DATA_DIR, 'compiles'), + // Where to cache downloaded URLs for the CLSI + clsiCacheDir: Path.join(DATA_DIR, 'cache'), + // Where to write the output files to disk after running LaTeX + outputDir: Path.join(DATA_DIR, 'output'), + }, - // Local disk caching - // ------------------ - path: { - // If we ever need to write something to disk (e.g. incoming requests - // that need processing but may be too big for memory), then write - // them to disk here: - dumpFolder: Path.join(TMP_DIR, "dumpFolder"), - // Where to write uploads before they are processed - uploadFolder: Path.join(TMP_DIR, "uploads"), - // Where to write the project to disk before running LaTeX on it - compilesDir: Path.join(DATA_DIR, "compiles"), - // Where to cache downloaded URLs for the CLSI - clsiCacheDir: Path.join(DATA_DIR, "cache"), - // Where to write the output files to disk after running LaTeX - outputDir: Path.join(DATA_DIR, "output") - }, + // Server Config + // ------------- - // Server Config - // ------------- + // Where your instance of ShareLaTeX can be found publicly. This is used + // when emails are sent out and in generated links: + siteUrl: (siteUrl = process.env.SHARELATEX_SITE_URL || 'http://localhost'), - // Where your instance of ShareLaTeX can be found publicly. This is used - // when emails are sent out and in generated links: - siteUrl: (siteUrl = process.env.SHARELATEX_SITE_URL || 'http://localhost'), + // The name this is used to describe your ShareLaTeX Installation + appName: process.env.SHARELATEX_APP_NAME || 'ShareLaTeX (Community Edition)', - // The name this is used to describe your ShareLaTeX Installation - appName: process.env.SHARELATEX_APP_NAME || "ShareLaTeX (Community Edition)", + restrictInvitesToExistingAccounts: + process.env.SHARELATEX_RESTRICT_INVITES_TO_EXISTING_ACCOUNTS === 'true', - restrictInvitesToExistingAccounts: process.env.SHARELATEX_RESTRICT_INVITES_TO_EXISTING_ACCOUNTS === 'true', + nav: { + title: + process.env.SHARELATEX_NAV_TITLE || + process.env.SHARELATEX_APP_NAME || + 'ShareLaTeX Community Edition', + }, - nav: { - title: process.env.SHARELATEX_NAV_TITLE || process.env.SHARELATEX_APP_NAME || "ShareLaTeX Community Edition" - }, + // The email address which users will be directed to as the main point of + // contact for this installation of ShareLaTeX. + adminEmail: process.env.SHARELATEX_ADMIN_EMAIL || 'placeholder@example.com', + // If provided, a sessionSecret is used to sign cookies so that they cannot be + // spoofed. This is recommended. + security: { + sessionSecret: + process.env.SHARELATEX_SESSION_SECRET || process.env.CRYPTO_RANDOM, + }, - // The email address which users will be directed to as the main point of - // contact for this installation of ShareLaTeX. - adminEmail: process.env.SHARELATEX_ADMIN_EMAIL || "placeholder@example.com", + // These credentials are used for authenticating api requests + // between services that may need to go over public channels + httpAuthUsers, - // If provided, a sessionSecret is used to sign cookies so that they cannot be - // spoofed. This is recommended. - security: { - sessionSecret: process.env.SHARELATEX_SESSION_SECRET || process.env.CRYPTO_RANDOM - }, + // Should javascript assets be served minified or not. + useMinifiedJs: true, - // These credentials are used for authenticating api requests - // between services that may need to go over public channels - httpAuthUsers, + // Should static assets be sent with a header to tell the browser to cache + // them. This should be false in development where changes are being made, + // but should be set to true in production. + cacheStaticAssets: true, - // Should javascript assets be served minified or not. - useMinifiedJs: true, + // If you are running ShareLaTeX over https, set this to true to send the + // cookie with a secure flag (recommended). + secureCookie: process.env.SHARELATEX_SECURE_COOKIE != null, - // Should static assets be sent with a header to tell the browser to cache - // them. This should be false in development where changes are being made, - // but should be set to true in production. - cacheStaticAssets: true, + // If you are running ShareLaTeX behind a proxy (like Apache, Nginx, etc) + // then set this to true to allow it to correctly detect the forwarded IP + // address and http/https protocol information. - // If you are running ShareLaTeX over https, set this to true to send the - // cookie with a secure flag (recommended). - secureCookie: (process.env.SHARELATEX_SECURE_COOKIE != null), + behindProxy: process.env.SHARELATEX_BEHIND_PROXY || false, - // If you are running ShareLaTeX behind a proxy (like Apache, Nginx, etc) - // then set this to true to allow it to correctly detect the forwarded IP - // address and http/https protocol information. + i18n: { + subdomainLang: { + www: { + lngCode: process.env.SHARELATEX_SITE_LANGUAGE || 'en', + url: siteUrl, + }, + }, + defaultLng: process.env.SHARELATEX_SITE_LANGUAGE || 'en', + }, - behindProxy: process.env.SHARELATEX_BEHIND_PROXY || false, + currentImageName: process.env.TEX_LIVE_DOCKER_IMAGE, - i18n: { - subdomainLang: { - www: {lngCode:process.env.SHARELATEX_SITE_LANGUAGE || "en", url: siteUrl} - }, - defaultLng: process.env.SHARELATEX_SITE_LANGUAGE || "en" - }, + apis: { + web: { + url: 'http://localhost:3000', + user: httpAuthUser, + pass: httpAuthPass, + }, + project_history: { + enabled: false, + }, + }, + references: {}, + notifications: undefined, - currentImageName: process.env.TEX_LIVE_DOCKER_IMAGE, - - apis: { - web: { - url: "http://localhost:3000", - user: httpAuthUser, - pass: httpAuthPass - }, - project_history: { - enabled: false - } - }, - references:{}, - notifications:undefined, - - defaultFeatures: { - collaborators: -1, - dropbox: true, - versioning: true, - compileTimeout: parseIntOrFail(process.env.COMPILE_TIMEOUT || 180), - compileGroup: "standard", - trackChanges: true, - templates: true, - references: true - } -}; + defaultFeatures: { + collaborators: -1, + dropbox: true, + versioning: true, + compileTimeout: parseIntOrFail(process.env.COMPILE_TIMEOUT || 180), + compileGroup: 'standard', + trackChanges: true, + templates: true, + references: true, + }, +} // # OPTIONAL CONFIGURABLE SETTINGS if (process.env.SHARELATEX_LEFT_FOOTER != null) { - try { - settings.nav.left_footer = JSON.parse(process.env.SHARELATEX_LEFT_FOOTER); - } catch (error) { - e = error; - console.error("could not parse SHARELATEX_LEFT_FOOTER, not valid JSON"); - } + try { + settings.nav.left_footer = JSON.parse(process.env.SHARELATEX_LEFT_FOOTER) + } catch (error) { + e = error + console.error('could not parse SHARELATEX_LEFT_FOOTER, not valid JSON') + } } if (process.env.SHARELATEX_RIGHT_FOOTER != null) { - settings.nav.right_footer = process.env.SHARELATEX_RIGHT_FOOTER; - try { - settings.nav.right_footer = JSON.parse(process.env.SHARELATEX_RIGHT_FOOTER); - } catch (error1) { - e = error1; - console.error("could not parse SHARELATEX_RIGHT_FOOTER, not valid JSON"); - } + settings.nav.right_footer = process.env.SHARELATEX_RIGHT_FOOTER + try { + settings.nav.right_footer = JSON.parse(process.env.SHARELATEX_RIGHT_FOOTER) + } catch (error1) { + e = error1 + console.error('could not parse SHARELATEX_RIGHT_FOOTER, not valid JSON') + } } if (process.env.SHARELATEX_HEADER_IMAGE_URL != null) { - settings.nav.custom_logo = process.env.SHARELATEX_HEADER_IMAGE_URL; + settings.nav.custom_logo = process.env.SHARELATEX_HEADER_IMAGE_URL } if (process.env.SHARELATEX_HEADER_NAV_LINKS != null) { - console.error(`\ + console.error(`\ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # WARNING: SHARELATEX_HEADER_NAV_LINKS is no longer supported # See https://github.com/sharelatex/sharelatex/wiki/Configuring-Headers,-Footers-&-Logo # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\ -` - ); +`) } if (process.env.SHARELATEX_HEADER_EXTRAS != null) { - try { - settings.nav.header_extras = JSON.parse(process.env.SHARELATEX_HEADER_EXTRAS); - } catch (error2) { - e = error2; - console.error("could not parse SHARELATEX_HEADER_EXTRAS, not valid JSON"); - } + try { + settings.nav.header_extras = JSON.parse( + process.env.SHARELATEX_HEADER_EXTRAS + ) + } catch (error2) { + e = error2 + console.error('could not parse SHARELATEX_HEADER_EXTRAS, not valid JSON') + } } - - // Sending Email // ------------- // @@ -323,370 +364,415 @@ if (process.env.SHARELATEX_HEADER_EXTRAS != null) { // // http://www.nodemailer.com/docs/transports - if (process.env.SHARELATEX_EMAIL_FROM_ADDRESS != null) { + settings.email = { + fromAddress: process.env.SHARELATEX_EMAIL_FROM_ADDRESS, + replyTo: process.env.SHARELATEX_EMAIL_REPLY_TO || '', + driver: process.env.SHARELATEX_EMAIL_DRIVER, + parameters: { + // AWS Creds + AWSAccessKeyID: process.env.SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID, + AWSSecretKey: process.env.SHARELATEX_EMAIL_AWS_SES_SECRET_KEY, - settings.email = { - fromAddress: process.env.SHARELATEX_EMAIL_FROM_ADDRESS, - replyTo: process.env.SHARELATEX_EMAIL_REPLY_TO || "", - driver: process.env.SHARELATEX_EMAIL_DRIVER, - parameters: { - // AWS Creds - AWSAccessKeyID: process.env.SHARELATEX_EMAIL_AWS_SES_ACCESS_KEY_ID, - AWSSecretKey: process.env.SHARELATEX_EMAIL_AWS_SES_SECRET_KEY, + // SMTP Creds + host: process.env.SHARELATEX_EMAIL_SMTP_HOST, + port: process.env.SHARELATEX_EMAIL_SMTP_PORT, + secure: parse(process.env.SHARELATEX_EMAIL_SMTP_SECURE), + ignoreTLS: parse(process.env.SHARELATEX_EMAIL_SMTP_IGNORE_TLS), + name: process.env.SHARELATEX_EMAIL_SMTP_NAME, + logger: process.env.SHARELATEX_EMAIL_SMTP_LOGGER === 'true', + }, - // SMTP Creds - host: process.env.SHARELATEX_EMAIL_SMTP_HOST, - port: process.env.SHARELATEX_EMAIL_SMTP_PORT, - secure: parse(process.env.SHARELATEX_EMAIL_SMTP_SECURE), - ignoreTLS: parse(process.env.SHARELATEX_EMAIL_SMTP_IGNORE_TLS), - name: process.env.SHARELATEX_EMAIL_SMTP_NAME, - logger: process.env.SHARELATEX_EMAIL_SMTP_LOGGER === 'true' - }, + textEncoding: process.env.SHARELATEX_EMAIL_TEXT_ENCODING, + template: { + customFooter: process.env.SHARELATEX_CUSTOM_EMAIL_FOOTER, + }, + } - textEncoding: process.env.SHARELATEX_EMAIL_TEXT_ENCODING, - template: { - customFooter: process.env.SHARELATEX_CUSTOM_EMAIL_FOOTER - } - }; + if (process.env.SHARELATEX_EMAIL_AWS_SES_REGION != null) { + settings.email.parameters.region = + process.env.SHARELATEX_EMAIL_AWS_SES_REGION + } - if (process.env.SHARELATEX_EMAIL_AWS_SES_REGION != null) { - settings.email.parameters.region = process.env.SHARELATEX_EMAIL_AWS_SES_REGION; - } + if ( + process.env.SHARELATEX_EMAIL_SMTP_USER != null || + process.env.SHARELATEX_EMAIL_SMTP_PASS != null + ) { + settings.email.parameters.auth = { + user: process.env.SHARELATEX_EMAIL_SMTP_USER, + pass: process.env.SHARELATEX_EMAIL_SMTP_PASS, + } + } - if ((process.env.SHARELATEX_EMAIL_SMTP_USER != null) || (process.env.SHARELATEX_EMAIL_SMTP_PASS != null)) { - settings.email.parameters.auth = { - user: process.env.SHARELATEX_EMAIL_SMTP_USER, - pass: process.env.SHARELATEX_EMAIL_SMTP_PASS - }; - } - - if (process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH != null) { - settings.email.parameters.tls = - {rejectUnauthorized: parse(process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH)}; - } + if (process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH != null) { + settings.email.parameters.tls = { + rejectUnauthorized: parse( + process.env.SHARELATEX_EMAIL_SMTP_TLS_REJECT_UNAUTH + ), + } + } } - // i18n if (process.env.SHARELATEX_LANG_DOMAIN_MAPPING != null) { - - settings.i18n.subdomainLang = parse(process.env.SHARELATEX_LANG_DOMAIN_MAPPING); + settings.i18n.subdomainLang = parse( + process.env.SHARELATEX_LANG_DOMAIN_MAPPING + ) } // Password Settings // ----------- // These restrict the passwords users can use when registering // opts are from http://antelle.github.io/passfield -if (process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH) { - - settings.passwordStrengthOptions = { - pattern: process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || "aA$3", - length: {min:process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || 8, max: process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH || 150} - }; +if ( + process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || + process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || + process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH +) { + settings.passwordStrengthOptions = { + pattern: process.env.SHARELATEX_PASSWORD_VALIDATION_PATTERN || 'aA$3', + length: { + min: process.env.SHARELATEX_PASSWORD_VALIDATION_MIN_LENGTH || 8, + max: process.env.SHARELATEX_PASSWORD_VALIDATION_MAX_LENGTH || 150, + }, + } } - - - // ###################### // ShareLaTeX Server Pro // ###################### if (parse(process.env.SHARELATEX_IS_SERVER_PRO) === true) { - settings.bypassPercentageRollouts = true; - settings.apis.references = - {url: "http://localhost:3040"}; + settings.bypassPercentageRollouts = true + settings.apis.references = { url: 'http://localhost:3040' } } - // LDAP - SERVER PRO ONLY // ---------- if (process.env.SHARELATEX_LDAP_HOST) { - console.error(`\ + console.error(`\ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # WARNING: The LDAP configuration format has changed in version 0.5.1 # See https://github.com/sharelatex/sharelatex/wiki/Server-Pro:-LDAP-Config # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\ -` - ); +`) } if (process.env.SHARELATEX_LDAP_URL) { - let _ldap_connect_timeout, _ldap_group_search_attribs, _ldap_search_attribs, _ldap_timeout; - settings.externalAuth = true; - settings.ldap = { - emailAtt: process.env.SHARELATEX_LDAP_EMAIL_ATT, - nameAtt: process.env.SHARELATEX_LDAP_NAME_ATT, - lastNameAtt: process.env.SHARELATEX_LDAP_LAST_NAME_ATT, - updateUserDetailsOnLogin: process.env.SHARELATEX_LDAP_UPDATE_USER_DETAILS_ON_LOGIN === 'true', - placeholder: process.env.SHARELATEX_LDAP_PLACEHOLDER, - server: { - url: process.env.SHARELATEX_LDAP_URL, - bindDn: process.env.SHARELATEX_LDAP_BIND_DN, - bindCredentials: process.env.SHARELATEX_LDAP_BIND_CREDENTIALS, - bindProperty: process.env.SHARELATEX_LDAP_BIND_PROPERTY, - searchBase: process.env.SHARELATEX_LDAP_SEARCH_BASE, - searchScope: process.env.SHARELATEX_LDAP_SEARCH_SCOPE, - searchFilter: process.env.SHARELATEX_LDAP_SEARCH_FILTER, - searchAttributes: ( - (_ldap_search_attribs = process.env.SHARELATEX_LDAP_SEARCH_ATTRIBUTES) ? - (() => { try { - return JSON.parse(_ldap_search_attribs); - } catch (error3) { - e = error3; - return console.error("could not parse SHARELATEX_LDAP_SEARCH_ATTRIBUTES"); - } })() - : - undefined - ), - groupDnProperty: process.env.SHARELATEX_LDAP_GROUP_DN_PROPERTY, - groupSearchBase: process.env.SHARELATEX_LDAP_GROUP_SEARCH_BASE, - groupSearchScope: process.env.SHARELATEX_LDAP_GROUP_SEARCH_SCOPE, - groupSearchFilter: process.env.SHARELATEX_LDAP_GROUP_SEARCH_FILTER, - groupSearchAttributes: ( - (_ldap_group_search_attribs = process.env.SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES) ? - (() => { try { - return JSON.parse(_ldap_group_search_attribs); - } catch (error4) { - e = error4; - return console.error("could not parse SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES"); - } })() - : - undefined - ), - cache: process.env.SHARELATEX_LDAP_CACHE === 'true', - timeout: ( - (_ldap_timeout = process.env.SHARELATEX_LDAP_TIMEOUT) ? - (() => { try { - return parseIntOrFail(_ldap_timeout); - } catch (error5) { - e = error5; - return console.error("Cannot parse SHARELATEX_LDAP_TIMEOUT"); - } })() - : - undefined - ), - connectTimeout: ( - (_ldap_connect_timeout = process.env.SHARELATEX_LDAP_CONNECT_TIMEOUT) ? - (() => { try { - return parseIntOrFail(_ldap_connect_timeout); - } catch (error6) { - e = error6; - return console.error("Cannot parse SHARELATEX_LDAP_CONNECT_TIMEOUT"); - } })() - : - undefined - ) - } - }; + let _ldap_connect_timeout, + _ldap_group_search_attribs, + _ldap_search_attribs, + _ldap_timeout + settings.externalAuth = true + settings.ldap = { + emailAtt: process.env.SHARELATEX_LDAP_EMAIL_ATT, + nameAtt: process.env.SHARELATEX_LDAP_NAME_ATT, + lastNameAtt: process.env.SHARELATEX_LDAP_LAST_NAME_ATT, + updateUserDetailsOnLogin: + process.env.SHARELATEX_LDAP_UPDATE_USER_DETAILS_ON_LOGIN === 'true', + placeholder: process.env.SHARELATEX_LDAP_PLACEHOLDER, + server: { + url: process.env.SHARELATEX_LDAP_URL, + bindDn: process.env.SHARELATEX_LDAP_BIND_DN, + bindCredentials: process.env.SHARELATEX_LDAP_BIND_CREDENTIALS, + bindProperty: process.env.SHARELATEX_LDAP_BIND_PROPERTY, + searchBase: process.env.SHARELATEX_LDAP_SEARCH_BASE, + searchScope: process.env.SHARELATEX_LDAP_SEARCH_SCOPE, + searchFilter: process.env.SHARELATEX_LDAP_SEARCH_FILTER, + searchAttributes: (_ldap_search_attribs = + process.env.SHARELATEX_LDAP_SEARCH_ATTRIBUTES) + ? (() => { + try { + return JSON.parse(_ldap_search_attribs) + } catch (error3) { + e = error3 + return console.error( + 'could not parse SHARELATEX_LDAP_SEARCH_ATTRIBUTES' + ) + } + })() + : undefined, + groupDnProperty: process.env.SHARELATEX_LDAP_GROUP_DN_PROPERTY, + groupSearchBase: process.env.SHARELATEX_LDAP_GROUP_SEARCH_BASE, + groupSearchScope: process.env.SHARELATEX_LDAP_GROUP_SEARCH_SCOPE, + groupSearchFilter: process.env.SHARELATEX_LDAP_GROUP_SEARCH_FILTER, + groupSearchAttributes: (_ldap_group_search_attribs = + process.env.SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES) + ? (() => { + try { + return JSON.parse(_ldap_group_search_attribs) + } catch (error4) { + e = error4 + return console.error( + 'could not parse SHARELATEX_LDAP_GROUP_SEARCH_ATTRIBUTES' + ) + } + })() + : undefined, + cache: process.env.SHARELATEX_LDAP_CACHE === 'true', + timeout: (_ldap_timeout = process.env.SHARELATEX_LDAP_TIMEOUT) + ? (() => { + try { + return parseIntOrFail(_ldap_timeout) + } catch (error5) { + e = error5 + return console.error('Cannot parse SHARELATEX_LDAP_TIMEOUT') + } + })() + : undefined, + connectTimeout: (_ldap_connect_timeout = + process.env.SHARELATEX_LDAP_CONNECT_TIMEOUT) + ? (() => { + try { + return parseIntOrFail(_ldap_connect_timeout) + } catch (error6) { + e = error6 + return console.error( + 'Cannot parse SHARELATEX_LDAP_CONNECT_TIMEOUT' + ) + } + })() + : undefined, + }, + } - if (process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH) { - let ca, ca_paths; - try { - ca = JSON.parse(process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH); - } catch (error7) { - e = error7; - console.error("could not parse SHARELATEX_LDAP_TLS_OPTS_CA_PATH, invalid JSON"); - } + if (process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH) { + let ca, ca_paths + try { + ca = JSON.parse(process.env.SHARELATEX_LDAP_TLS_OPTS_CA_PATH) + } catch (error7) { + e = error7 + console.error( + 'could not parse SHARELATEX_LDAP_TLS_OPTS_CA_PATH, invalid JSON' + ) + } - if (typeof(ca) === 'string') { - ca_paths = [ca]; - } else if ((typeof(ca) === 'object') && ((ca != null ? ca.length : undefined) != null)) { - ca_paths = ca; - } else { - console.error("problem parsing SHARELATEX_LDAP_TLS_OPTS_CA_PATH"); - } + if (typeof ca === 'string') { + ca_paths = [ca] + } else if ( + typeof ca === 'object' && + (ca != null ? ca.length : undefined) != null + ) { + ca_paths = ca + } else { + console.error('problem parsing SHARELATEX_LDAP_TLS_OPTS_CA_PATH') + } - settings.ldap.server.tlsOptions = { - rejectUnauthorized: process.env.SHARELATEX_LDAP_TLS_OPTS_REJECT_UNAUTH === "true", - ca:ca_paths // e.g.'/etc/ldap/ca_certs.pem' - }; - } + settings.ldap.server.tlsOptions = { + rejectUnauthorized: + process.env.SHARELATEX_LDAP_TLS_OPTS_REJECT_UNAUTH === 'true', + ca: ca_paths, // e.g.'/etc/ldap/ca_certs.pem' + } + } } - - - - if (process.env.SHARELATEX_SAML_ENTRYPOINT) { - // NOTE: see https://github.com/node-saml/passport-saml/blob/master/README.md for docs of `server` options - let _saml_additionalAuthorizeParams, _saml_additionalLogoutParams, _saml_additionalParams, _saml_expiration, _saml_skew; - settings.externalAuth = true; - settings.saml = { - updateUserDetailsOnLogin: process.env.SHARELATEX_SAML_UPDATE_USER_DETAILS_ON_LOGIN === 'true', - identityServiceName: process.env.SHARELATEX_SAML_IDENTITY_SERVICE_NAME, - emailField: process.env.SHARELATEX_SAML_EMAIL_FIELD || process.env.SHARELATEX_SAML_EMAIL_FIELD_NAME, - firstNameField: process.env.SHARELATEX_SAML_FIRST_NAME_FIELD, - lastNameField: process.env.SHARELATEX_SAML_LAST_NAME_FIELD, - server: { - // strings - entryPoint: process.env.SHARELATEX_SAML_ENTRYPOINT, - callbackUrl: process.env.SHARELATEX_SAML_CALLBACK_URL, - issuer: process.env.SHARELATEX_SAML_ISSUER, - decryptionPvk: process.env.SHARELATEX_SAML_DECRYPTION_PVK, - decryptionCert: process.env.SHARELATEX_SAML_DECRYPTION_CERT, - signatureAlgorithm: process.env.SHARELATEX_SAML_SIGNATURE_ALGORITHM, - identifierFormat: process.env.SHARELATEX_SAML_IDENTIFIER_FORMAT, - attributeConsumingServiceIndex: process.env.SHARELATEX_SAML_ATTRIBUTE_CONSUMING_SERVICE_INDEX, - authnContext: process.env.SHARELATEX_SAML_AUTHN_CONTEXT, - authnRequestBinding: process.env.SHARELATEX_SAML_AUTHN_REQUEST_BINDING, - validateInResponseTo: process.env.SHARELATEX_SAML_VALIDATE_IN_RESPONSE_TO, - cacheProvider: process.env.SHARELATEX_SAML_CACHE_PROVIDER, - logoutUrl: process.env.SHARELATEX_SAML_LOGOUT_URL, - logoutCallbackUrl: process.env.SHARELATEX_SAML_LOGOUT_CALLBACK_URL, - disableRequestedAuthnContext: process.env.SHARELATEX_SAML_DISABLE_REQUESTED_AUTHN_CONTEXT === 'true', - forceAuthn: process.env.SHARELATEX_SAML_FORCE_AUTHN === 'true', - skipRequestCompression: process.env.SHARELATEX_SAML_SKIP_REQUEST_COMPRESSION === 'true', - acceptedClockSkewMs: ( - (_saml_skew = process.env.SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS) ? - (() => { try { - return parseIntOrFail(_saml_skew); - } catch (error8) { - e = error8; - return console.error("Cannot parse SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS"); - } })() - : - undefined - ), - requestIdExpirationPeriodMs: ( - (_saml_expiration = process.env.SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS) ? - (() => { try { - return parseIntOrFail(_saml_expiration); - } catch (error9) { - e = error9; - return console.error("Cannot parse SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS"); - } })() - : - undefined - ), - additionalParams: ( - (_saml_additionalParams = process.env.SHARELATEX_SAML_ADDITIONAL_PARAMS) ? - (() => { try { - return JSON.parse(_saml_additionalParams); - } catch (error10) { - e = error10; - return console.error("Cannot parse SHARELATEX_SAML_ADDITIONAL_PARAMS"); - } })() - : - undefined - ), - additionalAuthorizeParams: ( - (_saml_additionalAuthorizeParams = process.env.SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS) ? - (() => { try { - return JSON.parse(_saml_additionalAuthorizeParams ); - } catch (error11) { - e = error11; - return console.error("Cannot parse SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS"); - } })() - : - undefined - ), - additionalLogoutParams: ( - (_saml_additionalLogoutParams = process.env.SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS) ? - (() => { try { - return JSON.parse(_saml_additionalLogoutParams ); - } catch (error12) { - e = error12; - return console.error("Cannot parse SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS"); - } })() - : - undefined - ) - } - }; + // NOTE: see https://github.com/node-saml/passport-saml/blob/master/README.md for docs of `server` options + let _saml_additionalAuthorizeParams, + _saml_additionalLogoutParams, + _saml_additionalParams, + _saml_expiration, + _saml_skew + settings.externalAuth = true + settings.saml = { + updateUserDetailsOnLogin: + process.env.SHARELATEX_SAML_UPDATE_USER_DETAILS_ON_LOGIN === 'true', + identityServiceName: process.env.SHARELATEX_SAML_IDENTITY_SERVICE_NAME, + emailField: + process.env.SHARELATEX_SAML_EMAIL_FIELD || + process.env.SHARELATEX_SAML_EMAIL_FIELD_NAME, + firstNameField: process.env.SHARELATEX_SAML_FIRST_NAME_FIELD, + lastNameField: process.env.SHARELATEX_SAML_LAST_NAME_FIELD, + server: { + // strings + entryPoint: process.env.SHARELATEX_SAML_ENTRYPOINT, + callbackUrl: process.env.SHARELATEX_SAML_CALLBACK_URL, + issuer: process.env.SHARELATEX_SAML_ISSUER, + decryptionPvk: process.env.SHARELATEX_SAML_DECRYPTION_PVK, + decryptionCert: process.env.SHARELATEX_SAML_DECRYPTION_CERT, + signatureAlgorithm: process.env.SHARELATEX_SAML_SIGNATURE_ALGORITHM, + identifierFormat: process.env.SHARELATEX_SAML_IDENTIFIER_FORMAT, + attributeConsumingServiceIndex: + process.env.SHARELATEX_SAML_ATTRIBUTE_CONSUMING_SERVICE_INDEX, + authnContext: process.env.SHARELATEX_SAML_AUTHN_CONTEXT, + authnRequestBinding: process.env.SHARELATEX_SAML_AUTHN_REQUEST_BINDING, + validateInResponseTo: process.env.SHARELATEX_SAML_VALIDATE_IN_RESPONSE_TO, + cacheProvider: process.env.SHARELATEX_SAML_CACHE_PROVIDER, + logoutUrl: process.env.SHARELATEX_SAML_LOGOUT_URL, + logoutCallbackUrl: process.env.SHARELATEX_SAML_LOGOUT_CALLBACK_URL, + disableRequestedAuthnContext: + process.env.SHARELATEX_SAML_DISABLE_REQUESTED_AUTHN_CONTEXT === 'true', + forceAuthn: process.env.SHARELATEX_SAML_FORCE_AUTHN === 'true', + skipRequestCompression: + process.env.SHARELATEX_SAML_SKIP_REQUEST_COMPRESSION === 'true', + acceptedClockSkewMs: (_saml_skew = + process.env.SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS) + ? (() => { + try { + return parseIntOrFail(_saml_skew) + } catch (error8) { + e = error8 + return console.error( + 'Cannot parse SHARELATEX_SAML_ACCEPTED_CLOCK_SKEW_MS' + ) + } + })() + : undefined, + requestIdExpirationPeriodMs: (_saml_expiration = + process.env.SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS) + ? (() => { + try { + return parseIntOrFail(_saml_expiration) + } catch (error9) { + e = error9 + return console.error( + 'Cannot parse SHARELATEX_SAML_REQUEST_ID_EXPIRATION_PERIOD_MS' + ) + } + })() + : undefined, + additionalParams: (_saml_additionalParams = + process.env.SHARELATEX_SAML_ADDITIONAL_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalParams) + } catch (error10) { + e = error10 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_PARAMS' + ) + } + })() + : undefined, + additionalAuthorizeParams: (_saml_additionalAuthorizeParams = + process.env.SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalAuthorizeParams) + } catch (error11) { + e = error11 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_AUTHORIZE_PARAMS' + ) + } + })() + : undefined, + additionalLogoutParams: (_saml_additionalLogoutParams = + process.env.SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS) + ? (() => { + try { + return JSON.parse(_saml_additionalLogoutParams) + } catch (error12) { + e = error12 + return console.error( + 'Cannot parse SHARELATEX_SAML_ADDITIONAL_LOGOUT_PARAMS' + ) + } + })() + : undefined, + }, + } - // SHARELATEX_SAML_CERT cannot be empty - // https://github.com/node-saml/passport-saml/commit/f6b1c885c0717f1083c664345556b535f217c102 - if (process.env.SHARELATEX_SAML_CERT) { - settings.saml.server.cert = process.env.SHARELATEX_SAML_CERT; - settings.saml.server.privateCert = process.env.SHARELATEX_SAML_PRIVATE_CERT; - } + // SHARELATEX_SAML_CERT cannot be empty + // https://github.com/node-saml/passport-saml/commit/f6b1c885c0717f1083c664345556b535f217c102 + if (process.env.SHARELATEX_SAML_CERT) { + settings.saml.server.cert = process.env.SHARELATEX_SAML_CERT + settings.saml.server.privateCert = process.env.SHARELATEX_SAML_PRIVATE_CERT + } } // Compiler // -------- -if (process.env.SANDBOXED_COMPILES === "true") { - settings.clsi = { - dockerRunner: true, - docker: { - image: process.env.TEX_LIVE_DOCKER_IMAGE, - env: { - HOME: "/tmp", - PATH: process.env.COMPILER_PATH || "/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - }, - user: "www-data" - } - }; +if (process.env.SANDBOXED_COMPILES === 'true') { + settings.clsi = { + dockerRunner: true, + docker: { + image: process.env.TEX_LIVE_DOCKER_IMAGE, + env: { + HOME: '/tmp', + PATH: + process.env.COMPILER_PATH || + '/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', + }, + user: 'www-data', + }, + } - if ((settings.path == null)) { - settings.path = {}; - } - settings.path.synctexBaseDir = () => "/compile"; - if (process.env.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true') { - console.log("Using sibling containers for sandboxed compiles"); - if (process.env.SANDBOXED_COMPILES_HOST_DIR) { - settings.path.sandboxedCompilesHostDir = process.env.SANDBOXED_COMPILES_HOST_DIR; - } else { - console.error('Sibling containers, but SANDBOXED_COMPILES_HOST_DIR not set'); - } - } + if (settings.path == null) { + settings.path = {} + } + settings.path.synctexBaseDir = () => '/compile' + if (process.env.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true') { + console.log('Using sibling containers for sandboxed compiles') + if (process.env.SANDBOXED_COMPILES_HOST_DIR) { + settings.path.sandboxedCompilesHostDir = + process.env.SANDBOXED_COMPILES_HOST_DIR + } else { + console.error( + 'Sibling containers, but SANDBOXED_COMPILES_HOST_DIR not set' + ) + } + } } - // Templates // --------- if (process.env.SHARELATEX_TEMPLATES_USER_ID) { - settings.templates = { - mountPointUrl: "/templates", - user_id: process.env.SHARELATEX_TEMPLATES_USER_ID - }; + settings.templates = { + mountPointUrl: '/templates', + user_id: process.env.SHARELATEX_TEMPLATES_USER_ID, + } - settings.templateLinks = parse(process.env.SHARELATEX_NEW_PROJECT_TEMPLATE_LINKS); + settings.templateLinks = parse( + process.env.SHARELATEX_NEW_PROJECT_TEMPLATE_LINKS + ) } - // /Learn // ------- if (process.env.SHARELATEX_PROXY_LEARN != null) { - settings.proxyLearn = parse(process.env.SHARELATEX_PROXY_LEARN); + settings.proxyLearn = parse(process.env.SHARELATEX_PROXY_LEARN) } - // /References // ----------- if (process.env.SHARELATEX_ELASTICSEARCH_URL != null) { - settings.references.elasticsearch = - {host: process.env.SHARELATEX_ELASTICSEARCH_URL}; + settings.references.elasticsearch = { + host: process.env.SHARELATEX_ELASTICSEARCH_URL, + } } // TeX Live Images // ----------- if (process.env.ALL_TEX_LIVE_DOCKER_IMAGES != null) { - allTexLiveDockerImages = process.env.ALL_TEX_LIVE_DOCKER_IMAGES.split(','); + allTexLiveDockerImages = process.env.ALL_TEX_LIVE_DOCKER_IMAGES.split(',') } if (process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES != null) { - allTexLiveDockerImageNames = process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES.split(','); + allTexLiveDockerImageNames = + process.env.ALL_TEX_LIVE_DOCKER_IMAGE_NAMES.split(',') } if (allTexLiveDockerImages != null) { - settings.allowedImageNames = []; - for (let index = 0; index < allTexLiveDockerImages.length; index++) { - const fullImageName = allTexLiveDockerImages[index]; - const imageName = Path.basename(fullImageName); - const imageDesc = (allTexLiveDockerImageNames != null) ? allTexLiveDockerImageNames[index] : imageName; - settings.allowedImageNames.push({ imageName, imageDesc }); - } + settings.allowedImageNames = [] + for (let index = 0; index < allTexLiveDockerImages.length; index++) { + const fullImageName = allTexLiveDockerImages[index] + const imageName = Path.basename(fullImageName) + const imageDesc = + allTexLiveDockerImageNames != null + ? allTexLiveDockerImageNames[index] + : imageName + settings.allowedImageNames.push({ imageName, imageDesc }) + } } // With lots of incoming and outgoing HTTP connections to different services, // sometimes long running, it is a good idea to increase the default number // of sockets that Node will hold open. -const http = require('http'); -http.globalAgent.maxSockets = 300; -const https = require('https'); -https.globalAgent.maxSockets = 300; +const http = require('http') +http.globalAgent.maxSockets = 300 +const https = require('https') +https.globalAgent.maxSockets = 300 -module.exports = settings; +module.exports = settings diff --git a/tasks/CreateAndDestroyUsers.js b/tasks/CreateAndDestroyUsers.js index 9417396b73..22be44ae88 100644 --- a/tasks/CreateAndDestroyUsers.js +++ b/tasks/CreateAndDestroyUsers.js @@ -11,75 +11,106 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -module.exports = function(grunt) { +module.exports = function (grunt) { + grunt.registerTask( + 'user:create-admin', + 'Create a user with the given email address and make them an admin. Update in place if the user already exists. Usage: grunt user:create-admin --email joe@example.com', + function () { + const done = this.async() + const email = grunt.option('email') + if (email == null) { + console.error('Usage: grunt user:create-admin --email=joe@example.com') + process.exit(1) + } - grunt.registerTask('user:create-admin', "Create a user with the given email address and make them an admin. Update in place if the user already exists. Usage: grunt user:create-admin --email joe@example.com", function() { - const done = this.async(); - const email = grunt.option("email"); - if ((email == null)) { - console.error("Usage: grunt user:create-admin --email=joe@example.com"); - process.exit(1); - } - - const settings = require("settings-sharelatex"); - const mongodb = require("../web/app/src/infrastructure/mongodb"); - const UserRegistrationHandler = require("../web/app/src/Features/User/UserRegistrationHandler"); - const OneTimeTokenHandler = require("../web/app/src/Features/Security/OneTimeTokenHandler"); - return mongodb.waitForDb().then(() => UserRegistrationHandler.registerNewUser({ + const settings = require('settings-sharelatex') + const mongodb = require('../web/app/src/infrastructure/mongodb') + const UserRegistrationHandler = require('../web/app/src/Features/User/UserRegistrationHandler') + const OneTimeTokenHandler = require('../web/app/src/Features/Security/OneTimeTokenHandler') + return mongodb.waitForDb().then(() => + UserRegistrationHandler.registerNewUser( + { email, - password: require("crypto").randomBytes(32).toString("hex") - }, function(error, user) { - if ((error != null) && ((error != null ? error.message : undefined) !== "EmailAlreadyRegistered")) { - throw error; + password: require('crypto').randomBytes(32).toString('hex'), + }, + function (error, user) { + if ( + error != null && + (error != null ? error.message : undefined) !== + 'EmailAlreadyRegistered' + ) { + throw error } - user.isAdmin = true; - return user.save(function(error) { - if (error != null) { throw error; } - const ONE_WEEK = 7 * 24 * 60 * 60; // seconds - return OneTimeTokenHandler.getNewToken("password", { expiresIn: ONE_WEEK, email:user.email, user_id: user._id.toString() }, function(err, token){ - if (err != null) { return next(err); } + user.isAdmin = true + return user.save(function (error) { + if (error != null) { + throw error + } + const ONE_WEEK = 7 * 24 * 60 * 60 // seconds + return OneTimeTokenHandler.getNewToken( + 'password', + { + expiresIn: ONE_WEEK, + email: user.email, + user_id: user._id.toString(), + }, + function (err, token) { + if (err != null) { + return next(err) + } - console.log(""); - console.log(`\ + console.log('') + console.log(`\ Successfully created ${email} as an admin user. Please visit the following URL to set a password for ${email} and log in: ${settings.siteUrl}/user/password/set?passwordResetToken=${token} \ -` - ); - return done(); - }); - }); - })); - }); - - return grunt.registerTask('user:delete', "deletes a user and all their data, Usage: grunt user:delete --email joe@example.com", function() { - const done = this.async(); - const email = grunt.option("email"); - if ((email == null)) { - console.error("Usage: grunt user:delete --email=joe@example.com"); - process.exit(1); - } - const settings = require("settings-sharelatex"); - const mongodb = require("../web/app/src/infrastructure/mongodb"); - const UserGetter = require("../web/app/src/Features/User/UserGetter"); - const UserDeleter = require("../web/app/src/Features/User/UserDeleter"); - return mongodb.waitForDb().then(() => UserGetter.getUser({email}, function(error, user) { - if (error != null) { - throw error; - } - if ((user == null)) { - console.log(`user ${email} not in database, potentially already deleted`); - return done(); - } - return UserDeleter.deleteUser(user._id, function(err){ - if (err != null) { - throw err; +`) + return done() } - return done(); - }); - })); - }); -}; + ) + }) + } + ) + ) + } + ) + + return grunt.registerTask( + 'user:delete', + 'deletes a user and all their data, Usage: grunt user:delete --email joe@example.com', + function () { + const done = this.async() + const email = grunt.option('email') + if (email == null) { + console.error('Usage: grunt user:delete --email=joe@example.com') + process.exit(1) + } + const settings = require('settings-sharelatex') + const mongodb = require('../web/app/src/infrastructure/mongodb') + const UserGetter = require('../web/app/src/Features/User/UserGetter') + const UserDeleter = require('../web/app/src/Features/User/UserDeleter') + return mongodb.waitForDb().then(() => + UserGetter.getUser({ email }, function (error, user) { + if (error != null) { + throw error + } + if (user == null) { + console.log( + `user ${email} not in database, potentially already deleted` + ) + return done() + } + return UserDeleter.deleteUser(user._id, function (err) { + if (err != null) { + throw err + } + return done() + }) + }) + ) + } + ) +}