@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.
- package/.thelounge_home +1 -0
- package/LICENSE +22 -0
- package/README.md +95 -0
- package/client/index.html.tpl +69 -0
- package/dist/defaults/config.js +465 -0
- package/dist/package.json +174 -0
- package/dist/server/client.js +678 -0
- package/dist/server/clientManager.js +220 -0
- package/dist/server/command-line/index.js +85 -0
- package/dist/server/command-line/install.js +123 -0
- package/dist/server/command-line/outdated.js +30 -0
- package/dist/server/command-line/start.js +34 -0
- package/dist/server/command-line/storage.js +103 -0
- package/dist/server/command-line/uninstall.js +40 -0
- package/dist/server/command-line/upgrade.js +64 -0
- package/dist/server/command-line/users/add.js +67 -0
- package/dist/server/command-line/users/edit.js +39 -0
- package/dist/server/command-line/users/index.js +17 -0
- package/dist/server/command-line/users/list.js +53 -0
- package/dist/server/command-line/users/remove.js +37 -0
- package/dist/server/command-line/users/reset.js +64 -0
- package/dist/server/command-line/utils.js +177 -0
- package/dist/server/config.js +138 -0
- package/dist/server/helper.js +161 -0
- package/dist/server/identification.js +139 -0
- package/dist/server/index.js +3 -0
- package/dist/server/log.js +35 -0
- package/dist/server/models/chan.js +275 -0
- package/dist/server/models/msg.js +92 -0
- package/dist/server/models/network.js +546 -0
- package/dist/server/models/prefix.js +31 -0
- package/dist/server/models/user.js +42 -0
- package/dist/server/plugins/auth/ldap.js +188 -0
- package/dist/server/plugins/auth/local.js +41 -0
- package/dist/server/plugins/auth.js +70 -0
- package/dist/server/plugins/changelog.js +103 -0
- package/dist/server/plugins/clientCertificate.js +115 -0
- package/dist/server/plugins/dev-server.js +33 -0
- package/dist/server/plugins/inputs/action.js +54 -0
- package/dist/server/plugins/inputs/away.js +20 -0
- package/dist/server/plugins/inputs/ban.js +45 -0
- package/dist/server/plugins/inputs/blow.js +44 -0
- package/dist/server/plugins/inputs/connect.js +41 -0
- package/dist/server/plugins/inputs/ctcp.js +29 -0
- package/dist/server/plugins/inputs/disconnect.js +15 -0
- package/dist/server/plugins/inputs/ignore.js +74 -0
- package/dist/server/plugins/inputs/ignorelist.js +50 -0
- package/dist/server/plugins/inputs/index.js +105 -0
- package/dist/server/plugins/inputs/invite.js +31 -0
- package/dist/server/plugins/inputs/kick.js +26 -0
- package/dist/server/plugins/inputs/kill.js +13 -0
- package/dist/server/plugins/inputs/list.js +12 -0
- package/dist/server/plugins/inputs/mode.js +55 -0
- package/dist/server/plugins/inputs/msg.js +106 -0
- package/dist/server/plugins/inputs/mute.js +56 -0
- package/dist/server/plugins/inputs/nick.js +55 -0
- package/dist/server/plugins/inputs/notice.js +42 -0
- package/dist/server/plugins/inputs/part.js +46 -0
- package/dist/server/plugins/inputs/quit.js +27 -0
- package/dist/server/plugins/inputs/raw.js +13 -0
- package/dist/server/plugins/inputs/rejoin.js +25 -0
- package/dist/server/plugins/inputs/topic.js +24 -0
- package/dist/server/plugins/inputs/whois.js +19 -0
- package/dist/server/plugins/irc-events/away.js +59 -0
- package/dist/server/plugins/irc-events/cap.js +62 -0
- package/dist/server/plugins/irc-events/chghost.js +29 -0
- package/dist/server/plugins/irc-events/connection.js +152 -0
- package/dist/server/plugins/irc-events/ctcp.js +72 -0
- package/dist/server/plugins/irc-events/error.js +80 -0
- package/dist/server/plugins/irc-events/help.js +21 -0
- package/dist/server/plugins/irc-events/info.js +21 -0
- package/dist/server/plugins/irc-events/invite.js +27 -0
- package/dist/server/plugins/irc-events/join.js +53 -0
- package/dist/server/plugins/irc-events/kick.js +39 -0
- package/dist/server/plugins/irc-events/link.js +442 -0
- package/dist/server/plugins/irc-events/list.js +47 -0
- package/dist/server/plugins/irc-events/message.js +187 -0
- package/dist/server/plugins/irc-events/mode.js +124 -0
- package/dist/server/plugins/irc-events/modelist.js +67 -0
- package/dist/server/plugins/irc-events/motd.js +29 -0
- package/dist/server/plugins/irc-events/names.js +21 -0
- package/dist/server/plugins/irc-events/nick.js +45 -0
- package/dist/server/plugins/irc-events/part.js +35 -0
- package/dist/server/plugins/irc-events/quit.js +32 -0
- package/dist/server/plugins/irc-events/sasl.js +26 -0
- package/dist/server/plugins/irc-events/topic.js +42 -0
- package/dist/server/plugins/irc-events/unhandled.js +31 -0
- package/dist/server/plugins/irc-events/welcome.js +22 -0
- package/dist/server/plugins/irc-events/whois.js +57 -0
- package/dist/server/plugins/messageStorage/sqlite.js +454 -0
- package/dist/server/plugins/messageStorage/text.js +124 -0
- package/dist/server/plugins/packages/index.js +200 -0
- package/dist/server/plugins/packages/publicClient.js +66 -0
- package/dist/server/plugins/packages/themes.js +61 -0
- package/dist/server/plugins/storage.js +88 -0
- package/dist/server/plugins/sts.js +85 -0
- package/dist/server/plugins/uploader.js +267 -0
- package/dist/server/plugins/webpush.js +99 -0
- package/dist/server/server.js +857 -0
- package/dist/server/storageCleaner.js +131 -0
- package/dist/server/utils/fish.js +432 -0
- package/dist/shared/irc.js +19 -0
- package/dist/shared/linkify.js +81 -0
- package/dist/shared/types/chan.js +22 -0
- package/dist/shared/types/changelog.js +2 -0
- package/dist/shared/types/config.js +2 -0
- package/dist/shared/types/mention.js +2 -0
- package/dist/shared/types/msg.js +34 -0
- package/dist/shared/types/network.js +2 -0
- package/dist/shared/types/storage.js +2 -0
- package/dist/shared/types/user.js +2 -0
- package/dist/webpack.config.js +224 -0
- package/index.js +38 -0
- package/package.json +174 -0
- package/public/audio/pop.wav +0 -0
- package/public/css/style.css +12 -0
- package/public/css/style.css.map +1 -0
- package/public/favicon.ico +0 -0
- package/public/fonts/fa-solid-900.woff +0 -0
- package/public/fonts/fa-solid-900.woff2 +0 -0
- package/public/img/favicon-alerted.ico +0 -0
- package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
- package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
- package/public/img/icon-black-transparent-bg.svg +1 -0
- package/public/img/logo-grey-bg-120x120px.png +0 -0
- package/public/img/logo-grey-bg-152x152px.png +0 -0
- package/public/img/logo-grey-bg-167x167px.png +0 -0
- package/public/img/logo-grey-bg-180x180px.png +0 -0
- package/public/img/logo-grey-bg-192x192px.png +0 -0
- package/public/img/logo-grey-bg-512x512px.png +0 -0
- package/public/img/logo-grey-bg.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-horizontal-transparent-bg.svg +1 -0
- package/public/img/logo-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-transparent-bg.svg +1 -0
- package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
- package/public/img/logo-vertical-transparent-bg.svg +1 -0
- package/public/js/bundle.js +2 -0
- package/public/js/bundle.js.map +1 -0
- package/public/js/bundle.vendor.js +3 -0
- package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
- package/public/js/bundle.vendor.js.map +1 -0
- package/public/js/loading-error-handlers.js +1 -0
- package/public/robots.txt +2 -0
- package/public/service-worker.js +1 -0
- package/public/thelounge.webmanifest +53 -0
- package/public/themes/default.css +35 -0
- 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
|
+
};
|