Migrated settings to es6

This commit is contained in:
SamTV12345 2024-08-17 10:23:03 +02:00
parent 6ed711a4d8
commit 154e67315c
39 changed files with 710 additions and 702 deletions

8
pnpm-lock.yaml generated
View file

@ -297,6 +297,9 @@ importers:
'@types/jsdom': '@types/jsdom':
specifier: ^21.1.7 specifier: ^21.1.7
version: 21.1.7 version: 21.1.7
'@types/jsonminify':
specifier: ^0.4.3
version: 0.4.3
'@types/jsonwebtoken': '@types/jsonwebtoken':
specifier: ^9.0.6 specifier: ^9.0.6
version: 9.0.6 version: 9.0.6
@ -1516,6 +1519,9 @@ packages:
'@types/json5@0.0.29': '@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
'@types/jsonminify@0.4.3':
resolution: {integrity: sha512-+oz7EbPz1Nwmn/sr3UztgXpRhdFpvFrjGi5ictEYxUri5ZvQMTcdTi36MTfD/gCb1A5xhJKdH8Hwz2uz5k6s9A==}
'@types/jsonwebtoken@9.0.6': '@types/jsonwebtoken@9.0.6':
resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
@ -5703,6 +5709,8 @@ snapshots:
'@types/json5@0.0.29': {} '@types/json5@0.0.29': {}
'@types/jsonminify@0.4.3': {}
'@types/jsonwebtoken@9.0.6': '@types/jsonwebtoken@9.0.6':
dependencies: dependencies:
'@types/node': 22.4.0 '@types/node': 22.4.0

View file

@ -22,7 +22,7 @@
*/ */
import {Database} from 'ueberdb2'; import {Database} from 'ueberdb2';
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
import log4js from 'log4js'; import log4js from 'log4js';
const stats = require('../stats') const stats = require('../stats')

View file

@ -14,7 +14,7 @@ import AttributePool from '../../static/js/AttributePool';
const Stream = require('../utils/Stream'); const Stream = require('../utils/Stream');
const assert = require('assert').strict; const assert = require('assert').strict;
const db = require('./DB'); const db = require('./DB');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const authorManager = require('./AuthorManager'); const authorManager = require('./AuthorManager');
const padManager = require('./PadManager'); const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler'); const padMessageHandler = require('../handler/PadMessageHandler');

View file

@ -25,7 +25,7 @@ import {PadType} from "../types/PadType";
const CustomError = require('../utils/customError'); const CustomError = require('../utils/customError');
const Pad = require('../db/Pad'); const Pad = require('../db/Pad');
const db = require('./DB'); const db = require('./DB');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
/** /**
* A cache of all loaded Pads. * A cache of all loaded Pads.

View file

@ -26,7 +26,7 @@ const hooks = require('../../static/js/pluginfw/hooks');
const padManager = require('./PadManager'); const padManager = require('./PadManager');
const readOnlyManager = require('./ReadOnlyManager'); const readOnlyManager = require('./ReadOnlyManager');
const sessionManager = require('./SessionManager'); const sessionManager = require('./SessionManager');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const webaccess = require('../hooks/express/webaccess'); const webaccess = require('../hooks/express/webaccess');
const log4js = require('log4js'); const log4js = require('log4js');
const authLogger = log4js.getLogger('auth'); const authLogger = log4js.getLogger('auth');

View file

@ -25,7 +25,7 @@ const fs = require('fs');
const hooks = require('../../static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
const path = require('path'); const path = require('path');
const resolve = require('resolve'); const resolve = require('resolve');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
import {pluginInstallPath} from '../../static/js/pluginfw/installer' import {pluginInstallPath} from '../../static/js/pluginfw/installer'
const templateCache = new Map(); const templateCache = new Map();

View file

@ -1,15 +1,15 @@
const absolutePaths = require('../utils/AbsolutePaths'); const absolutePaths = require('../utils/AbsolutePaths');
import fs from 'fs'; import fs from 'fs';
import log4js from 'log4js'; import log4js from 'log4js';
const randomString = require('../utils/randomstring'); import randomString from '../utils/randomstring';
const argv = require('../utils/Cli').argv; import {argvP} from '../utils/Cli'
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const apiHandlerLogger = log4js.getLogger('APIHandler'); const apiHandlerLogger = log4js.getLogger('APIHandler');
// ensure we have an apikey // ensure we have an apikey
export let apikey:string|null = null; export let apikey:string|null = null;
const apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || './APIKEY.txt'); const apikeyFilename = absolutePaths.makeAbsolute(argvP.apikey || './APIKEY.txt');
if(settings.authenticationMethod === 'apikey') { if(settings.authenticationMethod === 'apikey') {

View file

@ -25,7 +25,7 @@ const padManager = require('../db/PadManager');
const padMessageHandler = require('./PadMessageHandler'); const padMessageHandler = require('./PadMessageHandler');
import {promises as fs} from 'fs'; import {promises as fs} from 'fs';
import path from 'path'; import path from 'path';
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const {Formidable} = require('formidable'); const {Formidable} = require('formidable');
import os from 'os'; import os from 'os';
const importHtml = require('../utils/ImportHtml'); const importHtml = require('../utils/ImportHtml');

View file

@ -30,7 +30,7 @@ const AttributeManager = require('../../static/js/AttributeManager');
const authorManager = require('../db/AuthorManager'); const authorManager = require('../db/AuthorManager');
import padutils from '../../static/js/pad_utils'; import padutils from '../../static/js/pad_utils';
const readOnlyManager = require('../db/ReadOnlyManager'); const readOnlyManager = require('../db/ReadOnlyManager');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const securityManager = require('../db/SecurityManager'); const securityManager = require('../db/SecurityManager');
const plugins = require('../../static/js/pluginfw/plugin_defs'); const plugins = require('../../static/js/pluginfw/plugin_defs');
import log4js from 'log4js'; import log4js from 'log4js';

View file

@ -23,7 +23,7 @@
import {MapArrayType} from "../types/MapType"; import {MapArrayType} from "../types/MapType";
import {SocketModule} from "../types/SocketModule"; import {SocketModule} from "../types/SocketModule";
const log4js = require('log4js'); const log4js = require('log4js');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const stats = require('../../node/stats') const stats = require('../../node/stats')
const logger = log4js.getLogger('socket.io'); const logger = log4js.getLogger('socket.io');

View file

@ -14,7 +14,7 @@ import fs from 'fs';
const hooks = require('../../static/js/pluginfw/hooks'); const hooks = require('../../static/js/pluginfw/hooks');
import log4js from 'log4js'; import log4js from 'log4js';
const SessionStore = require('../db/SessionStore'); const SessionStore = require('../db/SessionStore');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
const stats = require('../stats') const stats = require('../stats')
import util from 'util'; import util from 'util';
const webaccess = require('./express/webaccess'); const webaccess = require('./express/webaccess');
@ -190,6 +190,7 @@ exports.restartServer = async () => {
secretRotator = new SecretRotator( secretRotator = new SecretRotator(
'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey); 'expressSessionSecrets', keyRotationInterval, sessionLifetime, settings.sessionKey);
await secretRotator.start(); await secretRotator.start();
// @ts-ignore
secret = secretRotator.secrets; secret = secretRotator.secrets;
} }
if (!secret) throw new Error('missing cookie signing secret'); if (!secret) throw new Error('missing cookie signing secret');

View file

@ -5,7 +5,7 @@ import fs from "fs";
import * as url from "node:url"; import * as url from "node:url";
import {MapArrayType} from "../../types/MapType"; import {MapArrayType} from "../../types/MapType";
const settings = require('ep_etherpad-lite/node/utils/Settings'); import settings from 'ep_etherpad-lite/node/utils/Settings';
const ADMIN_PATH = path.join(settings.root, 'src', 'templates'); const ADMIN_PATH = path.join(settings.root, 'src', 'templates');
const PROXY_HEADER = "x-proxy-path" const PROXY_HEADER = "x-proxy-path"

View file

@ -9,7 +9,7 @@ const eejs = require('../../eejs');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
const plugins = require('../../../static/js/pluginfw/plugins'); const plugins = require('../../../static/js/pluginfw/plugins');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
const UpdateCheck = require('../../utils/UpdateCheck'); const UpdateCheck = require('../../utils/UpdateCheck');
const padManager = require('../../db/PadManager'); const padManager = require('../../db/PadManager');
const api = require('../../db/API'); const api = require('../../db/API');

View file

@ -3,7 +3,7 @@
import {ArgsExpressType} from "../../types/ArgsExpressType"; import {ArgsExpressType} from "../../types/ArgsExpressType";
const hasPadAccess = require('../../padaccess'); const hasPadAccess = require('../../padaccess');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
const exportHandler = require('../../handler/ExportHandler'); const exportHandler = require('../../handler/ExportHandler');
const importHandler = require('../../handler/ImportHandler'); const importHandler = require('../../handler/ImportHandler');
const padManager = require('../../db/PadManager'); const padManager = require('../../db/PadManager');

View file

@ -24,7 +24,7 @@ const cloneDeep = require('lodash.clonedeep');
const createHTTPError = require('http-errors'); const createHTTPError = require('http-errors');
const apiHandler = require('../../handler/APIHandler'); const apiHandler = require('../../handler/APIHandler');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
const log4js = require('log4js'); const log4js = require('log4js');
const logger = log4js.getLogger('API'); const logger = log4js.getLogger('API');

View file

@ -6,7 +6,7 @@ import events from 'events';
const express = require('../express'); const express = require('../express');
import log4js from 'log4js'; import log4js from 'log4js';
const proxyaddr = require('proxy-addr'); const proxyaddr = require('proxy-addr');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
import {Server, Socket} from 'socket.io' import {Server, Socket} from 'socket.io'
const socketIORouter = require('../../handler/SocketIORouter'); const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');

View file

@ -6,7 +6,7 @@ import fs from 'node:fs';
const fsp = fs.promises; const fsp = fs.promises;
const toolbar = require('../../utils/toolbar'); const toolbar = require('../../utils/toolbar');
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
import util from 'node:util'; import util from 'node:util';
const webaccess = require('./webaccess'); const webaccess = require('./webaccess');
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
@ -40,7 +40,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
app.get('/robots.txt', (req:any, res:any) => { app.get('/robots.txt', (req:any, res:any) => {
let filePath = let filePath =
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'robots.txt'); path.join(settings.root, 'src', 'static', 'skins', settings.skinName!, 'robots.txt');
res.sendFile(filePath, (err:any) => { res.sendFile(filePath, (err:any) => {
// there is no custom robots.txt, send the default robots.txt which dissallows all // there is no custom robots.txt, send the default robots.txt which dissallows all
if (err) { if (err) {
@ -64,7 +64,7 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
const fns = [ const fns = [
...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []), ...(settings.favicon ? [path.resolve(settings.root, settings.favicon)] : []),
path.join(settings.root, 'src', 'static', 'skins', settings.skinName, 'favicon.ico'), path.join(settings.root, 'src', 'static', 'skins', settings.skinName!, 'favicon.ico'),
path.join(settings.root, 'src', 'static', 'favicon.ico'), path.join(settings.root, 'src', 'static', 'favicon.ico'),
]; ];
for (const fn of fns) { for (const fn of fns) {
@ -174,6 +174,7 @@ const handleLiveReload = async (args: any, padString: string, timeSliderString:
req, req,
toolbar, toolbar,
isReadOnly, isReadOnly,
settings: settings,
entrypoint: '/watch/pad?hash=' + hash entrypoint: '/watch/pad?hash=' + hash
}) })
res.send(content); res.send(content);
@ -203,6 +204,7 @@ const handleLiveReload = async (args: any, padString: string, timeSliderString:
req, req,
toolbar, toolbar,
isReadOnly, isReadOnly,
settings: settings,
entrypoint: '/watch/timeslider?hash=' + hash entrypoint: '/watch/timeslider?hash=' + hash
}) })
res.send(content); res.send(content);

View file

@ -7,7 +7,7 @@ const fs = require('fs').promises;
import {minify} from '../../utils/Minify'; import {minify} from '../../utils/Minify';
import path from 'node:path'; import path from 'node:path';
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
// Rewrite tar to include modules with no extensions and proper rooted paths. // Rewrite tar to include modules with no extensions and proper rooted paths.
const getTar = async () => { const getTar = async () => {

View file

@ -7,7 +7,7 @@ const path = require('path');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const plugins = require('../../../static/js/pluginfw/plugin_defs'); const plugins = require('../../../static/js/pluginfw/plugin_defs');
const sanitizePathname = require('../../utils/sanitizePathname'); const sanitizePathname = require('../../utils/sanitizePathname');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
// Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/' // Returns all *.js files under specDir (recursively) as relative paths to specDir, using '/'
// instead of path.sep to separate pathname components. // instead of path.sep to separate pathname components.

View file

@ -6,7 +6,7 @@ import {SocketClientRequest} from "../../types/SocketClientRequest";
import {WebAccessTypes} from "../../types/WebAccessTypes"; import {WebAccessTypes} from "../../types/WebAccessTypes";
import {SettingsUser} from "../../types/SettingsUser"; import {SettingsUser} from "../../types/SettingsUser";
const httpLogger = log4js.getLogger('http'); const httpLogger = log4js.getLogger('http');
const settings = require('../../utils/Settings'); import settings from '../../utils/Settings';
const hooks = require('../../../static/js/pluginfw/hooks'); const hooks = require('../../../static/js/pluginfw/hooks');
const readOnlyManager = require('../../db/ReadOnlyManager'); const readOnlyManager = require('../../db/ReadOnlyManager');

View file

@ -9,7 +9,7 @@ const path = require('path');
const _ = require('underscore'); const _ = require('underscore');
const pluginDefs = require('../../static/js/pluginfw/plugin_defs'); const pluginDefs = require('../../static/js/pluginfw/plugin_defs');
const existsSync = require('../utils/path_exists'); const existsSync = require('../utils/path_exists');
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
// returns all existing messages merged together and grouped by langcode // returns all existing messages merged together and grouped by langcode
// {es: {"foo": "string"}, en:...} // {es: {"foo": "string"}, en:...}

View file

@ -3,7 +3,7 @@ import Provider, {Account, Configuration} from 'oidc-provider';
import {generateKeyPair, exportJWK, KeyLike} from 'jose' import {generateKeyPair, exportJWK, KeyLike} from 'jose'
import MemoryAdapter from "./OIDCAdapter"; import MemoryAdapter from "./OIDCAdapter";
import path from "path"; import path from "path";
const settings = require('../utils/Settings'); import settings from '../utils/Settings';
import {IncomingForm} from 'formidable' import {IncomingForm} from 'formidable'
import express, {Request, Response} from 'express'; import express, {Request, Response} from 'express';
import {format} from 'url' import {format} from 'url'
@ -137,7 +137,7 @@ export const expressCreateServer = async (hookName: string, args: ArgsExpressTyp
} else if (token.kind === "ClientCredentials") { } else if (token.kind === "ClientCredentials") {
let extraParams: MapArrayType<string> = {} let extraParams: MapArrayType<string> = {}
settings.sso.clients settings.sso.clients!
.filter((client:any) => client.client_id === token.clientId) .filter((client:any) => client.client_id === token.clientId)
.forEach((client:any) => { .forEach((client:any) => {
if(client.extraParams !== undefined) { if(client.extraParams !== undefined) {

View file

@ -29,7 +29,7 @@ import pkg from '../package.json';
import {checkForMigration} from "../static/js/pluginfw/installer"; import {checkForMigration} from "../static/js/pluginfw/installer";
import axios from "axios"; import axios from "axios";
const settings = require('./utils/Settings'); import settings from './utils/Settings';
let wtfnode: any; let wtfnode: any;
if (settings.dumpOnUncleanExit) { if (settings.dumpOnUncleanExit) {

View file

@ -24,7 +24,7 @@ import {AsyncQueueTask} from "../types/AsyncQueueTask";
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const async = require('async'); const async = require('async');
const settings = require('./Settings'); import settings from './Settings';
const os = require('os'); const os = require('os');
// on windows we have to spawn a process for each convertion, // on windows we have to spawn a process for each convertion,

View file

@ -18,9 +18,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
const log4js = require('log4js'); import log4js from 'log4js';
const path = require('path'); import path from 'path';
const _ = require('underscore'); import _ from 'underscore';
const absPathLogger = log4js.getLogger('AbsolutePaths'); const absPathLogger = log4js.getLogger('AbsolutePaths');
@ -74,7 +74,7 @@ const popIfEndsWith = (stringArray: string[], lastDesiredElements: string[]): st
* @return {string} The identified absolute base path. If such path cannot be * @return {string} The identified absolute base path. If such path cannot be
* identified, prints a log and exits the application. * identified, prints a log and exits the application.
*/ */
exports.findEtherpadRoot = () => { export const findEtherpadRoot = () => {
if (etherpadRoot != null) { if (etherpadRoot != null) {
return etherpadRoot; return etherpadRoot;
} }
@ -130,12 +130,12 @@ exports.findEtherpadRoot = () => {
* it is returned unchanged. Otherwise it is interpreted * it is returned unchanged. Otherwise it is interpreted
* relative to exports.root. * relative to exports.root.
*/ */
exports.makeAbsolute = (somePath: string) => { export const makeAbsolute = (somePath: string) => {
if (path.isAbsolute(somePath)) { if (path.isAbsolute(somePath)) {
return somePath; return somePath;
} }
const rewrittenPath = path.join(exports.findEtherpadRoot(), somePath); const rewrittenPath = path.join(findEtherpadRoot(), somePath);
absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`); absPathLogger.debug(`Relative path "${somePath}" can be rewritten to "${rewrittenPath}"`);
return rewrittenPath; return rewrittenPath;
@ -149,7 +149,7 @@ exports.makeAbsolute = (somePath: string) => {
* a subdirectory of the base one * a subdirectory of the base one
* @return {boolean} * @return {boolean}
*/ */
exports.isSubdir = (parent: string, arbitraryDir: string): boolean => { export const isSubdir = (parent: string, arbitraryDir: string): boolean => {
// modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825 // modified from: https://stackoverflow.com/questions/37521893/determine-if-a-path-is-subdirectory-of-another-in-node-js#45242825
const relative = path.relative(parent, arbitraryDir); const relative = path.relative(parent, arbitraryDir);
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);

View file

@ -21,7 +21,9 @@
*/ */
// An object containing the parsed command-line options // An object containing the parsed command-line options
exports.argv = {}; import {MapArrayType} from "../types/MapType";
export const argvP: MapArrayType<any> = {};
const argv = process.argv.slice(2); const argv = process.argv.slice(2);
let arg, prevArg; let arg, prevArg;
@ -32,22 +34,22 @@ for (let i = 0; i < argv.length; i++) {
// Override location of settings.json file // Override location of settings.json file
if (prevArg === '--settings' || prevArg === '-s') { if (prevArg === '--settings' || prevArg === '-s') {
exports.argv.settings = arg; argvP.settings = arg;
} }
// Override location of credentials.json file // Override location of credentials.json file
if (prevArg === '--credentials') { if (prevArg === '--credentials') {
exports.argv.credentials = arg; argvP.credentials = arg;
} }
// Override location of settings.json file // Override location of settings.json file
if (prevArg === '--sessionkey') { if (prevArg === '--sessionkey') {
exports.argv.sessionkey = arg; argvP.sessionkey = arg;
} }
// Override location of APIKEY.txt file // Override location of APIKEY.txt file
if (prevArg === '--apikey') { if (prevArg === '--apikey') {
exports.argv.apikey = arg; argvP.apikey = arg;
} }
prevArg = arg; prevArg = arg;

View file

@ -23,7 +23,7 @@ const log4js = require('log4js');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const runCmd = require('./run_cmd'); const runCmd = require('./run_cmd');
const settings = require('./Settings'); import settings from './Settings';
const logger = log4js.getLogger('LibreOffice'); const logger = log4js.getLogger('LibreOffice');

View file

@ -26,7 +26,7 @@ import mime from 'mime-types';
import log4js from 'log4js'; import log4js from 'log4js';
import {compressCSS, compressJS} from './MinifyWorker' import {compressCSS, compressJS} from './MinifyWorker'
const settings = require('./Settings'); import settings from './Settings';
import {promises as fs} from 'fs'; import {promises as fs} from 'fs';
import path from 'node:path'; import path from 'node:path';
const plugins = require('../../static/js/pluginfw/plugin_defs'); const plugins = require('../../static/js/pluginfw/plugin_defs');

View file

@ -28,124 +28,111 @@
*/ */
import {MapArrayType} from "../types/MapType"; import {MapArrayType} from "../types/MapType";
import {SettingsNode, SettingsTree} from "./SettingsTree"; import {SettingsNode} from "./SettingsTree";
import {coerce} from "semver"; import {version} from '../../package.json'
import {findEtherpadRoot, isSubdir, makeAbsolute} from './AbsolutePaths';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {argvP} from "./Cli";
import jsonminify from 'jsonminify';
import log4js from 'log4js';
import randomString from './randomstring';
const absolutePaths = require('./AbsolutePaths'); import _ from 'underscore';
const deepEqual = require('fast-deep-equal/es6');
const fs = require('fs');
const os = require('os');
const path = require('path');
const argv = require('./Cli').argv;
const jsonminify = require('jsonminify');
const log4js = require('log4js');
const randomString = require('./randomstring');
const suppressDisableMsg = ' -- To suppress these warning messages change ' +
'suppressErrorsInPadText to true in your settings.json\n';
const _ = require('underscore');
const logger = log4js.getLogger('settings');
// Exported values that settings.json and credentials.json cannot override.
const nonSettings = [
'credentialsFilename',
'settingsFilename',
];
// This is a function to make it easy to create a new instance. It is important to not reuse a class Settings {
// config object after passing it to log4js.configure() because that method mutates the object. :(
const defaultLogConfig = (level: string, layoutType: string) => ({
appenders: {console: {type: 'console', layout: {type: layoutType}}},
categories: {
default: {appenders: ['console'], level},
}
});
const defaultLogLevel = 'INFO';
const defaultLogLayoutType = 'colored';
const initLogging = (config: any) => {
// log4js.configure() modifies exports.logconfig so check for equality first.
log4js.configure(config);
log4js.getLogger('console');
// Overwrites for console output methods
console.debug = logger.debug.bind(logger);
console.log = logger.info.bind(logger);
console.warn = logger.warn.bind(logger);
console.error = logger.error.bind(logger);
};
constructor() {
// Initialize logging as early as possible with reasonable defaults. Logging will be re-initialized // Initialize logging as early as possible with reasonable defaults. Logging will be re-initialized
// with the user's chosen log level and logger config after the settings have been loaded. // with the user's chosen log level and logger config after the settings have been loaded.
initLogging(defaultLogConfig(defaultLogLevel, defaultLogLayoutType)); this.initLogging(this.defaultLogConfig(this.defaultLogLevel));
this.logger.info('All relative paths will be interpreted relative to the identified ' +
/* Root path of the installation */ `Etherpad base dir: ${this.root}`);
exports.root = absolutePaths.findEtherpadRoot(); // initially load settings
logger.info('All relative paths will be interpreted relative to the identified ' + this.reloadSettings();
`Etherpad base dir: ${exports.root}`); }
exports.settingsFilename = absolutePaths.makeAbsolute(argv.settings || 'settings.json');
exports.credentialsFilename = absolutePaths.makeAbsolute(argv.credentials || 'credentials.json');
/** /**
* The app title, visible e.g. in the browser window * The app title, visible e.g. in the browser window
*/ */
exports.title = 'Etherpad'; title = 'Etherpad';
settingsFilename = makeAbsolute(argvP.settings || 'settings.json');
credentialsFilename = makeAbsolute(argvP.credentials || 'credentials.json');
suppressDisableMsg = ' -- To suppress these warning messages change ' +
'suppressErrorsInPadText to true in your settings.json\n';
defaultLogLevel = 'INFO';
private logger = log4js.getLogger('settings');
/* Root path of the installation */
root = findEtherpadRoot();
/** /**
* Pathname of the favicon you want to use. If null, the skin's favicon is * Pathname of the favicon you want to use. If null, the skin's favicon is
* used if one is provided by the skin, otherwise the default Etherpad favicon * used if one is provided by the skin, otherwise the default Etherpad favicon
* is used. If this is a relative path it is interpreted as relative to the * is used. If this is a relative path it is interpreted as relative to the
* Etherpad root directory. * Etherpad root directory.
*/ */
exports.favicon = null; favicon: string|null = null;
// Exported values that settings.json and credentials.json cannot override.
exports.ttl = { nonSettings = [
AccessToken: 1 * 60 * 60, // 1 hour in seconds 'credentialsFilename',
AuthorizationCode: 10 * 60, // 10 minutes in seconds 'settingsFilename',
ClientCredentials: 1 * 60 * 60, // 1 hour in seconds ]
IdToken: 1 * 60 * 60, // 1 hour in seconds
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
}
/* /*
* Skin name. * Skin name.
* *
* Initialized to null, so we can spot an old configuration file and invite the * Initialized to null, so we can spot an old configuration file and invite the
* user to update it before falling back to the default. * user to update it before falling back to the default.
*/ */
exports.skinName = null; skinName: string | null = null;
skinVariants = 'super-light-toolbar super-light-editor light-background';
exports.skinVariants = 'super-light-toolbar super-light-editor light-background'; ttl = {
AccessToken: 1 * 60 * 60, // 1 hour in seconds
AuthorizationCode: 10 * 60, // 10 minutes in seconds
ClientCredentials: 1 * 60 * 60, // 1 hour in seconds
IdToken: 1 * 60 * 60, // 1 hour in seconds
RefreshToken: 1 * 24 * 60 * 60, // 1 day in seconds
}
/** /**
* The IP ep-lite should listen to * Should we suppress Error messages from being in Pad Contents
*/ */
exports.ip = '0.0.0.0'; suppressErrorsInPadText = false;
/** /**
* The Port ep-lite should listen to * The Port ep-lite should listen to
*/ */
exports.port = process.env.PORT || 9001; port = process.env.PORT as unknown as number || 9001;
/** /**
* Should we suppress Error messages from being in Pad Contents * The IP ep-lite should listen to
*/ */
exports.suppressErrorsInPadText = false; ip: string = '0.0.0.0';
// This is a function to make it easy to create a new instance. It is important to not reuse a
// config object after passing it to log4js.configure() because that method mutates the object. :(
private defaultLogConfig = (level: string) => ({
appenders: {console: {type: 'console'}},
categories: {
default: {appenders: ['console'], level},
}
})
/** /**
* The SSL signed server key and the Certificate Authority's own certificate * The SSL signed server key and the Certificate Authority's own certificate
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case. * default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
*/ */
exports.ssl = false; ssl:{
key:string,
cert:string
ca?: string[]
}|false = false;
/** /**
* socket.io transport methods * socket.io transport methods
**/ **/
exports.socketTransportProtocols = ['websocket', 'polling']; socketTransportProtocols: any[] = ['websocket', 'polling'];
socketIo = {
exports.socketIo = {
/** /**
* Maximum permitted client message size (in bytes). * Maximum permitted client message size (in bytes).
* *
@ -157,28 +144,25 @@ exports.socketIo = {
maxHttpBufferSize: 50000, maxHttpBufferSize: 50000,
}; };
/* /*
The authentication method used by the server. The authentication method used by the server.
The default value is sso The default value is sso
If you want to use the old authentication system, change this to apikey If you want to use the old authentication system, change this to apikey
*/ */
exports.authenticationMethod = 'sso' authenticationMethod = 'sso'
/* /*
* The Type of the database * The Type of the database
*/ */
exports.dbType = 'dirty'; dbType = 'dirty';
/** /**
* This setting is passed with dbType to ueberDB to set up the database * This setting is passed with dbType to ueberDB to set up the database
*/ */
exports.dbSettings = {filename: path.join(exports.root, 'var/dirty.db')}; dbSettings = {filename: path.join(this.root, 'var/dirty.db')};
/** /**
* The default Text of a new pad * The default Text of a new pad
*/ */
exports.defaultPadText = [ defaultPadText = [
'Welcome to Etherpad!', 'Welcome to Etherpad!',
'', '',
'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' + 'This pad text is synchronized as you type, so that everyone viewing this page sees the same ' +
@ -187,10 +171,11 @@ exports.defaultPadText = [
'Etherpad on Github: https://github.com/ether/etherpad-lite', 'Etherpad on Github: https://github.com/ether/etherpad-lite',
].join('\n'); ].join('\n');
/** /**
* The default Pad Settings for a user (Can be overridden by changing the setting * The default Pad Settings for a user (Can be overridden by changing the setting
*/ */
exports.padOptions = { padOptions = {
noColors: false, noColors: false,
showControls: true, showControls: true,
showChat: true, showChat: true,
@ -207,7 +192,7 @@ exports.padOptions = {
/** /**
* Whether certain shortcut keys are enabled for a user in the pad * Whether certain shortcut keys are enabled for a user in the pad
*/ */
exports.padShortcutEnabled = { padShortcutEnabled = {
altF9: true, altF9: true,
altC: true, altC: true,
delete: true, delete: true,
@ -235,7 +220,7 @@ exports.padShortcutEnabled = {
/** /**
* The toolbar buttons and order. * The toolbar buttons and order.
*/ */
exports.toolbar = { public toolbar = {
left: [ left: [
['bold', 'italic', 'underline', 'strikethrough'], ['bold', 'italic', 'underline', 'strikethrough'],
['orderedlist', 'unorderedlist', 'indent', 'outdent'], ['orderedlist', 'unorderedlist', 'indent', 'outdent'],
@ -255,92 +240,89 @@ exports.toolbar = {
/** /**
* A flag that requires any user to have a valid session (via the api) before accessing a pad * A flag that requires any user to have a valid session (via the api) before accessing a pad
*/ */
exports.requireSession = false; requireSession = false;
/** /**
* A flag that prevents users from creating new pads * A flag that prevents users from creating new pads
*/ */
exports.editOnly = false; editOnly = false;
/** /**
* Max age that responses will have (affects caching layer). * Max age that responses will have (affects caching layer).
*/ */
exports.maxAge = 1000 * 60 * 60 * 6; // 6 hours maxAge = 1000 * 60 * 60 * 6; // 6 hours
/** /**
* A flag that shows if minification is enabled or not * A flag that shows if minification is enabled or not
*/ */
exports.minify = true; minify = true;
/** /**
* The path of the abiword executable * The path of the abiword executable
*/ */
exports.abiword = null; abiword = null;
/** /**
* The path of the libreoffice executable * The path of the libreoffice executable
*/ */
exports.soffice = null; soffice = null;
/** /**
* Should we support none natively supported file types on import? * Should we support none natively supported file types on import?
*/ */
exports.allowUnknownFileEnds = true; allowUnknownFileEnds = true;
/** /**
* The log level of log4js * The log level of log4js
*/ */
exports.loglevel = defaultLogLevel; loglevel: any = this.defaultLogLevel;
/**
* The log layout type of log4js
*/
exports.logLayoutType = defaultLogLayoutType;
/** /**
* Disable IP logging * Disable IP logging
*/ */
exports.disableIPlogging = false; disableIPlogging = false;
/** /**
* Number of seconds to automatically reconnect pad * Number of seconds to automatically reconnect pad
*/ */
exports.automaticReconnectionTimeout = 0; automaticReconnectionTimeout = 0;
/** /**
* Disable Load Testing * Disable Load Testing
*/ */
exports.loadTest = false; loadTest = false;
/** /**
* Disable dump of objects preventing a clean exit * Disable dump of objects preventing a clean exit
*/ */
exports.dumpOnUncleanExit = false; dumpOnUncleanExit = false;
/** /**
* Enable indentation on new lines * Enable indentation on new lines
*/ */
exports.indentationOnNewLine = true; indentationOnNewLine = true;
/* /*
* log4js appender configuration * log4js appender configuration
*/ */
exports.logconfig = null; private logconfig: { categories: { default: { level: string, appenders: string[] } }, appenders: { console: { type: string } } } | null = null;
/* /*
* Deprecated cookie signing key. * Deprecated cookie signing key.
*/ */
exports.sessionKey = null; sessionKey: string|null = null;
/* /*
* Trust Proxy, whether or not trust the x-forwarded-for header. * Trust Proxy, whether or not trust the x-forwarded-for header.
*/ */
exports.trustProxy = false; trustProxy = false;
/* /*
* Settings controlling the session cookie issued by Etherpad. * Settings controlling the session cookie issued by Etherpad.
*/ */
exports.cookie = { cookie = {
keyRotationInterval: 1 * 24 * 60 * 60 * 1000, keyRotationInterval: 1 * 24 * 60 * 60 * 1000,
/* /*
* Value of the SameSite cookie property. "Lax" is recommended unless * Value of the SameSite cookie property. "Lax" is recommended unless
@ -363,27 +345,31 @@ exports.cookie = {
* authorization. Note: /admin always requires authentication, and * authorization. Note: /admin always requires authentication, and
* either authorization by a module, or a user with is_admin set * either authorization by a module, or a user with is_admin set
*/ */
exports.requireAuthentication = false; requireAuthentication = false;
exports.requireAuthorization = false; requireAuthorization = false;
exports.users = {}; users = {};
/* /*
* This setting is used for configuring sso * This setting is used for configuring sso
*/ */
exports.sso = { sso:{
issuer: string,
clients?: any[],
} = {
issuer: "http://localhost:9001" issuer: "http://localhost:9001"
} }
/* /*
* Show settings in admin page, by default it is true * Show settings in admin page, by default it is true
*/ */
exports.showSettingsInAdminPage = true; public showSettingsInAdminPage = true;
/* /*
* By default, when caret is moved out of viewport, it scrolls the minimum * By default, when caret is moved out of viewport, it scrolls the minimum
* height needed to make this line visible. * height needed to make this line visible.
*/ */
exports.scrollWhenFocusLineIsOutOfViewport = { scrollWhenFocusLineIsOutOfViewport = {
/* /*
* Percentage of viewport height to be additionally scrolled. * Percentage of viewport height to be additionally scrolled.
*/ */
@ -416,12 +402,12 @@ exports.scrollWhenFocusLineIsOutOfViewport = {
* *
* Do not enable on production machines. * Do not enable on production machines.
*/ */
exports.exposeVersion = false; exposeVersion = false;
/* /*
* Override any strings found in locale directories * Override any strings found in locale directories
*/ */
exports.customLocaleStrings = {}; customLocaleStrings = {};
/* /*
* From Etherpad 1.8.3 onwards, import and export of pads is always rate * From Etherpad 1.8.3 onwards, import and export of pads is always rate
@ -432,7 +418,7 @@ exports.customLocaleStrings = {};
* *
* See https://github.com/nfriedly/express-rate-limit for more options * See https://github.com/nfriedly/express-rate-limit for more options
*/ */
exports.importExportRateLimiting = { importExportRateLimiting: { max: number, windowMs?: number } = {
// duration of the rate limit window (milliseconds) // duration of the rate limit window (milliseconds)
windowMs: 90000, windowMs: 90000,
@ -448,7 +434,7 @@ exports.importExportRateLimiting = {
* *
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options * See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/ */
exports.commitRateLimiting = { commitRateLimiting = {
// duration of the rate limit window (seconds) // duration of the rate limit window (seconds)
duration: 1, duration: 1,
@ -462,39 +448,53 @@ exports.commitRateLimiting = {
* *
* File size is specified in bytes. Default is 50 MB. * File size is specified in bytes. Default is 50 MB.
*/ */
exports.importMaxFileSize = 50 * 1024 * 1024; importMaxFileSize = 50 * 1024 * 1024;
/* /*
* Disable Admin UI tests * Disable Admin UI tests
*/ */
exports.enableAdminUITests = false; enableAdminUITests = false;
/* /*
* Enable auto conversion of pad Ids to lowercase. * Enable auto conversion of pad Ids to lowercase.
* e.g. /p/EtHeRpAd to /p/etherpad * e.g. /p/EtHeRpAd to /p/etherpad
*/ */
exports.lowerCasePadIds = false; lowerCasePadIds = false;
randomVersionString: string|null = null;
private initLogging = (config: any) => {
// log4js.configure() modifies logconfig so check for equality first.
log4js.configure(config);
log4js.getLogger('console');
// Overwrites for console output methods
console.debug = this.logger.debug.bind(this.logger);
console.log = this.logger.info.bind(this.logger);
console.warn = this.logger.warn.bind(this.logger);
console.error = this.logger.error.bind(this.logger);
}
// checks if abiword is avaiable // checks if abiword is avaiable
exports.abiwordAvailable = () => { abiwordAvailable = () => {
if (exports.abiword != null) { if (this.abiword != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else { } else {
return 'no'; return 'no';
} }
}; };
exports.sofficeAvailable = () => { sofficeAvailable = () => {
if (exports.soffice != null) { if (this.soffice != null) {
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
} else { } else {
return 'no'; return 'no';
} }
}; };
exports.exportAvailable = () => { exportAvailable = () => {
const abiword = exports.abiwordAvailable(); const abiword = this.abiwordAvailable();
const soffice = exports.sofficeAvailable(); const soffice = this.sofficeAvailable();
if (abiword === 'no' && soffice === 'no') { if (abiword === 'no' && soffice === 'no') {
return 'no'; return 'no';
@ -507,13 +507,13 @@ exports.exportAvailable = () => {
}; };
// Provide git version if available // Provide git version if available
exports.getGitCommit = () => { getGitCommit = () => {
let version = ''; let version = '';
try { try {
let rootPath = exports.root; let rootPath = this.root;
if (fs.lstatSync(`${rootPath}/.git`).isFile()) { if (fs.lstatSync(`${rootPath}/.git`).isFile()) {
rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8'); rootPath = fs.readFileSync(`${rootPath}/.git`, 'utf8');
rootPath = rootPath.split(' ').pop().trim(); rootPath = rootPath.split(' ').pop()!.trim();
} else { } else {
rootPath += '/.git'; rootPath += '/.git';
} }
@ -526,15 +526,13 @@ exports.getGitCommit = () => {
} }
version = version.substring(0, 7); version = version.substring(0, 7);
} catch (e: any) { } catch (e: any) {
logger.warn(`Can't get git version for server header\n${e.message}`); this.logger.warn(`Can't get git version for server header\n${e.message}`);
} }
return version; return version;
}; }
// Return etherpad version from package.json // Return etherpad version from package.json
exports.getEpVersion = () => require('../../package.json').version; getEpVersion = () => version;
/** /**
* Receives a settingsObj and, if the property name is a valid configuration * Receives a settingsObj and, if the property name is a valid configuration
@ -543,32 +541,37 @@ exports.getEpVersion = () => require('../../package.json').version;
* This code refactors a previous version that copied & pasted the same code for * This code refactors a previous version that copied & pasted the same code for
* both "settings.json" and "credentials.json". * both "settings.json" and "credentials.json".
*/ */
const storeSettings = (settingsObj: any) => { private storeSettings = (settingsObj: any) => {
for (const i of Object.keys(settingsObj || {})) { for (const i of Object.keys(settingsObj || {})) {
if (nonSettings.includes(i)) { if (this.nonSettings.includes(i)) {
logger.warn(`Ignoring setting: '${i}'`); this.logger.warn(`Ignoring setting: '${i}'`);
continue; continue;
} }
// test if the setting starts with a lowercase character // test if the setting starts with a lowercase character
if (i.charAt(0).search('[a-z]') !== 0) { if (i.charAt(0).search('[a-z]') !== 0) {
logger.warn(`Settings should start with a lowercase character: '${i}'`); this.logger.warn(`Settings should start with a lowercase character: '${i}'`);
} }
// we know this setting, so we overwrite it // we know this setting, so we overwrite it
// or it's a settings hash, specific to a plugin // or it's a settings hash, specific to a plugin
if (exports[i] !== undefined || i.indexOf('ep_') === 0) { // @ts-ignore
if (this[i] !== undefined || i.indexOf('ep_') === 0) {
if (_.isObject(settingsObj[i]) && !Array.isArray(settingsObj[i])) { if (_.isObject(settingsObj[i]) && !Array.isArray(settingsObj[i])) {
exports[i] = _.defaults(settingsObj[i], exports[i]); // @ts-ignore
this[i] = _.defaults(settingsObj[i], exports[i]);
} else { } else {
exports[i] = settingsObj[i]; // @ts-ignore
this[i] = settingsObj[i];
} }
} else { } else {
// this setting is unknown, output a warning and throw it away // this setting is unknown, output a warning and throw it away
logger.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`); this.logger.warn(`Unknown Setting: '${i}'. This setting doesn't exist or it was removed`);
} }
} }
}; }
/* /*
* If stringValue is a numeric string, or its value is "true" or "false", coerce * If stringValue is a numeric string, or its value is "true" or "false", coerce
@ -582,7 +585,7 @@ const storeSettings = (settingsObj: any) => {
* short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result * short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result
* in the literal string "null", instead. * in the literal string "null", instead.
*/ */
const coerceValue = (stringValue: string) => { private coerceValue = (stringValue: string) => {
// cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number // cooked from https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
// @ts-ignore // @ts-ignore
const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue)); const isNumeric = !isNaN(stringValue) && !isNaN(parseFloat(stringValue) && isFinite(stringValue));
@ -643,7 +646,7 @@ const coerceValue = (stringValue: string) => {
* *
* see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter * see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter
*/ */
const lookupEnvironmentVariables = (obj: MapArrayType<any>) => { private lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
const replaceEnvs = (obj: MapArrayType<any>) => { const replaceEnvs = (obj: MapArrayType<any>) => {
for (let [key, value] of Object.entries(obj)) { for (let [key, value] of Object.entries(obj)) {
/* /*
@ -702,7 +705,7 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
const defaultValue = match[3]; const defaultValue = match[3];
if ((envVarValue === undefined) && (defaultValue === undefined)) { if ((envVarValue === undefined) && (defaultValue === undefined)) {
logger.warn(`Environment variable "${envVarName}" does not contain any value for ` + this.logger.warn(`Environment variable "${envVarName}" does not contain any value for ` +
`configuration key "${key}", and no default was given. Using null. ` + `configuration key "${key}", and no default was given. Using null. ` +
'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' + 'THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF ETHERPAD; you should ' +
'explicitly use "null" as the default if you want to continue to use null.'); 'explicitly use "null" as the default if you want to continue to use null.');
@ -716,10 +719,10 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
} }
if ((envVarValue === undefined) && (defaultValue !== undefined)) { if ((envVarValue === undefined) && (defaultValue !== undefined)) {
logger.debug(`Environment variable "${envVarName}" not found for ` + this.logger.debug(`Environment variable "${envVarName}" not found for ` +
`configuration key "${key}". Falling back to default value.`); `configuration key "${key}". Falling back to default value.`);
obj[key] = coerceValue(defaultValue); obj[key] = this.coerceValue(defaultValue);
continue continue
} }
@ -729,10 +732,10 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
* For numeric and boolean strings let's convert it to proper types before * For numeric and boolean strings let's convert it to proper types before
* returning it, in order to maintain backward compatibility. * returning it, in order to maintain backward compatibility.
*/ */
logger.debug( this.logger.debug(
`Configuration key "${key}" will be read from environment variable "${envVarName}"`); `Configuration key "${key}" will be read from environment variable "${envVarName}"`);
obj[key] = coerceValue(envVarValue!); obj[key] = this.coerceValue(envVarValue!);
} }
return obj return obj
} }
@ -764,7 +767,8 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
const rooting = root.collectFromLeafsUpwards() const rooting = root.collectFromLeafsUpwards()
obj = Object.assign(obj, rooting) obj = Object.assign(obj, rooting)
return obj; return obj;
}; }
/** /**
@ -775,7 +779,7 @@ const lookupEnvironmentVariables = (obj: MapArrayType<any>) => {
* *
* The isSettings variable only controls the error logging. * The isSettings variable only controls the error logging.
*/ */
const parseSettings = (settingsFilename: string, isSettings: boolean) => { private parseSettings = (settingsFilename: string, isSettings: boolean) => {
let settingsStr = ''; let settingsStr = '';
let settingsType, notFoundMessage, notFoundFunction; let settingsType, notFoundMessage, notFoundFunction;
@ -783,11 +787,11 @@ const parseSettings = (settingsFilename: string, isSettings: boolean) => {
if (isSettings) { if (isSettings) {
settingsType = 'settings'; settingsType = 'settings';
notFoundMessage = 'Continuing using defaults!'; notFoundMessage = 'Continuing using defaults!';
notFoundFunction = logger.warn.bind(logger); notFoundFunction = this.logger.warn.bind(this.logger);
} else { } else {
settingsType = 'credentials'; settingsType = 'credentials';
notFoundMessage = 'Ignoring.'; notFoundMessage = 'Ignoring.';
notFoundFunction = logger.info.bind(logger); notFoundFunction = this.logger.info.bind(this.logger);
} }
try { try {
@ -805,145 +809,137 @@ const parseSettings = (settingsFilename: string, isSettings: boolean) => {
const settings = JSON.parse(settingsStr); const settings = JSON.parse(settingsStr);
logger.info(`${settingsType} loaded from: ${settingsFilename}`); this.logger.info(`${settingsType} loaded from: ${settingsFilename}`);
return lookupEnvironmentVariables(settings); return this.lookupEnvironmentVariables(settings);
} catch (e: any) { } catch (e: any) {
logger.error(`There was an error processing your ${settingsType} ` + this.logger.error(`There was an error processing your ${settingsType} ` +
`file from ${settingsFilename}: ${e.message}`); `file from ${settingsFilename}: ${e.message}`);
process.exit(1); process.exit(1);
} }
};
exports.reloadSettings = () => {
const settings = parseSettings(exports.settingsFilename, true);
const credentials = parseSettings(exports.credentialsFilename, false);
storeSettings(settings);
storeSettings(credentials);
// Init logging config
exports.logconfig = defaultLogConfig(
exports.loglevel ? exports.loglevel : defaultLogLevel,
exports.logLayoutType ? exports.logLayoutType : defaultLogLayoutType
);
logger.warn("loglevel: " + exports.loglevel);
logger.warn("logLayoutType: " + exports.logLayoutType);
initLogging(exports.logconfig);
if (!exports.skinName) {
logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
'update your settings.json. Falling back to the default "colibris".');
exports.skinName = 'colibris';
} }
if (!exports.socketTransportProtocols.includes("websocket") || !exports.socketTransportProtocols.includes("polling")) { reloadSettings = () => {
logger.warn("Invalid socketTransportProtocols setting. Please check out settings.json.template and update your settings.json. Falling back to the default ['websocket', 'polling']."); const settings = this.parseSettings(this.settingsFilename, true);
exports.socketTransportProtocols = ['websocket', 'polling']; const credentials = this.parseSettings(this.credentialsFilename, false);
this.storeSettings(settings);
this.storeSettings(credentials);
// Init logging config
this.logconfig = this.defaultLogConfig(this.loglevel ? this.loglevel : this.defaultLogLevel);
this.initLogging(this.logconfig);
if (!this.skinName) {
this.logger.warn('No "skinName" parameter found. Please check out settings.json.template and ' +
'update your settings.json. Falling back to the default "colibris".');
this.skinName = 'colibris';
}
if (!this.socketTransportProtocols.includes("websocket") || this.socketTransportProtocols.includes("polling")) {
this.logger.warn("Invalid socketTransportProtocols setting. Please check out settings.json.template and update your settings.json. Falling back to the default ['websocket', 'polling'].");
this.socketTransportProtocols = ['websocket', 'polling'];
} }
// checks if skinName has an acceptable value, otherwise falls back to "colibris" // checks if skinName has an acceptable value, otherwise falls back to "colibris"
if (exports.skinName) { if (this.skinName) {
const skinBasePath = path.join(exports.root, 'src', 'static', 'skins'); const skinBasePath = path.join(this.root, 'src', 'static', 'skins');
const countPieces = exports.skinName.split(path.sep).length; const countPieces = this.skinName.split(path.sep).length;
if (countPieces !== 1) { if (countPieces !== 1) {
logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` + this.logger.error(`skinName must be the name of a directory under "${skinBasePath}". This is ` +
`not valid: "${exports.skinName}". Falling back to the default "colibris".`); `not valid: "${this.skinName}". Falling back to the default "colibris".`);
this.skinName = 'colibris';
exports.skinName = 'colibris';
} }
// informative variable, just for the log messages // informative variable, just for the log messages
let skinPath = path.join(skinBasePath, exports.skinName); let skinPath = path.join(skinBasePath, this.skinName);
// what if someone sets skinName == ".." or "."? We catch him! // what if someone sets skinName == ".." or "."? We catch him!
if (absolutePaths.isSubdir(skinBasePath, skinPath) === false) { if (isSubdir(skinBasePath, skinPath) === false) {
logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` + this.logger.error(`Skin path ${skinPath} must be a subdirectory of ${skinBasePath}. ` +
'Falling back to the default "colibris".'); 'Falling back to the default "colibris".');
exports.skinName = 'colibris'; this.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName); skinPath = path.join(skinBasePath, this.skinName);
} }
if (fs.existsSync(skinPath) === false) { if (fs.existsSync(skinPath) === false) {
logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`); this.logger.error(`Skin path ${skinPath} does not exist. Falling back to the default "colibris".`);
exports.skinName = 'colibris'; this.skinName = 'colibris';
skinPath = path.join(skinBasePath, exports.skinName); skinPath = path.join(skinBasePath,this.skinName);
} }
logger.info(`Using skin "${exports.skinName}" in dir: ${skinPath}`); this.logger.info(`Using skin "${this.skinName}" in dir: ${skinPath}`);
} }
if (exports.abiword) { if (this.abiword) {
// Check abiword actually exists // Check abiword actually exists
if (exports.abiword != null) { if (this.abiword != null) {
fs.exists(exports.abiword, (exists: boolean) => { let exists = fs.existsSync(this.abiword)
if (!exists) { if (!exists) {
const abiwordError = 'Abiword does not exist at this path, check your settings file.'; const abiwordError = 'Abiword does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) { if (!this.suppressErrorsInPadText) {
exports.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`; this.defaultPadText += `\nError: ${abiwordError}${this.suppressDisableMsg}`;
} }
logger.error(`${abiwordError} File location: ${exports.abiword}`); this.logger.error(`${abiwordError} File location: ${this.abiword}`);
exports.abiword = null; this.abiword = null;
} }
});
} }
} }
if (exports.soffice) { if (this.soffice) {
fs.exists(exports.soffice, (exists: boolean) => { let exists = fs.existsSync(this.soffice)
if (!exists) { if (!exists) {
const sofficeError = const sofficeError =
'soffice (libreoffice) does not exist at this path, check your settings file.'; 'soffice (libreoffice) does not exist at this path, check your settings file.';
if (!exports.suppressErrorsInPadText) { if (!this.suppressErrorsInPadText) {
exports.defaultPadText += `\nError: ${sofficeError}${suppressDisableMsg}`; this.defaultPadText += `\nError: ${sofficeError}${this.suppressDisableMsg}`;
} }
logger.error(`${sofficeError} File location: ${exports.soffice}`); this.logger.error(`${sofficeError} File location: ${this.soffice}`);
exports.soffice = null; this.soffice = null;
} }
});
} }
const sessionkeyFilename = absolutePaths.makeAbsolute(argv.sessionkey || './SESSIONKEY.txt'); const sessionkeyFilename = makeAbsolute(argvP.sessionkey || './SESSIONKEY.txt');
if (!exports.sessionKey) { if (!this.sessionKey) {
try { try {
exports.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8'); this.sessionKey = fs.readFileSync(sessionkeyFilename, 'utf8');
logger.info(`Session key loaded from: ${sessionkeyFilename}`); this.logger.info(`Session key loaded from: ${sessionkeyFilename}`);
} catch (err) { /* ignored */ } catch (err) { /* ignored */
} }
const keyRotationEnabled = exports.cookie.keyRotationInterval && exports.cookie.sessionLifetime; const keyRotationEnabled = this.cookie.keyRotationInterval && this.cookie.sessionLifetime;
if (!exports.sessionKey && !keyRotationEnabled) { if (!this.sessionKey && !keyRotationEnabled) {
logger.info( this.logger.info(
`Session key file "${sessionkeyFilename}" not found. Creating with random contents.`); `Session key file "${sessionkeyFilename}" not found. Creating with random contents.`);
exports.sessionKey = randomString(32); this.sessionKey = randomString(32);
fs.writeFileSync(sessionkeyFilename, exports.sessionKey, 'utf8'); fs.writeFileSync(sessionkeyFilename, this.sessionKey, 'utf8');
} }
} else { } else {
logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' + this.logger.warn('Declaring the sessionKey in the settings.json is deprecated. ' +
'This value is auto-generated now. Please remove the setting from the file. -- ' + 'This value is auto-generated now. Please remove the setting from the file. -- ' +
'If you are seeing this error after restarting using the Admin User ' + 'If you are seeing this error after restarting using the Admin User ' +
'Interface then you can ignore this message.'); 'Interface then you can ignore this message.');
} }
if (exports.sessionKey) { if (this.sessionKey) {
logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` + this.logger.warn(`The sessionKey setting and ${sessionkeyFilename} file are deprecated; ` +
'use automatic key rotation instead (see the cookie.keyRotationInterval setting).'); 'use automatic key rotation instead (see the cookie.keyRotationInterval setting).');
} }
if (exports.dbType === 'dirty') { if (this.dbType === 'dirty') {
const dirtyWarning = 'DirtyDB is used. This is not recommended for production.'; const dirtyWarning = 'DirtyDB is used. This is not recommended for production.';
if (!exports.suppressErrorsInPadText) { if (!this.suppressErrorsInPadText) {
exports.defaultPadText += `\nWarning: ${dirtyWarning}${suppressDisableMsg}`; this.defaultPadText += `\nWarning: ${dirtyWarning}${this.suppressDisableMsg}`;
} }
exports.dbSettings.filename = absolutePaths.makeAbsolute(exports.dbSettings.filename); this.dbSettings.filename = makeAbsolute(this.dbSettings.filename);
logger.warn(`${dirtyWarning} File location: ${exports.dbSettings.filename}`); this.logger.warn(`${dirtyWarning} File location: ${this.dbSettings.filename}`);
} }
if (exports.ip === '') { if (this.ip === '') {
// using Unix socket for connectivity // using Unix socket for connectivity
logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' + this.logger.warn('The settings file contains an empty string ("") for the "ip" parameter. The ' +
'"port" parameter will be interpreted as the path to a Unix socket to bind at.'); '"port" parameter will be interpreted as the path to a Unix socket to bind at.');
} }
@ -958,13 +954,13 @@ exports.reloadSettings = () => {
* ACHTUNG: this may prevent caching HTTP proxies to work * ACHTUNG: this may prevent caching HTTP proxies to work
* TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead * TODO: remove the "?v=randomstring" parameter, and replace with hashed filenames instead
*/ */
exports.randomVersionString = randomString(4); this.randomVersionString = randomString(4);
logger.info(`Random string used for versioning assets: ${exports.randomVersionString}`); this.logger.info(`Random string used for versioning assets: ${this.randomVersionString}`);
}; };
}
const settings = new Settings()
export default settings
exports.exportedForTestingOnly = {
parseSettings,
};
// initially load settings
exports.reloadSettings();

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const semver = require('semver'); const semver = require('semver');
const settings = require('./Settings'); import settings from './Settings';
import axios from 'axios'; import axios from 'axios';
const headers = { const headers = {
'User-Agent': 'Etherpad/' + settings.getEpVersion(), 'User-Agent': 'Etherpad/' + settings.getEpVersion(),

View file

@ -3,8 +3,8 @@
* Generates a random String with the given length. Is needed to generate the * Generates a random String with the given length. Is needed to generate the
* Author, Group, readonly, session Ids * Author, Group, readonly, session Ids
*/ */
const cryptoMod = require('crypto'); import cryptoMod from 'crypto';
const randomString = (len: number) => cryptoMod.randomBytes(len).toString('hex'); const randomString = (len: number) => cryptoMod.randomBytes(len).toString('hex');
module.exports = randomString; export default randomString

View file

@ -8,7 +8,7 @@ import {Readable} from "node:stream";
const spawn = require('cross-spawn'); const spawn = require('cross-spawn');
const log4js = require('log4js'); const log4js = require('log4js');
const path = require('path'); const path = require('path');
const settings = require('./Settings'); import settings from './Settings';
const logger = log4js.getLogger('runCmd'); const logger = log4js.getLogger('runCmd');

View file

@ -88,6 +88,7 @@
"@types/jquery": "^3.5.30", "@types/jquery": "^3.5.30",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/jsdom": "^21.1.7", "@types/jsdom": "^21.1.7",
"@types/jsonminify": "^0.4.3",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@types/mocha": "^10.0.7", "@types/mocha": "^10.0.7",

View file

@ -4,7 +4,7 @@ import {node_modules, pluginInstallPath} from "./installer";
import {accessSync, constants, rmSync, symlinkSync, unlinkSync} from "node:fs"; import {accessSync, constants, rmSync, symlinkSync, unlinkSync} from "node:fs";
import {dependencies, name} from '../../../package.json' import {dependencies, name} from '../../../package.json'
import {pathToFileURL} from 'node:url'; import {pathToFileURL} from 'node:url';
const settings = require('../../../node/utils/Settings'); import settings from '../../../node/utils/Settings';
import {readFileSync} from "fs"; import {readFileSync} from "fs";
export class LinkInstaller { export class LinkInstaller {

View file

@ -13,7 +13,7 @@ import {promises as fs} from "fs";
const plugins = require('./plugins'); const plugins = require('./plugins');
const hooks = require('./hooks'); const hooks = require('./hooks');
const runCmd = require('../../../node/utils/run_cmd'); const runCmd = require('../../../node/utils/run_cmd');
const settings = require('../../../node/utils/Settings'); import settings from '../../../node/utils/Settings';
import {LinkInstaller} from "./LinkInstaller"; import {LinkInstaller} from "./LinkInstaller";
const {findEtherpadRoot} = require('../../../node/utils/AbsolutePaths'); const {findEtherpadRoot} = require('../../../node/utils/AbsolutePaths');

View file

@ -9,7 +9,7 @@ const runCmd = require('../../../node/utils/run_cmd');
const tsort = require('./tsort'); const tsort = require('./tsort');
const pluginUtils = require('./shared'); const pluginUtils = require('./shared');
const defs = require('./plugin_defs'); const defs = require('./plugin_defs');
const settings = require('../../../node/utils/Settings'); import settings from '../../../node/utils/Settings'
const logger = log4js.getLogger('plugins'); const logger = log4js.getLogger('plugins');

View file

@ -1,6 +1,5 @@
<% <%
var settings = require("ep_etherpad-lite/node/utils/Settings") var langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
, pluginUtils = require('ep_etherpad-lite/static/js/pluginfw/shared') , pluginUtils = require('ep_etherpad-lite/static/js/pluginfw/shared')
; ;
%> %>

View file

@ -1,6 +1,5 @@
<% <%
var settings = require("ep_etherpad-lite/node/utils/Settings") var langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
%> %>
<!doctype html> <!doctype html>
<html translate="no" class="pad <%=settings.skinVariants%>"> <html translate="no" class="pad <%=settings.skinVariants%>">

View file

@ -10,7 +10,7 @@ import padutils from '../../static/js/pad_utils';
const process = require('process'); const process = require('process');
const server = require('../../node/server'); const server = require('../../node/server');
const setCookieParser = require('set-cookie-parser'); const setCookieParser = require('set-cookie-parser');
const settings = require('../../node/utils/Settings'); import settings from '../../node/utils/Settings';
import supertest from 'supertest'; import supertest from 'supertest';
import TestAgent from "supertest/lib/agent"; import TestAgent from "supertest/lib/agent";
import {Http2Server} from "node:http2"; import {Http2Server} from "node:http2";
@ -26,7 +26,7 @@ export let baseUrl:string|null = null;
export let httpServer: Http2Server|null = null; export let httpServer: Http2Server|null = null;
export const logger = log4js.getLogger('test'); export const logger = log4js.getLogger('test');
const logLevel = logger.level; const logLevel = logger.level as log4js.Level;
// Mocha doesn't monitor unhandled Promise rejections, so convert them to uncaught exceptions. // Mocha doesn't monitor unhandled Promise rejections, so convert them to uncaught exceptions.
// https://github.com/mochajs/mocha/issues/2640 // https://github.com/mochajs/mocha/issues/2640