user.js 12 KB

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