From 95e3313c65035a93c97e0c32116a8a8e58f61759 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 12 Feb 2022 10:53:34 +0100 Subject: [PATCH 01/11] Make config.defaults an array of networks Instead of a single network. For now, this has no visible effect on the UI, because: * with 'public', only the first network is visible * with 'lockNetwork' but not 'public', other networks are technically allowed by the server, but the client UI does not offer the option * without either, only the first network is visible Future commits will address these points one by one --- client/components/Windows/Connect.vue | 6 +-- defaults/config.js | 58 ++++++++++++++------------- src/client.js | 8 ++-- src/helper.js | 21 ++++++++-- src/models/network.js | 24 +++++++---- src/server.js | 24 +++++------ test/fixtures/.thelounge/config.js | 4 +- test/models/network.js | 47 ++++++++++++++++++++-- 8 files changed, 128 insertions(+), 64 deletions(-) diff --git a/client/components/Windows/Connect.vue b/client/components/Windows/Connect.vue index 11fc5661..5d8b3086 100644 --- a/client/components/Windows/Connect.vue +++ b/client/components/Windows/Connect.vue @@ -18,7 +18,7 @@ export default { // Merge settings from url params into default settings const defaults = Object.assign( {}, - this.$store.state.serverConfiguration.defaults, + this.$store.state.serverConfiguration.defaults[0], this.parseOverrideParams(this.queryParams) ); return { @@ -49,7 +49,7 @@ export default { if ( !Object.prototype.hasOwnProperty.call( - this.$store.state.serverConfiguration.defaults, + this.$store.state.serverConfiguration.defaults[0], key ) ) { @@ -78,7 +78,7 @@ export default { } // Override server provided defaults with parameters passed in the URL if they match the data type - switch (typeof this.$store.state.serverConfiguration.defaults[key]) { + switch (typeof this.$store.state.serverConfiguration.defaults[0][key]) { case "boolean": if (value === "0" || value === "false") { parsedParams[key] = false; diff --git a/defaults/config.js b/defaults/config.js index 89212c39..782a2c2b 100644 --- a/defaults/config.js +++ b/defaults/config.js @@ -204,7 +204,7 @@ module.exports = { // default. leaveMessage: "The Lounge - https://thelounge.chat", - // ## Default network + // ## Default networks // ### `defaults` // @@ -235,37 +235,41 @@ module.exports = { // Libera.Chat by default: // // ```js - // defaults: { - // name: "Libera.Chat", - // host: "irc.libera.chat", - // port: 6697, - // password: "", - // tls: true, - // rejectUnauthorized: true, - // nick: "thelounge%%", - // username: "thelounge", - // realname: "The Lounge User", - // join: "#thelounge" - // } + // defaults: [ + // { + // name: "Libera.Chat", + // host: "irc.libera.chat", + // port: 6697, + // password: "", + // tls: true, + // rejectUnauthorized: true, + // nick: "thelounge%%", + // username: "thelounge", + // realname: "The Lounge User", + // join: "#thelounge" + // }, + // ] // ``` - defaults: { - name: "Libera.Chat", - host: "irc.libera.chat", - port: 6697, - password: "", - tls: true, - rejectUnauthorized: true, - nick: "thelounge%%", - username: "thelounge", - realname: "The Lounge User", - join: "#thelounge", - leaveMessage: "", - }, + defaults: [ + { + name: "Libera.Chat", + host: "irc.libera.chat", + port: 6697, + password: "", + tls: true, + rejectUnauthorized: true, + nick: "thelounge%%", + username: "thelounge", + realname: "The Lounge User", + join: "#thelounge", + leaveMessage: "", + }, + ], // ### `lockNetwork` // // When set to `true`, users will not be able to modify host, port and TLS - // settings and will be limited to the configured network. + // settings and will be limited to the configured networks. // These fields will also be hidden from the UI. // // This value is set to `false` by default. diff --git a/src/client.js b/src/client.js index a4729415..ae3c30a9 100644 --- a/src/client.js +++ b/src/client.js @@ -233,11 +233,13 @@ Client.prototype.connect = function (args, isStartup = false) { }); } + const defaultNetwork = + Helper.getDefaultNetworks().filter((network) => this.name === network.name)[0] || + Helper.getDefaultNetworks()[0]; + const network = new Network({ uuid: args.uuid, - name: String( - args.name || (Helper.config.lockNetwork ? Helper.config.defaults.name : "") || "" - ), + name: String(args.name || (Helper.config.lockNetwork ? defaultNetwork.name : "") || ""), host: String(args.host || ""), port: parseInt(args.port, 10), tls: !!args.tls, diff --git a/src/helper.js b/src/helper.js index 58316b97..b6e08afa 100644 --- a/src/helper.js +++ b/src/helper.js @@ -40,7 +40,8 @@ const Helper = { getGitCommit, ip2hex, mergeConfig, - getDefaultNick, + formatDefaultNick, + getDefaultNetworks, parseHostmask, compareHostmask, compareWithWildcard, @@ -271,12 +272,24 @@ function passwordCompare(password, expected) { return bcrypt.compare(password, expected); } -function getDefaultNick() { - if (!this.config.defaults.nick) { +function formatDefaultNick(format) { + if (!format) { return "thelounge"; } - return this.config.defaults.nick.replace(/%/g, () => Math.floor(Math.random() * 10)); + return format.replace(/%/g, () => Math.floor(Math.random() * 10)); +} + +function getDefaultNetworks() { + if (this.config.defaults === undefined) { + return []; + } + + if (Array.isArray(this.config.defaults)) { + return this.config.defaults; + } + + return [this.config.defaults]; } function mergeConfig(oldConfig, newConfig) { diff --git a/src/models/network.js b/src/models/network.js index 6e3c175e..ba867acc 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -92,7 +92,7 @@ Network.prototype.validate = function (client) { // Remove new lines and limit length const cleanString = (str) => str.replace(/[\x00\r\n]/g, "").substring(0, 300); - this.setNick(cleanNick(String(this.nick || Helper.getDefaultNick()))); + this.setNick(cleanNick(String(this.nick || Helper.formatDefaultNick()))); if (!this.username) { // If username is empty, make one from the provided nick @@ -138,27 +138,35 @@ Network.prototype.validate = function (client) { } if (Helper.config.lockNetwork) { + // Get the first configured network that matches this one, if any. + let defaultNetwork = Helper.getDefaultNetworks().filter( + (network) => this.name === network.name + )[0]; + + // Otherwise, default to the first configured + defaultNetwork = defaultNetwork || Helper.getDefaultNetworks()[0]; + // This check is needed to prevent invalid user configurations if ( !Helper.config.public && this.host && this.host.length > 0 && - this.host !== Helper.config.defaults.host + defaultNetwork === undefined ) { error(this, `The hostname you specified (${this.host}) is not allowed.`); return false; } if (Helper.config.public) { - this.name = Helper.config.defaults.name; + this.name = defaultNetwork.name; // Sync lobby channel name - this.channels[0].name = Helper.config.defaults.name; + this.channels[0].name = defaultNetwork.name; } - this.host = Helper.config.defaults.host; - this.port = Helper.config.defaults.port; - this.tls = Helper.config.defaults.tls; - this.rejectUnauthorized = Helper.config.defaults.rejectUnauthorized; + this.host = defaultNetwork.host; + this.port = defaultNetwork.port; + this.tls = defaultNetwork.tls; + this.rejectUnauthorized = defaultNetwork.rejectUnauthorized; } if (this.host.length === 0) { diff --git a/src/server.js b/src/server.js index f90e50b7..4105a001 100644 --- a/src/server.js +++ b/src/server.js @@ -758,17 +758,11 @@ function getClientConfiguration() { config.ldapEnabled = Helper.config.ldap.enable; if (!config.lockNetwork) { - config.defaults = _.clone(Helper.config.defaults); + config.defaults = _.clone(Helper.getDefaultNetworks()); } else { - // Only send defaults that are visible on the client - config.defaults = _.pick(Helper.config.defaults, [ - "name", - "nick", - "username", - "password", - "realname", - "join", - ]); + config.defaults = Helper.getDefaultNetworks().map((network) => + _.pick(network, ["name", "nick", "username", "password", "realname", "join"]) + ); } config.isUpdateAvailable = changelog.isUpdateAvailable; @@ -777,10 +771,12 @@ function getClientConfiguration() { config.gitCommit = Helper.getGitCommit(); config.themes = themes.getAll(); config.defaultTheme = Helper.config.theme; - config.defaults.nick = Helper.getDefaultNick(); - config.defaults.sasl = ""; - config.defaults.saslAccount = ""; - config.defaults.saslPassword = ""; + config.defaults.forEach((network) => { + network.nick = Helper.formatDefaultNick(network.nick); + network.sasl = ""; + network.saslAccount = ""; + network.saslPassword = ""; + }); if (Uploader) { config.fileUploadMaxFileSize = Uploader.getMaxFileSize(); diff --git a/test/fixtures/.thelounge/config.js b/test/fixtures/.thelounge/config.js index 9767acc9..2c108358 100644 --- a/test/fixtures/.thelounge/config.js +++ b/test/fixtures/.thelounge/config.js @@ -2,8 +2,8 @@ var config = require("../../../defaults/config.js"); -config.defaults.name = "Example IRC Server"; -config.defaults.host = "irc.example.com"; +config.defaults[0].name = "Example IRC Server"; +config.defaults[0].host = "127.0.0.1"; config.public = true; config.prefetch = true; config.host = config.bind = "127.0.0.1"; diff --git a/test/models/network.js b/test/models/network.js index fe8699d9..b8307dc6 100644 --- a/test/models/network.js +++ b/test/models/network.js @@ -63,7 +63,7 @@ describe("Network", function () { }); it("validate should set correct defaults", function () { - Helper.config.defaults.nick = ""; + Helper.config.defaults[0].nick = ""; const network = new Network({ host: "localhost", @@ -96,7 +96,7 @@ describe("Network", function () { rejectUnauthorized: false, }); expect(network.validate()).to.be.true; - expect(network.host).to.equal("irc.example.com"); + expect(network.host).to.equal("127.0.0.1"); expect(network.port).to.equal(6697); expect(network.tls).to.be.true; expect(network.rejectUnauthorized).to.be.true; @@ -108,11 +108,52 @@ describe("Network", function () { host: "some.fake.tld", }); expect(network2.validate()).to.be.true; - expect(network2.host).to.equal("irc.example.com"); + expect(network2.host).to.equal("127.0.0.1"); Helper.config.lockNetwork = false; }); + it("lockNetwork should allow networks that are not the first one", function () { + Helper.config.lockNetwork = true; + Helper.config.defaults.push({ + name: "Other Example Network", + host: "irc2.example.com", + port: 6667, + tls: false, + rejectUnauthorized: false, + }); + + // Make sure we lock in private mode + Helper.config.public = false; + + const network = new Network({ + name: "Other Example Network", + host: "illegal.example.com", + port: 1337, + tls: true, + rejectUnauthorized: true, + }); + expect(network.validate()).to.be.true; + expect(network.host).to.equal("irc2.example.com"); + expect(network.port).to.equal(6667); + expect(network.tls).to.be.false; + expect(network.rejectUnauthorized).to.be.false; + + // Make sure lock in public mode defaults to the first network when + // the hostname does not match (also resets public=true and config.defaults + // for other tests) + Helper.config.public = true; + + const network2 = new Network({ + host: "some.fake.tld", + }); + expect(network2.validate()).to.be.true; + expect(network2.host).to.equal("127.0.0.1"); + + Helper.config.lockNetwork = false; + Helper.config.defaults.pop(); + }); + it("editing a network should enforce correct types", function () { let saveCalled = false; let nameEmitCalled = false; From ffbc2eb580e004959d36e5080571209539428e39 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 12 Feb 2022 11:53:40 +0100 Subject: [PATCH 02/11] Auto-fill with other configured networks when !lockNetwork when selected by name --- client/components/NetworkForm.vue | 16 ++++++++++++++++ client/components/Windows/Connect.vue | 1 + 2 files changed, 17 insertions(+) diff --git a/client/components/NetworkForm.vue b/client/components/NetworkForm.vue index 7eefaefd..49c16510 100644 --- a/client/components/NetworkForm.vue +++ b/client/components/NetworkForm.vue @@ -23,10 +23,18 @@ + +
@@ -463,6 +471,14 @@ export default { this.$nextTick(() => this.$refs.publicPassword.focus()); } }, + "defaults.name"(name) { + for (const defaultNetwork of this.config.defaults) { + if (defaultNetwork.name === name) { + Object.assign(this.defaults, defaultNetwork); + break; + } + } + }, "defaults.commands"() { this.$nextTick(this.resizeCommandsInput); }, diff --git a/client/components/Windows/Connect.vue b/client/components/Windows/Connect.vue index 5d8b3086..e8425d48 100644 --- a/client/components/Windows/Connect.vue +++ b/client/components/Windows/Connect.vue @@ -23,6 +23,7 @@ export default { ); return { disabled: false, + defaultNetworks: this.$store.state.serverConfiguration, defaults, }; }, From 60dea564361d119d3123045b2ee78b1c381a6077 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 12 Feb 2022 12:52:58 +0100 Subject: [PATCH 03/11] Allow selecting a network when lockNetwork && !public As a side-effects, this prevents changing the network name immediately when configuring the network, because the name is used to match the configuration with one of the allowed networks. --- client/components/NetworkForm.vue | 16 +++++++++------- client/components/Windows/Connect.vue | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/client/components/NetworkForm.vue b/client/components/NetworkForm.vue index 49c16510..3e6d9c8a 100644 --- a/client/components/NetworkForm.vue +++ b/client/components/NetworkForm.vue @@ -185,13 +185,15 @@

Network settings

- +
diff --git a/client/components/Windows/Connect.vue b/client/components/Windows/Connect.vue index e8425d48..b1f4b9d4 100644 --- a/client/components/Windows/Connect.vue +++ b/client/components/Windows/Connect.vue @@ -58,11 +58,18 @@ export default { } // When the network is locked, URL overrides should not affect disabled fields - if ( - this.$store.state.serverConfiguration.lockNetwork && - ["name", "host", "port", "tls", "rejectUnauthorized"].includes(key) - ) { - continue; + if (this.$store.state.serverConfiguration.lockNetwork) { + if (["host", "port", "tls", "rejectUnauthorized"].includes(key)) { + continue; + } + + // Network name is only disabled if there is a single network + if ( + this.$store.state.serverConfiguration.defaults.length < 2 && + key === "name" + ) { + continue; + } } if (key === "join") { From 654f8ee7344e2c0f791bb25d253f0a17a7779e74 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 12 Feb 2022 13:07:14 +0100 Subject: [PATCH 04/11] Allow selecting a network when lockNetwork && public --- client/components/NetworkForm.vue | 71 ++++++++++++++++++------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/client/components/NetworkForm.vue b/client/components/NetworkForm.vue index 3e6d9c8a..d87e07d5 100644 --- a/client/components/NetworkForm.vue +++ b/client/components/NetworkForm.vue @@ -181,37 +181,48 @@
-

User preferences

From 50a21eca2d173ebcbd79a272c8df428ec89d8d3a Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Mon, 14 Feb 2022 19:32:48 +0100 Subject: [PATCH 05/11] Log a warning when using a deprecated format for 'defaults'. It looks like this: ``` 2022-02-14 18:32:26 [WARN] Key "defaults" should now be an array of networks, please update your config. It should look like this: defaults: [ { "name": "Libera.Chat", "host": "irc.Libera.Chat", "port": 6697, "password": "", "tls": true, "rejectUnauthorized": true, "nick": "thelounge%%", "username": "thelounge", "realname": "The Lounge User", "join": "#progval", "leaveMessage": "" } ], ``` --- src/helper.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/helper.js b/src/helper.js index b6e08afa..7ebdee90 100644 --- a/src/helper.js +++ b/src/helper.js @@ -280,6 +280,14 @@ function formatDefaultNick(format) { return format.replace(/%/g, () => Math.floor(Math.random() * 10)); } +const warnLegacyDefaultNetworks = _.debounce((defaults) => { + const key = colors.bold("defaults"); + const newDefaults = JSON.stringify([defaults], null, "\t"); + log.warn( + `Key "${key}" should now be an array of networks, please update your config. It should look like this:\ndefaults: ${newDefaults},` + ); +}); + function getDefaultNetworks() { if (this.config.defaults === undefined) { return []; @@ -289,6 +297,7 @@ function getDefaultNetworks() { return this.config.defaults; } + warnLegacyDefaultNetworks(this.config.defaults); return [this.config.defaults]; } From d30a45227d54e36c263447e223d633dff871620b Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Tue, 15 Feb 2022 22:37:53 +0100 Subject: [PATCH 06/11] Change warning message + add link to the doc --- src/helper.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/helper.js b/src/helper.js index 7ebdee90..f830ba8b 100644 --- a/src/helper.js +++ b/src/helper.js @@ -280,14 +280,6 @@ function formatDefaultNick(format) { return format.replace(/%/g, () => Math.floor(Math.random() * 10)); } -const warnLegacyDefaultNetworks = _.debounce((defaults) => { - const key = colors.bold("defaults"); - const newDefaults = JSON.stringify([defaults], null, "\t"); - log.warn( - `Key "${key}" should now be an array of networks, please update your config. It should look like this:\ndefaults: ${newDefaults},` - ); -}); - function getDefaultNetworks() { if (this.config.defaults === undefined) { return []; @@ -297,7 +289,10 @@ function getDefaultNetworks() { return this.config.defaults; } - warnLegacyDefaultNetworks(this.config.defaults); + const key = colors.bold("defaults"); + log.warn( + `Key "${key}" should now be an array of networks. Support for the old object format will be removed in a future version, please update your config. https://thelounge.chat/docs/configuration#default-networks` + ); return [this.config.defaults]; } From eb54de477074170706bb230b5c9182328a21973c Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sat, 5 Mar 2022 12:08:43 +0100 Subject: [PATCH 07/11] When !lockNetwork, use a separate 'Preset'