users.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import express from "express";
  2. import internalUser from "../internal/user.js";
  3. import Access from "../lib/access.js";
  4. import { isCI } from "../lib/config.js";
  5. import errs from "../lib/error.js";
  6. import jwtdecode from "../lib/express/jwt-decode.js";
  7. import userIdFromMe from "../lib/express/user-id-from-me.js";
  8. import apiValidator from "../lib/validator/api.js";
  9. import validator from "../lib/validator/index.js";
  10. import { express as logger } from "../logger.js";
  11. import { getValidationSchema } from "../schema/index.js";
  12. import { isSetup } from "../setup.js";
  13. const router = express.Router({
  14. caseSensitive: true,
  15. strict: true,
  16. mergeParams: true,
  17. });
  18. /**
  19. * /api/users
  20. */
  21. router
  22. .route("/")
  23. .options((_, res) => {
  24. res.sendStatus(204);
  25. })
  26. .all(jwtdecode())
  27. /**
  28. * GET /api/users
  29. *
  30. * Retrieve all users
  31. */
  32. .get(async (req, res, next) => {
  33. try {
  34. const data = await validator(
  35. {
  36. additionalProperties: false,
  37. properties: {
  38. expand: {
  39. $ref: "common#/properties/expand",
  40. },
  41. query: {
  42. $ref: "common#/properties/query",
  43. },
  44. },
  45. },
  46. {
  47. expand:
  48. typeof req.query.expand === "string"
  49. ? req.query.expand.split(",")
  50. : null,
  51. query: typeof req.query.query === "string" ? req.query.query : null,
  52. },
  53. );
  54. const users = await internalUser.getAll(
  55. res.locals.access,
  56. data.expand,
  57. data.query,
  58. );
  59. res.status(200).send(users);
  60. } catch (err) {
  61. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  62. next(err);
  63. }
  64. })
  65. /**
  66. * POST /api/users
  67. *
  68. * Create a new User
  69. */
  70. .post(async (req, res, next) => {
  71. const body = req.body;
  72. try {
  73. // If we are in setup mode, we don't check access for current user
  74. const setup = await isSetup();
  75. if (!setup) {
  76. logger.info("Creating a new user in setup mode");
  77. const access = new Access(null);
  78. await access.load(true);
  79. res.locals.access = access;
  80. // We are in setup mode, set some defaults for this first new user, such as making
  81. // them an admin.
  82. body.is_disabled = false;
  83. if (typeof body.roles !== "object" || body.roles === null) {
  84. body.roles = [];
  85. }
  86. if (body.roles.indexOf("admin") === -1) {
  87. body.roles.push("admin");
  88. }
  89. }
  90. const payload = await apiValidator(
  91. getValidationSchema("/users", "post"),
  92. body,
  93. );
  94. const user = await internalUser.create(res.locals.access, payload);
  95. res.status(201).send(user);
  96. } catch (err) {
  97. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  98. next(err);
  99. }
  100. })
  101. /**
  102. * DELETE /api/users
  103. *
  104. * Deletes ALL users. This is NOT GENERALLY AVAILABLE!
  105. * (!) It is NOT an authenticated endpoint.
  106. * (!) Only CI should be able to call this endpoint. As a result,
  107. *
  108. * it will only work when the env vars DEBUG=true and CI=true
  109. *
  110. * Do NOT set those env vars in a production environment!
  111. */
  112. .delete(async (_, res, next) => {
  113. if (isCI()) {
  114. try {
  115. logger.warn("Deleting all users - CI environment detected, allowing this operation");
  116. await internalUser.deleteAll();
  117. res.status(200).send(true);
  118. } catch (err) {
  119. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  120. next(err);
  121. }
  122. return;
  123. }
  124. next(new errs.ItemNotFoundError());
  125. });
  126. /**
  127. * Specific user
  128. *
  129. * /api/users/123
  130. */
  131. router
  132. .route("/:user_id")
  133. .options((_, res) => {
  134. res.sendStatus(204);
  135. })
  136. .all(jwtdecode())
  137. .all(userIdFromMe)
  138. /**
  139. * GET /users/123 or /users/me
  140. *
  141. * Retrieve a specific user
  142. */
  143. .get(async (req, res, next) => {
  144. try {
  145. const data = await validator(
  146. {
  147. required: ["user_id"],
  148. additionalProperties: false,
  149. properties: {
  150. user_id: {
  151. $ref: "common#/properties/id",
  152. },
  153. expand: {
  154. $ref: "common#/properties/expand",
  155. },
  156. },
  157. },
  158. {
  159. user_id: req.params.user_id,
  160. expand:
  161. typeof req.query.expand === "string"
  162. ? req.query.expand.split(",")
  163. : null,
  164. },
  165. );
  166. const user = await internalUser.get(res.locals.access, {
  167. id: data.user_id,
  168. expand: data.expand,
  169. omit: internalUser.getUserOmisionsByAccess(
  170. res.locals.access,
  171. data.user_id,
  172. ),
  173. });
  174. res.status(200).send(user);
  175. } catch (err) {
  176. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  177. next(err);
  178. }
  179. })
  180. /**
  181. * PUT /api/users/123
  182. *
  183. * Update and existing user
  184. */
  185. .put(async (req, res, next) => {
  186. try {
  187. const payload = await apiValidator(
  188. getValidationSchema("/users/{userID}", "put"),
  189. req.body,
  190. );
  191. payload.id = req.params.user_id;
  192. const result = await internalUser.update(res.locals.access, payload);
  193. res.status(200).send(result);
  194. } catch (err) {
  195. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  196. next(err);
  197. }
  198. })
  199. /**
  200. * DELETE /api/users/123
  201. *
  202. * Update and existing user
  203. */
  204. .delete(async (req, res, next) => {
  205. try {
  206. const result = await internalUser.delete(res.locals.access, {
  207. id: req.params.user_id,
  208. });
  209. res.status(200).send(result);
  210. } catch (err) {
  211. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  212. next(err);
  213. }
  214. });
  215. /**
  216. * Specific user auth
  217. *
  218. * /api/users/123/auth
  219. */
  220. router
  221. .route("/:user_id/auth")
  222. .options((_, res) => {
  223. res.sendStatus(204);
  224. })
  225. .all(jwtdecode())
  226. .all(userIdFromMe)
  227. /**
  228. * PUT /api/users/123/auth
  229. *
  230. * Update password for a user
  231. */
  232. .put(async (req, res, next) => {
  233. try {
  234. const payload = await apiValidator(
  235. getValidationSchema("/users/{userID}/auth", "put"),
  236. req.body,
  237. );
  238. payload.id = req.params.user_id;
  239. const result = await internalUser.setPassword(res.locals.access, payload);
  240. res.status(200).send(result);
  241. } catch (err) {
  242. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  243. next(err);
  244. }
  245. });
  246. /**
  247. * Specific user permissions
  248. *
  249. * /api/users/123/permissions
  250. */
  251. router
  252. .route("/:user_id/permissions")
  253. .options((_, res) => {
  254. res.sendStatus(204);
  255. })
  256. .all(jwtdecode())
  257. .all(userIdFromMe)
  258. /**
  259. * PUT /api/users/123/permissions
  260. *
  261. * Set some or all permissions for a user
  262. */
  263. .put(async (req, res, next) => {
  264. try {
  265. const payload = await apiValidator(
  266. getValidationSchema("/users/{userID}/permissions", "put"),
  267. req.body,
  268. );
  269. payload.id = req.params.user_id;
  270. const result = await internalUser.setPermissions(
  271. res.locals.access,
  272. payload,
  273. );
  274. res.status(200).send(result);
  275. } catch (err) {
  276. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  277. next(err);
  278. }
  279. });
  280. /**
  281. * Specific user login as
  282. *
  283. * /api/users/123/login
  284. */
  285. router
  286. .route("/:user_id/login")
  287. .options((_, res) => {
  288. res.sendStatus(204);
  289. })
  290. .all(jwtdecode())
  291. /**
  292. * POST /api/users/123/login
  293. *
  294. * Log in as a user
  295. */
  296. .post(async (req, res, next) => {
  297. try {
  298. const result = await internalUser.loginAs(res.locals.access, {
  299. id: Number.parseInt(req.params.user_id, 10),
  300. });
  301. res.status(200).send(result);
  302. } catch (err) {
  303. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  304. next(err);
  305. }
  306. });
  307. export default router;