1 | "use strict";
|
2 |
|
3 | const _ = require("lodash");
|
4 | const colors = require("colors/safe");
|
5 | const fs = require("fs");
|
6 | const path = require("path");
|
7 | const Client = require("./client");
|
8 | const Helper = require("./helper");
|
9 | const WebPush = require("./plugins/webpush");
|
10 |
|
11 | module.exports = ClientManager;
|
12 |
|
13 | function ClientManager() {
|
14 | this.clients = [];
|
15 | }
|
16 |
|
17 | ClientManager.prototype.init = function(identHandler, sockets) {
|
18 | this.sockets = sockets;
|
19 | this.identHandler = identHandler;
|
20 | this.webPush = new WebPush();
|
21 |
|
22 | if (!Helper.config.public && !Helper.config.ldap.enable) {
|
23 |
|
24 | if ("autoload" in Helper.config) {
|
25 | log.warn(`Autoloading users is now always enabled. Please remove the ${colors.yellow("autoload")} option from your configuration file.`);
|
26 | }
|
27 |
|
28 | this.autoloadUsers();
|
29 | }
|
30 | };
|
31 |
|
32 | ClientManager.prototype.findClient = function(name) {
|
33 | return this.clients.find((u) => u.name === name);
|
34 | };
|
35 |
|
36 | ClientManager.prototype.autoloadUsers = function() {
|
37 | const users = this.getUsers();
|
38 | const noUsersWarning = `There are currently no users. Create one with ${colors.bold("thelounge add <name>")}.`;
|
39 |
|
40 | if (users.length === 0) {
|
41 | log.info(noUsersWarning);
|
42 | }
|
43 |
|
44 | users.forEach((name) => this.loadUser(name));
|
45 |
|
46 | fs.watch(Helper.getUsersPath(), _.debounce(() => {
|
47 | const loaded = this.clients.map((c) => c.name);
|
48 | const updatedUsers = this.getUsers();
|
49 |
|
50 | if (updatedUsers.length === 0) {
|
51 | log.info(noUsersWarning);
|
52 | }
|
53 |
|
54 |
|
55 | updatedUsers.forEach((name) => this.loadUser(name));
|
56 |
|
57 |
|
58 | _.difference(loaded, updatedUsers).forEach((name) => {
|
59 | const client = _.find(this.clients, {name: name});
|
60 | if (client) {
|
61 | client.quit(true);
|
62 | this.clients = _.without(this.clients, client);
|
63 | log.info(`User ${colors.bold(name)} disconnected and removed.`);
|
64 | }
|
65 | });
|
66 | }, 1000, {maxWait: 10000}));
|
67 | };
|
68 |
|
69 | ClientManager.prototype.loadUser = function(name) {
|
70 | const userConfig = readUserConfig(name);
|
71 |
|
72 | if (!userConfig) {
|
73 | return;
|
74 | }
|
75 |
|
76 | let client = this.findClient(name);
|
77 |
|
78 | if (client) {
|
79 | if (userConfig.password !== client.config.password) {
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | client.config.password = userConfig.password;
|
88 | log.info(`Password for user ${colors.bold(name)} was reset.`);
|
89 | }
|
90 | } else {
|
91 | client = new Client(this, name, userConfig);
|
92 | this.clients.push(client);
|
93 | }
|
94 | return client;
|
95 | };
|
96 |
|
97 | ClientManager.prototype.getUsers = function() {
|
98 | return fs
|
99 | .readdirSync(Helper.getUsersPath())
|
100 | .filter((file) => file.endsWith(".json"))
|
101 | .map((file) => file.slice(0, -5));
|
102 | };
|
103 |
|
104 | ClientManager.prototype.addUser = function(name, password, enableLog) {
|
105 | if (path.basename(name) !== name) {
|
106 | throw new Error(`${name} is an invalid username.`);
|
107 | }
|
108 |
|
109 | const userPath = Helper.getUserConfigPath(name);
|
110 |
|
111 | if (fs.existsSync(userPath)) {
|
112 | log.error(`User ${colors.green(name)} already exists.`);
|
113 | return false;
|
114 | }
|
115 |
|
116 | const user = {
|
117 | password: password || "",
|
118 | log: enableLog || false,
|
119 | awayMessage: "",
|
120 | networks: [],
|
121 | sessions: {},
|
122 | };
|
123 |
|
124 | try {
|
125 | fs.writeFileSync(userPath, JSON.stringify(user, null, "\t"));
|
126 | } catch (e) {
|
127 | log.error(`Failed to create user ${colors.green(name)} (${e})`);
|
128 | throw e;
|
129 | }
|
130 |
|
131 | return true;
|
132 | };
|
133 |
|
134 | ClientManager.prototype.updateUser = function(name, opts, callback) {
|
135 | const user = readUserConfig(name);
|
136 |
|
137 | if (!user) {
|
138 | log.error(`Tried to update invalid user ${colors.green(name)}. This is most likely a bug.`);
|
139 | if (callback) {
|
140 | callback(true);
|
141 | }
|
142 | return false;
|
143 | }
|
144 |
|
145 | const currentUser = JSON.stringify(user, null, "\t");
|
146 | _.assign(user, opts);
|
147 | const newUser = JSON.stringify(user, null, "\t");
|
148 |
|
149 |
|
150 | if (currentUser === newUser) {
|
151 | return callback ? callback() : true;
|
152 | }
|
153 |
|
154 | try {
|
155 | fs.writeFileSync(Helper.getUserConfigPath(name), newUser);
|
156 | return callback ? callback() : true;
|
157 | } catch (e) {
|
158 | log.error(`Failed to update user ${colors.green(name)} (${e})`);
|
159 | if (callback) {
|
160 | callback(e);
|
161 | }
|
162 | throw e;
|
163 | }
|
164 | };
|
165 |
|
166 | ClientManager.prototype.removeUser = function(name) {
|
167 | const userPath = Helper.getUserConfigPath(name);
|
168 |
|
169 | if (!fs.existsSync(userPath)) {
|
170 | log.error(`Tried to remove non-existing user ${colors.green(name)}.`);
|
171 | return false;
|
172 | }
|
173 |
|
174 | fs.unlinkSync(userPath);
|
175 |
|
176 | return true;
|
177 | };
|
178 |
|
179 | function readUserConfig(name) {
|
180 | const userPath = Helper.getUserConfigPath(name);
|
181 |
|
182 | if (!fs.existsSync(userPath)) {
|
183 | log.error(`Tried to read non-existing user ${colors.green(name)}`);
|
184 | return false;
|
185 | }
|
186 |
|
187 | const data = fs.readFileSync(userPath, "utf-8");
|
188 | return JSON.parse(data);
|
189 | }
|