user.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. import gravatar from "gravatar";
  2. import _ from "lodash";
  3. import errs from "../lib/error.js";
  4. import utils from "../lib/utils.js";
  5. import authModel from "../models/auth.js";
  6. import userModel from "../models/user.js";
  7. import userPermissionModel from "../models/user_permission.js";
  8. import internalAuditLog from "./audit-log.js";
  9. import internalToken from "./token.js";
  10. const omissions = () => {
  11. return ["is_deleted"];
  12. }
  13. const internalUser = {
  14. /**
  15. * @param {Access} access
  16. * @param {Object} data
  17. * @returns {Promise}
  18. */
  19. create: (access, data) => {
  20. const auth = data.auth || null;
  21. delete data.auth;
  22. data.avatar = data.avatar || "";
  23. data.roles = data.roles || [];
  24. if (typeof data.is_disabled !== "undefined") {
  25. data.is_disabled = data.is_disabled ? 1 : 0;
  26. }
  27. return access
  28. .can("users:create", data)
  29. .then(() => {
  30. data.avatar = gravatar.url(data.email, { default: "mm" });
  31. return userModel.query().insertAndFetch(data).then(utils.omitRow(omissions()));
  32. })
  33. .then((user) => {
  34. if (auth) {
  35. return authModel
  36. .query()
  37. .insert({
  38. user_id: user.id,
  39. type: auth.type,
  40. secret: auth.secret,
  41. meta: {},
  42. })
  43. .then(() => {
  44. return user;
  45. });
  46. }
  47. return user;
  48. })
  49. .then((user) => {
  50. // Create permissions row as well
  51. const is_admin = data.roles.indexOf("admin") !== -1;
  52. return userPermissionModel
  53. .query()
  54. .insert({
  55. user_id: user.id,
  56. visibility: is_admin ? "all" : "user",
  57. proxy_hosts: "manage",
  58. redirection_hosts: "manage",
  59. dead_hosts: "manage",
  60. streams: "manage",
  61. access_lists: "manage",
  62. certificates: "manage",
  63. })
  64. .then(() => {
  65. return internalUser.get(access, { id: user.id, expand: ["permissions"] });
  66. });
  67. })
  68. .then((user) => {
  69. // Add to audit log
  70. return internalAuditLog
  71. .add(access, {
  72. action: "created",
  73. object_type: "user",
  74. object_id: user.id,
  75. meta: user,
  76. })
  77. .then(() => {
  78. return user;
  79. });
  80. });
  81. },
  82. /**
  83. * @param {Access} access
  84. * @param {Object} data
  85. * @param {Integer} data.id
  86. * @param {String} [data.email]
  87. * @param {String} [data.name]
  88. * @return {Promise}
  89. */
  90. update: (access, data) => {
  91. if (typeof data.is_disabled !== "undefined") {
  92. data.is_disabled = data.is_disabled ? 1 : 0;
  93. }
  94. return access
  95. .can("users:update", data.id)
  96. .then(() => {
  97. // Make sure that the user being updated doesn't change their email to another user that is already using it
  98. // 1. get user we want to update
  99. return internalUser.get(access, { id: data.id }).then((user) => {
  100. // 2. if email is to be changed, find other users with that email
  101. if (typeof data.email !== "undefined") {
  102. data.email = data.email.toLowerCase().trim();
  103. if (user.email !== data.email) {
  104. return internalUser.isEmailAvailable(data.email, data.id).then((available) => {
  105. if (!available) {
  106. throw new errs.ValidationError(`Email address already in use - ${data.email}`);
  107. }
  108. return user;
  109. });
  110. }
  111. }
  112. // No change to email:
  113. return user;
  114. });
  115. })
  116. .then((user) => {
  117. if (user.id !== data.id) {
  118. // Sanity check that something crazy hasn't happened
  119. throw new errs.InternalValidationError(
  120. `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
  121. );
  122. }
  123. data.avatar = gravatar.url(data.email || user.email, { default: "mm" });
  124. return userModel.query().patchAndFetchById(user.id, data).then(utils.omitRow(omissions()));
  125. })
  126. .then(() => {
  127. return internalUser.get(access, { id: data.id });
  128. })
  129. .then((user) => {
  130. // Add to audit log
  131. return internalAuditLog
  132. .add(access, {
  133. action: "updated",
  134. object_type: "user",
  135. object_id: user.id,
  136. meta: data,
  137. })
  138. .then(() => {
  139. return user;
  140. });
  141. });
  142. },
  143. /**
  144. * @param {Access} access
  145. * @param {Object} [data]
  146. * @param {Integer} [data.id] Defaults to the token user
  147. * @param {Array} [data.expand]
  148. * @param {Array} [data.omit]
  149. * @return {Promise}
  150. */
  151. get: (access, data) => {
  152. const thisData = data || {};
  153. if (typeof thisData.id === "undefined" || !thisData.id) {
  154. thisData.id = access.token.getUserId(0);
  155. }
  156. return access
  157. .can("users:get", thisData.id)
  158. .then(() => {
  159. const query = userModel
  160. .query()
  161. .where("is_deleted", 0)
  162. .andWhere("id", thisData.id)
  163. .allowGraph("[permissions]")
  164. .first();
  165. if (typeof thisData.expand !== "undefined" && thisData.expand !== null) {
  166. query.withGraphFetched(`[${thisData.expand.join(", ")}]`);
  167. }
  168. return query.then(utils.omitRow(omissions()));
  169. })
  170. .then((row) => {
  171. if (!row || !row.id) {
  172. throw new errs.ItemNotFoundError(thisData.id);
  173. }
  174. // Custom omissions
  175. if (typeof thisData.omit !== "undefined" && thisData.omit !== null) {
  176. return _.omit(row, thisData.omit);
  177. }
  178. return row;
  179. });
  180. },
  181. /**
  182. * Checks if an email address is available, but if a user_id is supplied, it will ignore checking
  183. * against that user.
  184. *
  185. * @param email
  186. * @param user_id
  187. */
  188. isEmailAvailable: (email, user_id) => {
  189. const query = userModel.query().where("email", "=", email.toLowerCase().trim()).where("is_deleted", 0).first();
  190. if (typeof user_id !== "undefined") {
  191. query.where("id", "!=", user_id);
  192. }
  193. return query.then((user) => {
  194. return !user;
  195. });
  196. },
  197. /**
  198. * @param {Access} access
  199. * @param {Object} data
  200. * @param {Integer} data.id
  201. * @param {String} [data.reason]
  202. * @returns {Promise}
  203. */
  204. delete: (access, data) => {
  205. return access
  206. .can("users:delete", data.id)
  207. .then(() => {
  208. return internalUser.get(access, { id: data.id });
  209. })
  210. .then((user) => {
  211. if (!user) {
  212. throw new errs.ItemNotFoundError(data.id);
  213. }
  214. // Make sure user can't delete themselves
  215. if (user.id === access.token.getUserId(0)) {
  216. throw new errs.PermissionError("You cannot delete yourself.");
  217. }
  218. return userModel
  219. .query()
  220. .where("id", user.id)
  221. .patch({
  222. is_deleted: 1,
  223. })
  224. .then(() => {
  225. // Add to audit log
  226. return internalAuditLog.add(access, {
  227. action: "deleted",
  228. object_type: "user",
  229. object_id: user.id,
  230. meta: _.omit(user, omissions()),
  231. });
  232. });
  233. })
  234. .then(() => {
  235. return true;
  236. });
  237. },
  238. /**
  239. * This will only count the users
  240. *
  241. * @param {Access} access
  242. * @param {String} [search_query]
  243. * @returns {*}
  244. */
  245. getCount: (access, search_query) => {
  246. return access
  247. .can("users:list")
  248. .then(() => {
  249. const query = userModel.query().count("id as count").where("is_deleted", 0).first();
  250. // Query is used for searching
  251. if (typeof search_query === "string") {
  252. query.where(function () {
  253. this.where("user.name", "like", `%${search_query}%`).orWhere(
  254. "user.email",
  255. "like",
  256. `%${search_query}%`,
  257. );
  258. });
  259. }
  260. return query;
  261. })
  262. .then((row) => {
  263. return Number.parseInt(row.count, 10);
  264. });
  265. },
  266. /**
  267. * All users
  268. *
  269. * @param {Access} access
  270. * @param {Array} [expand]
  271. * @param {String} [search_query]
  272. * @returns {Promise}
  273. */
  274. getAll: (access, expand, search_query) => {
  275. return access.can("users:list").then(() => {
  276. const query = userModel
  277. .query()
  278. .where("is_deleted", 0)
  279. .groupBy("id")
  280. .allowGraph("[permissions]")
  281. .orderBy("name", "ASC");
  282. // Query is used for searching
  283. if (typeof search_query === "string") {
  284. query.where(function () {
  285. this.where("name", "like", `%${search_query}%`).orWhere(
  286. "email",
  287. "like",
  288. `%${search_query}%`,
  289. );
  290. });
  291. }
  292. if (typeof expand !== "undefined" && expand !== null) {
  293. query.withGraphFetched(`[${expand.join(", ")}]`);
  294. }
  295. return query.then(utils.omitRows(omissions()));
  296. });
  297. },
  298. /**
  299. * @param {Access} access
  300. * @param {Integer} [id_requested]
  301. * @returns {[String]}
  302. */
  303. getUserOmisionsByAccess: (access, id_requested) => {
  304. let response = []; // Admin response
  305. if (!access.token.hasScope("admin") && access.token.getUserId(0) !== id_requested) {
  306. response = ["roles", "is_deleted"]; // Restricted response
  307. }
  308. return response;
  309. },
  310. /**
  311. * @param {Access} access
  312. * @param {Object} data
  313. * @param {Integer} data.id
  314. * @param {String} data.type
  315. * @param {String} data.secret
  316. * @return {Promise}
  317. */
  318. setPassword: (access, data) => {
  319. return access
  320. .can("users:password", data.id)
  321. .then(() => {
  322. return internalUser.get(access, { id: data.id });
  323. })
  324. .then((user) => {
  325. if (user.id !== data.id) {
  326. // Sanity check that something crazy hasn't happened
  327. throw new errs.InternalValidationError(
  328. `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
  329. );
  330. }
  331. if (user.id === access.token.getUserId(0)) {
  332. // they're setting their own password. Make sure their current password is correct
  333. if (typeof data.current === "undefined" || !data.current) {
  334. throw new errs.ValidationError("Current password was not supplied");
  335. }
  336. return internalToken
  337. .getTokenFromEmail({
  338. identity: user.email,
  339. secret: data.current,
  340. })
  341. .then(() => {
  342. return user;
  343. });
  344. }
  345. return user;
  346. })
  347. .then((user) => {
  348. // Get auth, patch if it exists
  349. return authModel
  350. .query()
  351. .where("user_id", user.id)
  352. .andWhere("type", data.type)
  353. .first()
  354. .then((existing_auth) => {
  355. if (existing_auth) {
  356. // patch
  357. return authModel.query().where("user_id", user.id).andWhere("type", data.type).patch({
  358. type: data.type, // This is required for the model to encrypt on save
  359. secret: data.secret,
  360. });
  361. }
  362. // insert
  363. return authModel.query().insert({
  364. user_id: user.id,
  365. type: data.type,
  366. secret: data.secret,
  367. meta: {},
  368. });
  369. })
  370. .then(() => {
  371. // Add to Audit Log
  372. return internalAuditLog.add(access, {
  373. action: "updated",
  374. object_type: "user",
  375. object_id: user.id,
  376. meta: {
  377. name: user.name,
  378. password_changed: true,
  379. auth_type: data.type,
  380. },
  381. });
  382. });
  383. })
  384. .then(() => {
  385. return true;
  386. });
  387. },
  388. /**
  389. * @param {Access} access
  390. * @param {Object} data
  391. * @return {Promise}
  392. */
  393. setPermissions: (access, data) => {
  394. return access
  395. .can("users:permissions", data.id)
  396. .then(() => {
  397. return internalUser.get(access, { id: data.id });
  398. })
  399. .then((user) => {
  400. if (user.id !== data.id) {
  401. // Sanity check that something crazy hasn't happened
  402. throw new errs.InternalValidationError(
  403. `User could not be updated, IDs do not match: ${user.id} !== ${data.id}`,
  404. );
  405. }
  406. return user;
  407. })
  408. .then((user) => {
  409. // Get perms row, patch if it exists
  410. return userPermissionModel
  411. .query()
  412. .where("user_id", user.id)
  413. .first()
  414. .then((existing_auth) => {
  415. if (existing_auth) {
  416. // patch
  417. return userPermissionModel
  418. .query()
  419. .where("user_id", user.id)
  420. .patchAndFetchById(existing_auth.id, _.assign({ user_id: user.id }, data));
  421. }
  422. // insert
  423. return userPermissionModel.query().insertAndFetch(_.assign({ user_id: user.id }, data));
  424. })
  425. .then((permissions) => {
  426. // Add to Audit Log
  427. return internalAuditLog.add(access, {
  428. action: "updated",
  429. object_type: "user",
  430. object_id: user.id,
  431. meta: {
  432. name: user.name,
  433. permissions: permissions,
  434. },
  435. });
  436. });
  437. })
  438. .then(() => {
  439. return true;
  440. });
  441. },
  442. /**
  443. * @param {Access} access
  444. * @param {Object} data
  445. * @param {Integer} data.id
  446. */
  447. loginAs: (access, data) => {
  448. return access
  449. .can("users:loginas", data.id)
  450. .then(() => {
  451. return internalUser.get(access, data);
  452. })
  453. .then((user) => {
  454. return internalToken.getTokenFromUser(user);
  455. });
  456. },
  457. };
  458. export default internalUser;