UNPKG

4.88 kBJavaScriptView Raw
1"use strict";
2
3const _ = require("lodash");
4const colors = require("colors/safe");
5const fs = require("fs");
6const path = require("path");
7const Client = require("./client");
8const Helper = require("./helper");
9const WebPush = require("./plugins/webpush");
10
11module.exports = ClientManager;
12
13function ClientManager() {
14 this.clients = [];
15}
16
17ClientManager.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 // TODO: Remove deprecated warning in v3.0.0
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
32ClientManager.prototype.findClient = function(name) {
33 return this.clients.find((u) => u.name === name);
34};
35
36ClientManager.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 // Reload all users. Existing users will only have their passwords reloaded.
55 updatedUsers.forEach((name) => this.loadUser(name));
56
57 // Existing users removed since last time users were loaded
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
69ClientManager.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 * If we happen to reload an existing client, make super duper sure we
82 * have their latest password. We're not replacing the entire config
83 * object, because that could have undesired consequences.
84 *
85 * @see https://github.com/thelounge/lounge/issues/598
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
97ClientManager.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
104ClientManager.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
134ClientManager.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 // Do not touch the disk if object has not changed
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
166ClientManager.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
179function 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}