@lordbex/thelounge 4.4.3-blowfish

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.thelounge_home +1 -0
  2. package/LICENSE +22 -0
  3. package/README.md +95 -0
  4. package/client/index.html.tpl +69 -0
  5. package/dist/defaults/config.js +465 -0
  6. package/dist/package.json +174 -0
  7. package/dist/server/client.js +678 -0
  8. package/dist/server/clientManager.js +220 -0
  9. package/dist/server/command-line/index.js +85 -0
  10. package/dist/server/command-line/install.js +123 -0
  11. package/dist/server/command-line/outdated.js +30 -0
  12. package/dist/server/command-line/start.js +34 -0
  13. package/dist/server/command-line/storage.js +103 -0
  14. package/dist/server/command-line/uninstall.js +40 -0
  15. package/dist/server/command-line/upgrade.js +64 -0
  16. package/dist/server/command-line/users/add.js +67 -0
  17. package/dist/server/command-line/users/edit.js +39 -0
  18. package/dist/server/command-line/users/index.js +17 -0
  19. package/dist/server/command-line/users/list.js +53 -0
  20. package/dist/server/command-line/users/remove.js +37 -0
  21. package/dist/server/command-line/users/reset.js +64 -0
  22. package/dist/server/command-line/utils.js +177 -0
  23. package/dist/server/config.js +138 -0
  24. package/dist/server/helper.js +161 -0
  25. package/dist/server/identification.js +139 -0
  26. package/dist/server/index.js +3 -0
  27. package/dist/server/log.js +35 -0
  28. package/dist/server/models/chan.js +275 -0
  29. package/dist/server/models/msg.js +92 -0
  30. package/dist/server/models/network.js +546 -0
  31. package/dist/server/models/prefix.js +31 -0
  32. package/dist/server/models/user.js +42 -0
  33. package/dist/server/plugins/auth/ldap.js +188 -0
  34. package/dist/server/plugins/auth/local.js +41 -0
  35. package/dist/server/plugins/auth.js +70 -0
  36. package/dist/server/plugins/changelog.js +103 -0
  37. package/dist/server/plugins/clientCertificate.js +115 -0
  38. package/dist/server/plugins/dev-server.js +33 -0
  39. package/dist/server/plugins/inputs/action.js +54 -0
  40. package/dist/server/plugins/inputs/away.js +20 -0
  41. package/dist/server/plugins/inputs/ban.js +45 -0
  42. package/dist/server/plugins/inputs/blow.js +44 -0
  43. package/dist/server/plugins/inputs/connect.js +41 -0
  44. package/dist/server/plugins/inputs/ctcp.js +29 -0
  45. package/dist/server/plugins/inputs/disconnect.js +15 -0
  46. package/dist/server/plugins/inputs/ignore.js +74 -0
  47. package/dist/server/plugins/inputs/ignorelist.js +50 -0
  48. package/dist/server/plugins/inputs/index.js +105 -0
  49. package/dist/server/plugins/inputs/invite.js +31 -0
  50. package/dist/server/plugins/inputs/kick.js +26 -0
  51. package/dist/server/plugins/inputs/kill.js +13 -0
  52. package/dist/server/plugins/inputs/list.js +12 -0
  53. package/dist/server/plugins/inputs/mode.js +55 -0
  54. package/dist/server/plugins/inputs/msg.js +106 -0
  55. package/dist/server/plugins/inputs/mute.js +56 -0
  56. package/dist/server/plugins/inputs/nick.js +55 -0
  57. package/dist/server/plugins/inputs/notice.js +42 -0
  58. package/dist/server/plugins/inputs/part.js +46 -0
  59. package/dist/server/plugins/inputs/quit.js +27 -0
  60. package/dist/server/plugins/inputs/raw.js +13 -0
  61. package/dist/server/plugins/inputs/rejoin.js +25 -0
  62. package/dist/server/plugins/inputs/topic.js +24 -0
  63. package/dist/server/plugins/inputs/whois.js +19 -0
  64. package/dist/server/plugins/irc-events/away.js +59 -0
  65. package/dist/server/plugins/irc-events/cap.js +62 -0
  66. package/dist/server/plugins/irc-events/chghost.js +29 -0
  67. package/dist/server/plugins/irc-events/connection.js +152 -0
  68. package/dist/server/plugins/irc-events/ctcp.js +72 -0
  69. package/dist/server/plugins/irc-events/error.js +80 -0
  70. package/dist/server/plugins/irc-events/help.js +21 -0
  71. package/dist/server/plugins/irc-events/info.js +21 -0
  72. package/dist/server/plugins/irc-events/invite.js +27 -0
  73. package/dist/server/plugins/irc-events/join.js +53 -0
  74. package/dist/server/plugins/irc-events/kick.js +39 -0
  75. package/dist/server/plugins/irc-events/link.js +442 -0
  76. package/dist/server/plugins/irc-events/list.js +47 -0
  77. package/dist/server/plugins/irc-events/message.js +187 -0
  78. package/dist/server/plugins/irc-events/mode.js +124 -0
  79. package/dist/server/plugins/irc-events/modelist.js +67 -0
  80. package/dist/server/plugins/irc-events/motd.js +29 -0
  81. package/dist/server/plugins/irc-events/names.js +21 -0
  82. package/dist/server/plugins/irc-events/nick.js +45 -0
  83. package/dist/server/plugins/irc-events/part.js +35 -0
  84. package/dist/server/plugins/irc-events/quit.js +32 -0
  85. package/dist/server/plugins/irc-events/sasl.js +26 -0
  86. package/dist/server/plugins/irc-events/topic.js +42 -0
  87. package/dist/server/plugins/irc-events/unhandled.js +31 -0
  88. package/dist/server/plugins/irc-events/welcome.js +22 -0
  89. package/dist/server/plugins/irc-events/whois.js +57 -0
  90. package/dist/server/plugins/messageStorage/sqlite.js +454 -0
  91. package/dist/server/plugins/messageStorage/text.js +124 -0
  92. package/dist/server/plugins/packages/index.js +200 -0
  93. package/dist/server/plugins/packages/publicClient.js +66 -0
  94. package/dist/server/plugins/packages/themes.js +61 -0
  95. package/dist/server/plugins/storage.js +88 -0
  96. package/dist/server/plugins/sts.js +85 -0
  97. package/dist/server/plugins/uploader.js +267 -0
  98. package/dist/server/plugins/webpush.js +99 -0
  99. package/dist/server/server.js +857 -0
  100. package/dist/server/storageCleaner.js +131 -0
  101. package/dist/server/utils/fish.js +432 -0
  102. package/dist/shared/irc.js +19 -0
  103. package/dist/shared/linkify.js +81 -0
  104. package/dist/shared/types/chan.js +22 -0
  105. package/dist/shared/types/changelog.js +2 -0
  106. package/dist/shared/types/config.js +2 -0
  107. package/dist/shared/types/mention.js +2 -0
  108. package/dist/shared/types/msg.js +34 -0
  109. package/dist/shared/types/network.js +2 -0
  110. package/dist/shared/types/storage.js +2 -0
  111. package/dist/shared/types/user.js +2 -0
  112. package/dist/webpack.config.js +224 -0
  113. package/index.js +38 -0
  114. package/package.json +174 -0
  115. package/public/audio/pop.wav +0 -0
  116. package/public/css/style.css +12 -0
  117. package/public/css/style.css.map +1 -0
  118. package/public/favicon.ico +0 -0
  119. package/public/fonts/fa-solid-900.woff +0 -0
  120. package/public/fonts/fa-solid-900.woff2 +0 -0
  121. package/public/img/favicon-alerted.ico +0 -0
  122. package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
  123. package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
  124. package/public/img/icon-black-transparent-bg.svg +1 -0
  125. package/public/img/logo-grey-bg-120x120px.png +0 -0
  126. package/public/img/logo-grey-bg-152x152px.png +0 -0
  127. package/public/img/logo-grey-bg-167x167px.png +0 -0
  128. package/public/img/logo-grey-bg-180x180px.png +0 -0
  129. package/public/img/logo-grey-bg-192x192px.png +0 -0
  130. package/public/img/logo-grey-bg-512x512px.png +0 -0
  131. package/public/img/logo-grey-bg.svg +1 -0
  132. package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
  133. package/public/img/logo-horizontal-transparent-bg.svg +1 -0
  134. package/public/img/logo-transparent-bg-inverted.svg +1 -0
  135. package/public/img/logo-transparent-bg.svg +1 -0
  136. package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
  137. package/public/img/logo-vertical-transparent-bg.svg +1 -0
  138. package/public/js/bundle.js +2 -0
  139. package/public/js/bundle.js.map +1 -0
  140. package/public/js/bundle.vendor.js +3 -0
  141. package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
  142. package/public/js/bundle.vendor.js.map +1 -0
  143. package/public/js/loading-error-handlers.js +1 -0
  144. package/public/robots.txt +2 -0
  145. package/public/service-worker.js +1 -0
  146. package/public/thelounge.webmanifest +53 -0
  147. package/public/themes/default.css +35 -0
  148. package/public/themes/morning.css +183 -0
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const lodash_1 = __importDefault(require("lodash"));
7
+ const prefix_1 = __importDefault(require("./prefix"));
8
+ class User {
9
+ modes;
10
+ // Users in the channel have only one mode assigned
11
+ mode;
12
+ away;
13
+ nick;
14
+ lastMessage;
15
+ constructor(attr, prefix) {
16
+ lodash_1.default.defaults(this, attr, {
17
+ modes: [],
18
+ away: "",
19
+ nick: "",
20
+ lastMessage: 0,
21
+ });
22
+ Object.defineProperty(this, "mode", {
23
+ get() {
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
25
+ return this.modes[0] || "";
26
+ },
27
+ });
28
+ this.setModes(this.modes, prefix || new prefix_1.default([]));
29
+ }
30
+ setModes(modes, prefix) {
31
+ // irc-framework sets character mode, but The Lounge works with symbols
32
+ this.modes = modes.map((mode) => prefix.modeToSymbol[mode]);
33
+ }
34
+ toJSON() {
35
+ return {
36
+ nick: this.nick,
37
+ modes: this.modes,
38
+ lastMessage: this.lastMessage,
39
+ };
40
+ }
41
+ }
42
+ exports.default = User;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const ldapjs_1 = __importDefault(require("ldapjs"));
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const log_1 = __importDefault(require("../../log"));
9
+ const config_1 = __importDefault(require("../../config"));
10
+ function ldapAuthCommon(user, bindDN, password, callback) {
11
+ const config = config_1.default.values;
12
+ const ldapclient = ldapjs_1.default.createClient({
13
+ url: config.ldap.url,
14
+ tlsOptions: config.ldap.tlsOptions,
15
+ });
16
+ ldapclient.on("error", function (err) {
17
+ log_1.default.error(`Unable to connect to LDAP server: ${err.toString()}`);
18
+ callback(false);
19
+ });
20
+ ldapclient.bind(bindDN, password, function (err) {
21
+ ldapclient.unbind();
22
+ if (err) {
23
+ log_1.default.error(`LDAP bind failed: ${err.toString()}`);
24
+ callback(false);
25
+ }
26
+ else {
27
+ callback(true);
28
+ }
29
+ });
30
+ }
31
+ function simpleLdapAuth(user, password, callback) {
32
+ if (!user || !password) {
33
+ return callback(false);
34
+ }
35
+ const config = config_1.default.values;
36
+ const userDN = user.replace(/([,\\/#+<>;"= ])/g, "\\$1");
37
+ const bindDN = `${config.ldap.primaryKey}=${userDN},${config.ldap.baseDN || ""}`;
38
+ log_1.default.info(`Auth against LDAP ${config.ldap.url} with provided bindDN ${bindDN}`);
39
+ ldapAuthCommon(user, bindDN, password, callback);
40
+ }
41
+ /**
42
+ * LDAP auth using initial DN search (see config comment for ldap.searchDN)
43
+ */
44
+ function advancedLdapAuth(user, password, callback) {
45
+ if (!user || !password) {
46
+ return callback(false);
47
+ }
48
+ const config = config_1.default.values;
49
+ const userDN = user.replace(/([,\\/#+<>;"= ])/g, "\\$1");
50
+ const ldapclient = ldapjs_1.default.createClient({
51
+ url: config.ldap.url,
52
+ tlsOptions: config.ldap.tlsOptions,
53
+ });
54
+ const base = config.ldap.searchDN.base;
55
+ const searchOptions = {
56
+ scope: config.ldap.searchDN.scope,
57
+ filter: `(&(${config.ldap.primaryKey}=${userDN})${config.ldap.searchDN.filter})`,
58
+ attributes: ["dn"],
59
+ };
60
+ ldapclient.on("error", function (err) {
61
+ log_1.default.error(`Unable to connect to LDAP server: ${err.toString()}`);
62
+ callback(false);
63
+ });
64
+ ldapclient.bind(config.ldap.searchDN.rootDN, config.ldap.searchDN.rootPassword, function (err) {
65
+ if (err) {
66
+ log_1.default.error("Invalid LDAP root credentials");
67
+ ldapclient.unbind();
68
+ callback(false);
69
+ return;
70
+ }
71
+ ldapclient.search(base, searchOptions, function (err2, res) {
72
+ if (err2) {
73
+ log_1.default.warn(`LDAP User not found: ${userDN}`);
74
+ ldapclient.unbind();
75
+ callback(false);
76
+ return;
77
+ }
78
+ let found = false;
79
+ res.on("searchEntry", function (entry) {
80
+ found = true;
81
+ const bindDN = entry.objectName;
82
+ log_1.default.info(`Auth against LDAP ${config.ldap.url} with found bindDN ${bindDN || ""}`);
83
+ ldapclient.unbind();
84
+ // TODO: Fix type !
85
+ ldapAuthCommon(user, bindDN, password, callback);
86
+ });
87
+ res.on("error", function (err3) {
88
+ log_1.default.error(`LDAP error: ${err3.toString()}`);
89
+ callback(false);
90
+ });
91
+ res.on("end", function (result) {
92
+ ldapclient.unbind();
93
+ if (!found) {
94
+ log_1.default.warn(`LDAP Search did not find anything for: ${userDN} (${result?.status.toString() || "unknown"})`);
95
+ callback(false);
96
+ }
97
+ });
98
+ });
99
+ });
100
+ }
101
+ const ldapAuth = (manager, client, user, password, callback) => {
102
+ // TODO: Enable the use of starttls() as an alternative to ldaps
103
+ // TODO: move this out of here and get rid of `manager` and `client` in
104
+ // auth plugin API
105
+ function callbackWrapper(valid) {
106
+ if (valid && !client) {
107
+ manager.addUser(user, null, true);
108
+ }
109
+ callback(valid);
110
+ }
111
+ let auth;
112
+ if ("baseDN" in config_1.default.values.ldap) {
113
+ auth = simpleLdapAuth;
114
+ }
115
+ else {
116
+ auth = advancedLdapAuth;
117
+ }
118
+ return auth(user, password, callbackWrapper);
119
+ };
120
+ /**
121
+ * Use the LDAP filter from config to check that users still exist before loading them
122
+ * via the supplied callback function.
123
+ */
124
+ function advancedLdapLoadUsers(users, callbackLoadUser) {
125
+ const config = config_1.default.values;
126
+ const ldapclient = ldapjs_1.default.createClient({
127
+ url: config.ldap.url,
128
+ tlsOptions: config.ldap.tlsOptions,
129
+ });
130
+ const base = config.ldap.searchDN.base;
131
+ ldapclient.on("error", function (err) {
132
+ log_1.default.error(`Unable to connect to LDAP server: ${err.toString()}`);
133
+ });
134
+ ldapclient.bind(config.ldap.searchDN.rootDN, config.ldap.searchDN.rootPassword, function (err) {
135
+ if (err) {
136
+ log_1.default.error("Invalid LDAP root credentials");
137
+ return true;
138
+ }
139
+ const remainingUsers = new Set(users);
140
+ const searchOptions = {
141
+ scope: config.ldap.searchDN.scope,
142
+ filter: `${config.ldap.searchDN.filter}`,
143
+ attributes: [config.ldap.primaryKey],
144
+ paged: true,
145
+ };
146
+ ldapclient.search(base, searchOptions, function (err2, res) {
147
+ if (err2) {
148
+ log_1.default.error(`LDAP search error: ${err2?.toString()}`);
149
+ return true;
150
+ }
151
+ res.on("searchEntry", function (entry) {
152
+ const user = entry.attributes[0].vals[0].toString();
153
+ if (remainingUsers.has(user)) {
154
+ remainingUsers.delete(user);
155
+ callbackLoadUser(user);
156
+ }
157
+ });
158
+ res.on("error", function (err3) {
159
+ log_1.default.error(`LDAP error: ${err3.toString()}`);
160
+ });
161
+ res.on("end", function () {
162
+ remainingUsers.forEach((user) => {
163
+ log_1.default.warn(`No account info in LDAP for ${chalk_1.default.bold(user)} but user config file exists`);
164
+ });
165
+ });
166
+ });
167
+ ldapclient.unbind();
168
+ });
169
+ return true;
170
+ }
171
+ function ldapLoadUsers(users, callbackLoadUser) {
172
+ if ("baseDN" in config_1.default.values.ldap) {
173
+ // simple LDAP case can't test for user existence without access to the
174
+ // user's unhashed password, so indicate need to fallback to default
175
+ // loadUser behaviour by returning false
176
+ return false;
177
+ }
178
+ return advancedLdapLoadUsers(users, callbackLoadUser);
179
+ }
180
+ function isLdapEnabled() {
181
+ return !config_1.default.values.public && config_1.default.values.ldap.enable;
182
+ }
183
+ exports.default = {
184
+ moduleName: "ldap",
185
+ auth: ldapAuth,
186
+ isEnabled: isLdapEnabled,
187
+ loadUsers: ldapLoadUsers,
188
+ };
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chalk_1 = __importDefault(require("chalk"));
7
+ const log_1 = __importDefault(require("../../log"));
8
+ const helper_1 = __importDefault(require("../../helper"));
9
+ const localAuth = (_manager, client, user, password, callback) => {
10
+ // If no user is found, or if the client has not provided a password,
11
+ // fail the authentication straight away
12
+ if (!client || !password) {
13
+ return callback(false);
14
+ }
15
+ // If this user has no password set, fail the authentication
16
+ if (!client.config.password) {
17
+ log_1.default.error(`User ${chalk_1.default.bold(user)} with no local password set tried to sign in. (Probably a LDAP user)`);
18
+ return callback(false);
19
+ }
20
+ helper_1.default.password
21
+ .compare(password, client.config.password)
22
+ .then((matching) => {
23
+ if (matching && helper_1.default.password.requiresUpdate(client.config.password)) {
24
+ const hash = helper_1.default.password.hash(password);
25
+ client.setPassword(hash, (success) => {
26
+ if (success) {
27
+ log_1.default.info(`User ${chalk_1.default.bold(client.name)} logged in and their hashed password has been updated to match new security requirements`);
28
+ }
29
+ });
30
+ }
31
+ callback(matching);
32
+ })
33
+ .catch((error) => {
34
+ log_1.default.error(`Error while checking users password. Error: ${error}`);
35
+ });
36
+ };
37
+ exports.default = {
38
+ moduleName: "local",
39
+ auth: localAuth,
40
+ isEnabled: () => true,
41
+ };
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const chalk_1 = __importDefault(require("chalk"));
30
+ const log_1 = __importDefault(require("../log"));
31
+ // The order defines priority: the first available plugin is used.
32
+ // Always keep 'local' auth plugin at the end of the list; it should always be enabled.
33
+ const plugins = [Promise.resolve().then(() => __importStar(require("./auth/ldap"))), Promise.resolve().then(() => __importStar(require("./auth/local")))];
34
+ const toExport = {
35
+ moduleName: "<module with no name>",
36
+ // Must override: implements authentication mechanism
37
+ auth: () => unimplemented("auth"),
38
+ // Optional to override: implements filter for loading users at start up
39
+ // This allows an auth plugin to check if a user is still acceptable, if the plugin
40
+ // can do so without access to the user's unhashed password.
41
+ // Returning 'false' triggers fallback to default behaviour of loading all users
42
+ loadUsers: () => false,
43
+ // local auth should always be enabled, but check here to verify
44
+ initialized: false,
45
+ // TODO: fix typing
46
+ async initialize() {
47
+ if (toExport.initialized) {
48
+ return;
49
+ }
50
+ // Override default API stubs with exports from first enabled plugin found
51
+ const resolvedPlugins = await Promise.all(plugins);
52
+ for (const { default: plugin } of resolvedPlugins) {
53
+ if (plugin.isEnabled()) {
54
+ toExport.initialized = true;
55
+ for (const name in plugin) {
56
+ toExport[name] = plugin[name];
57
+ }
58
+ break;
59
+ }
60
+ }
61
+ if (!toExport.initialized) {
62
+ log_1.default.error("None of the auth plugins is enabled");
63
+ }
64
+ },
65
+ };
66
+ function unimplemented(funcName) {
67
+ log_1.default.debug(`Auth module ${chalk_1.default.bold(toExport.moduleName)} doesn't implement function ${chalk_1.default.bold(funcName)}`);
68
+ }
69
+ // Default API implementations
70
+ exports.default = toExport;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const got_1 = __importDefault(require("got"));
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const log_1 = __importDefault(require("../log"));
9
+ const package_json_1 = __importDefault(require("../../package.json"));
10
+ const config_1 = __importDefault(require("../config"));
11
+ const TIME_TO_LIVE = 15 * 60 * 1000; // 15 minutes, in milliseconds
12
+ exports.default = {
13
+ isUpdateAvailable: false,
14
+ fetch,
15
+ checkForUpdates,
16
+ };
17
+ const versions = {
18
+ current: {
19
+ prerelease: false,
20
+ version: `v${package_json_1.default.version}`,
21
+ changelog: undefined,
22
+ url: "", // TODO: properly init
23
+ },
24
+ expiresAt: -1,
25
+ latest: undefined,
26
+ packages: undefined,
27
+ };
28
+ async function fetch() {
29
+ const time = Date.now();
30
+ // Serving information from cache
31
+ if (versions.expiresAt > time) {
32
+ return versions;
33
+ }
34
+ try {
35
+ const response = await (0, got_1.default)("https://api.github.com/repos/thelounge/thelounge/releases", {
36
+ headers: {
37
+ Accept: "application/vnd.github.v3.html", // Request rendered markdown
38
+ "User-Agent": package_json_1.default.name + "; +" + package_json_1.default.repository.url, // Identify the client
39
+ },
40
+ localAddress: config_1.default.values.bind,
41
+ });
42
+ if (response.statusCode !== 200) {
43
+ return versions;
44
+ }
45
+ updateVersions(response);
46
+ // Add expiration date to the data to send to the client for later refresh
47
+ versions.expiresAt = time + TIME_TO_LIVE;
48
+ }
49
+ catch (error) {
50
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
51
+ log_1.default.error(`Failed to fetch changelog: ${error}`);
52
+ }
53
+ return versions;
54
+ }
55
+ function updateVersions(response) {
56
+ let i;
57
+ let release;
58
+ let prerelease = false;
59
+ const body = JSON.parse(response.body);
60
+ // Find the current release among releases on GitHub
61
+ for (i = 0; i < body.length; i++) {
62
+ release = body[i];
63
+ if (release.tag_name === versions.current.version) {
64
+ versions.current.changelog = release.body_html;
65
+ prerelease = release.prerelease;
66
+ break;
67
+ }
68
+ }
69
+ // Find the latest release made after the current one if there is one
70
+ if (i > 0) {
71
+ for (let j = 0; j < i; j++) {
72
+ release = body[j];
73
+ // Find latest release or pre-release if current version is also a pre-release
74
+ if (!release.prerelease || release.prerelease === prerelease) {
75
+ module.exports.isUpdateAvailable = true;
76
+ versions.latest = {
77
+ prerelease: release.prerelease,
78
+ version: release.tag_name,
79
+ url: release.html_url,
80
+ };
81
+ break;
82
+ }
83
+ }
84
+ }
85
+ }
86
+ function checkForUpdates(manager) {
87
+ fetch()
88
+ .then((versionData) => {
89
+ if (!module.exports.isUpdateAvailable) {
90
+ // Check for updates every 24 hours + random jitter of <3 hours
91
+ setTimeout(() => checkForUpdates(manager), 24 * 3600 * 1000 + Math.floor(Math.random() * 10000000));
92
+ }
93
+ if (!versionData.latest) {
94
+ return;
95
+ }
96
+ log_1.default.info(`The Lounge ${chalk_1.default.green(versionData.latest.version)} is available. Read more on GitHub: ${versionData.latest.url}`);
97
+ // Notify all connected clients about the new version
98
+ manager.clients.forEach((client) => client.emit("changelog:newversion"));
99
+ })
100
+ .catch((error) => {
101
+ log_1.default.error(`Failed to check for updates: ${error.message}`);
102
+ });
103
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const node_forge_1 = require("node-forge");
10
+ const log_1 = __importDefault(require("../log"));
11
+ const config_1 = __importDefault(require("../config"));
12
+ exports.default = {
13
+ get,
14
+ remove,
15
+ };
16
+ function get(uuid) {
17
+ if (config_1.default.values.public) {
18
+ return null;
19
+ }
20
+ const folderPath = config_1.default.getClientCertificatesPath();
21
+ const paths = getPaths(folderPath, uuid);
22
+ if (!fs_1.default.existsSync(paths.privateKeyPath) || !fs_1.default.existsSync(paths.certificatePath)) {
23
+ return generateAndWrite(folderPath, paths);
24
+ }
25
+ try {
26
+ return {
27
+ private_key: fs_1.default.readFileSync(paths.privateKeyPath, "utf-8"),
28
+ certificate: fs_1.default.readFileSync(paths.certificatePath, "utf-8"),
29
+ };
30
+ }
31
+ catch (e) {
32
+ log_1.default.error("Unable to get certificate", e);
33
+ }
34
+ return null;
35
+ }
36
+ function remove(uuid) {
37
+ if (config_1.default.values.public) {
38
+ return null;
39
+ }
40
+ const paths = getPaths(config_1.default.getClientCertificatesPath(), uuid);
41
+ try {
42
+ if (fs_1.default.existsSync(paths.privateKeyPath)) {
43
+ fs_1.default.unlinkSync(paths.privateKeyPath);
44
+ }
45
+ if (fs_1.default.existsSync(paths.certificatePath)) {
46
+ fs_1.default.unlinkSync(paths.certificatePath);
47
+ }
48
+ }
49
+ catch (e) {
50
+ log_1.default.error("Unable to remove certificate", e);
51
+ }
52
+ }
53
+ function generateAndWrite(folderPath, paths) {
54
+ const certificate = generate();
55
+ try {
56
+ fs_1.default.mkdirSync(folderPath, { recursive: true });
57
+ fs_1.default.writeFileSync(paths.privateKeyPath, certificate.private_key, {
58
+ mode: 0o600,
59
+ });
60
+ fs_1.default.writeFileSync(paths.certificatePath, certificate.certificate, {
61
+ mode: 0o600,
62
+ });
63
+ return certificate;
64
+ }
65
+ catch (e) {
66
+ log_1.default.error("Unable to write certificate", String(e));
67
+ }
68
+ return null;
69
+ }
70
+ function generate() {
71
+ const keys = node_forge_1.pki.rsa.generateKeyPair(2048);
72
+ const cert = node_forge_1.pki.createCertificate();
73
+ cert.publicKey = keys.publicKey;
74
+ cert.serialNumber = crypto_1.default.randomBytes(16).toString("hex").toUpperCase();
75
+ // Set notBefore a day earlier just in case the time between
76
+ // the client and server is not perfectly in sync
77
+ cert.validity.notBefore = new Date();
78
+ cert.validity.notBefore.setDate(cert.validity.notBefore.getDate() - 1);
79
+ // Set notAfter 100 years into the future just in case
80
+ // the server actually validates this field
81
+ cert.validity.notAfter = new Date();
82
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 100);
83
+ const attrs = [
84
+ {
85
+ name: "commonName",
86
+ value: "The Lounge IRC Client",
87
+ },
88
+ ];
89
+ cert.setSubject(attrs);
90
+ cert.setIssuer(attrs);
91
+ // Set extensions that indicate this is a client authentication certificate
92
+ cert.setExtensions([
93
+ {
94
+ name: "extKeyUsage",
95
+ clientAuth: true,
96
+ },
97
+ {
98
+ name: "nsCertType",
99
+ client: true,
100
+ },
101
+ ]);
102
+ // Sign this certificate with a SHA256 signature
103
+ cert.sign(keys.privateKey, node_forge_1.md.sha256.create());
104
+ const pem = {
105
+ private_key: node_forge_1.pki.privateKeyToPem(keys.privateKey),
106
+ certificate: node_forge_1.pki.certificateToPem(cert),
107
+ };
108
+ return pem;
109
+ }
110
+ function getPaths(folderPath, uuid) {
111
+ return {
112
+ privateKeyPath: path_1.default.join(folderPath, `${uuid}.pem`),
113
+ certificatePath: path_1.default.join(folderPath, `${uuid}.crt`),
114
+ };
115
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const webpack_dev_middleware_1 = __importDefault(require("webpack-dev-middleware"));
7
+ const webpack_hot_middleware_1 = __importDefault(require("webpack-hot-middleware"));
8
+ const log_1 = __importDefault(require("../log"));
9
+ const webpack_1 = __importDefault(require("webpack"));
10
+ const webpack_config_1 = __importDefault(require("../../webpack.config"));
11
+ exports.default = (app) => {
12
+ log_1.default.debug("Starting server in development mode");
13
+ const webpackConfig = (0, webpack_config_1.default)(undefined, { mode: "production" });
14
+ if (!webpackConfig ||
15
+ !webpackConfig.plugins?.length ||
16
+ !webpackConfig.entry ||
17
+ !webpackConfig.entry["js/bundle.js"]) {
18
+ throw new Error("No valid production webpack config found");
19
+ }
20
+ webpackConfig.plugins.push(new webpack_1.default.HotModuleReplacementPlugin());
21
+ webpackConfig.entry["js/bundle.js"].push("webpack-hot-middleware/client?path=storage/__webpack_hmr");
22
+ const compiler = (0, webpack_1.default)(webpackConfig);
23
+ app.use(
24
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
25
+ (0, webpack_dev_middleware_1.default)(compiler, {
26
+ index: "/",
27
+ publicPath: webpackConfig.output?.publicPath,
28
+ })).use(
29
+ // TODO: Fix compiler type
30
+ (0, webpack_hot_middleware_1.default)(compiler, {
31
+ path: "/storage/__webpack_hmr",
32
+ }));
33
+ };
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const msg_1 = __importDefault(require("../../models/msg"));
7
+ const msg_2 = require("../../../shared/types/msg");
8
+ const chan_1 = require("../../../shared/types/chan");
9
+ const fish_1 = require("../../utils/fish");
10
+ const commands = ["slap", "me"];
11
+ const input = function ({ irc }, chan, cmd, args) {
12
+ if (chan.type !== chan_1.ChanType.CHANNEL && chan.type !== chan_1.ChanType.QUERY) {
13
+ chan.pushMessage(this, new msg_1.default({
14
+ type: msg_2.MessageType.ERROR,
15
+ text: `${cmd} command can only be used in channels and queries.`,
16
+ }));
17
+ return;
18
+ }
19
+ let text;
20
+ switch (cmd) {
21
+ case "slap":
22
+ text = "slaps " + args[0] + " around a bit with a large trout";
23
+ /* fall through */
24
+ case "me":
25
+ if (args.length === 0) {
26
+ break;
27
+ }
28
+ text = text || args.join(" ");
29
+ // If FiSH key is set, encrypt CTCP ACTION and send as normal PRIVMSG with +OK
30
+ if (chan.blowfishKey) {
31
+ const ctcp = "\x01ACTION " + text + "\x01";
32
+ const toSend = "+OK " + (0, fish_1.fishEncryptPayload)(ctcp, chan.blowfishKey);
33
+ irc.say(chan.name, toSend);
34
+ }
35
+ else {
36
+ irc.action(chan.name, text);
37
+ }
38
+ // If the IRCd does not support echo-message, simulate the message
39
+ // being sent back to us.
40
+ if (!irc.network.cap.isEnabled("echo-message")) {
41
+ irc.emit("action", {
42
+ nick: irc.user.nick,
43
+ target: chan.name,
44
+ message: text,
45
+ });
46
+ }
47
+ break;
48
+ }
49
+ return true;
50
+ };
51
+ exports.default = {
52
+ commands,
53
+ input,
54
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commands = ["away", "back"];
4
+ const input = function (network, chan, cmd, args) {
5
+ let reason = "";
6
+ if (cmd === "away") {
7
+ reason = args.join(" ") || " ";
8
+ network.irc.raw("AWAY", reason);
9
+ }
10
+ else {
11
+ // back command
12
+ network.irc.raw("AWAY");
13
+ }
14
+ network.awayMessage = reason;
15
+ this.save();
16
+ };
17
+ exports.default = {
18
+ commands,
19
+ input,
20
+ };