certificates.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import express from "express";
  2. import dnsPlugins from "../../certbot/dns-plugins.json" with { type: "json" };
  3. import internalCertificate from "../../internal/certificate.js";
  4. import errs from "../../lib/error.js";
  5. import jwtdecode from "../../lib/express/jwt-decode.js";
  6. import apiValidator from "../../lib/validator/api.js";
  7. import validator from "../../lib/validator/index.js";
  8. import { express as logger } from "../../logger.js";
  9. import { getValidationSchema } from "../../schema/index.js";
  10. const router = express.Router({
  11. caseSensitive: true,
  12. strict: true,
  13. mergeParams: true,
  14. });
  15. /**
  16. * /api/nginx/certificates
  17. */
  18. router
  19. .route("/")
  20. .options((_, res) => {
  21. res.sendStatus(204);
  22. })
  23. .all(jwtdecode())
  24. /**
  25. * GET /api/nginx/certificates
  26. *
  27. * Retrieve all certificates
  28. */
  29. .get(async (req, res, next) => {
  30. try {
  31. const data = await validator(
  32. {
  33. additionalProperties: false,
  34. properties: {
  35. expand: {
  36. $ref: "common#/properties/expand",
  37. },
  38. query: {
  39. $ref: "common#/properties/query",
  40. },
  41. },
  42. },
  43. {
  44. expand:
  45. typeof req.query.expand === "string"
  46. ? req.query.expand.split(",")
  47. : null,
  48. query: typeof req.query.query === "string" ? req.query.query : null,
  49. },
  50. );
  51. const rows = await internalCertificate.getAll(
  52. res.locals.access,
  53. data.expand,
  54. data.query,
  55. );
  56. res.status(200).send(rows);
  57. } catch (err) {
  58. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  59. next(err);
  60. }
  61. })
  62. /**
  63. * POST /api/nginx/certificates
  64. *
  65. * Create a new certificate
  66. */
  67. .post(async (req, res, next) => {
  68. try {
  69. const payload = await apiValidator(
  70. getValidationSchema("/nginx/certificates", "post"),
  71. req.body,
  72. );
  73. req.setTimeout(900000); // 15 minutes timeout
  74. const result = await internalCertificate.create(
  75. res.locals.access,
  76. payload,
  77. );
  78. res.status(201).send(result);
  79. } catch (err) {
  80. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  81. next(err);
  82. }
  83. });
  84. /**
  85. * /api/nginx/certificates/dns-providers
  86. */
  87. router
  88. .route("/dns-providers")
  89. .options((_, res) => {
  90. res.sendStatus(204);
  91. })
  92. .all(jwtdecode())
  93. /**
  94. * GET /api/nginx/certificates/dns-providers
  95. *
  96. * Get list of all supported DNS providers
  97. */
  98. .get(async (req, res, next) => {
  99. try {
  100. if (!res.locals.access.token.getUserId()) {
  101. throw new errs.PermissionError("Login required");
  102. }
  103. const clean = Object.keys(dnsPlugins).map((key) => ({
  104. id: key,
  105. name: dnsPlugins[key].name,
  106. credentials: dnsPlugins[key].credentials,
  107. }));
  108. clean.sort((a, b) => a.name.localeCompare(b.name));
  109. res.status(200).send(clean);
  110. } catch (err) {
  111. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  112. next(err);
  113. }
  114. });
  115. /**
  116. * Test HTTP challenge for domains
  117. *
  118. * /api/nginx/certificates/test-http
  119. */
  120. router
  121. .route("/test-http")
  122. .options((_, res) => {
  123. res.sendStatus(204);
  124. })
  125. .all(jwtdecode())
  126. /**
  127. * POST /api/nginx/certificates/test-http
  128. *
  129. * Test HTTP challenge for domains
  130. */
  131. .post(async (req, res, next) => {
  132. try {
  133. const payload = await apiValidator(
  134. getValidationSchema("/nginx/certificates/test-http", "post"),
  135. req.body,
  136. );
  137. req.setTimeout(60000); // 1 minute timeout
  138. const result = await internalCertificate.testHttpsChallenge(
  139. res.locals.access,
  140. payload,
  141. );
  142. res.status(200).send(result);
  143. } catch (err) {
  144. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  145. next(err);
  146. }
  147. });
  148. /**
  149. * Validate Certs before saving
  150. *
  151. * /api/nginx/certificates/validate
  152. */
  153. router
  154. .route("/validate")
  155. .options((_, res) => {
  156. res.sendStatus(204);
  157. })
  158. .all(jwtdecode())
  159. /**
  160. * POST /api/nginx/certificates/validate
  161. *
  162. * Validate certificates
  163. */
  164. .post(async (req, res, next) => {
  165. if (!req.files) {
  166. res.status(400).send({ error: "No files were uploaded" });
  167. return;
  168. }
  169. try {
  170. const result = await internalCertificate.validate({
  171. files: req.files,
  172. });
  173. res.status(200).send(result);
  174. } catch (err) {
  175. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  176. next(err);
  177. }
  178. });
  179. /**
  180. * Specific certificate
  181. *
  182. * /api/nginx/certificates/123
  183. */
  184. router
  185. .route("/:certificate_id")
  186. .options((_, res) => {
  187. res.sendStatus(204);
  188. })
  189. .all(jwtdecode())
  190. /**
  191. * GET /api/nginx/certificates/123
  192. *
  193. * Retrieve a specific certificate
  194. */
  195. .get(async (req, res, next) => {
  196. try {
  197. const data = await validator(
  198. {
  199. required: ["certificate_id"],
  200. additionalProperties: false,
  201. properties: {
  202. certificate_id: {
  203. $ref: "common#/properties/id",
  204. },
  205. expand: {
  206. $ref: "common#/properties/expand",
  207. },
  208. },
  209. },
  210. {
  211. certificate_id: req.params.certificate_id,
  212. expand:
  213. typeof req.query.expand === "string"
  214. ? req.query.expand.split(",")
  215. : null,
  216. },
  217. );
  218. const row = await internalCertificate.get(res.locals.access, {
  219. id: Number.parseInt(data.certificate_id, 10),
  220. expand: data.expand,
  221. });
  222. res.status(200).send(row);
  223. } catch (err) {
  224. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  225. next(err);
  226. }
  227. })
  228. /**
  229. * DELETE /api/nginx/certificates/123
  230. *
  231. * Update and existing certificate
  232. */
  233. .delete(async (req, res, next) => {
  234. try {
  235. const result = await internalCertificate.delete(res.locals.access, {
  236. id: Number.parseInt(req.params.certificate_id, 10),
  237. });
  238. res.status(200).send(result);
  239. } catch (err) {
  240. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  241. next(err);
  242. }
  243. });
  244. /**
  245. * Upload Certs
  246. *
  247. * /api/nginx/certificates/123/upload
  248. */
  249. router
  250. .route("/:certificate_id/upload")
  251. .options((_, res) => {
  252. res.sendStatus(204);
  253. })
  254. .all(jwtdecode())
  255. /**
  256. * POST /api/nginx/certificates/123/upload
  257. *
  258. * Upload certificates
  259. */
  260. .post(async (req, res, next) => {
  261. if (!req.files) {
  262. res.status(400).send({ error: "No files were uploaded" });
  263. return;
  264. }
  265. try {
  266. const result = await internalCertificate.upload(res.locals.access, {
  267. id: Number.parseInt(req.params.certificate_id, 10),
  268. files: req.files,
  269. });
  270. res.status(200).send(result);
  271. } catch (err) {
  272. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  273. next(err);
  274. }
  275. });
  276. /**
  277. * Renew LE Certs
  278. *
  279. * /api/nginx/certificates/123/renew
  280. */
  281. router
  282. .route("/:certificate_id/renew")
  283. .options((_, res) => {
  284. res.sendStatus(204);
  285. })
  286. .all(jwtdecode())
  287. /**
  288. * POST /api/nginx/certificates/123/renew
  289. *
  290. * Renew certificate
  291. */
  292. .post(async (req, res, next) => {
  293. req.setTimeout(900000); // 15 minutes timeout
  294. try {
  295. const result = await internalCertificate.renew(res.locals.access, {
  296. id: Number.parseInt(req.params.certificate_id, 10),
  297. });
  298. res.status(200).send(result);
  299. } catch (err) {
  300. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  301. next(err);
  302. }
  303. });
  304. /**
  305. * Download LE Certs
  306. *
  307. * /api/nginx/certificates/123/download
  308. */
  309. router
  310. .route("/:certificate_id/download")
  311. .options((_req, res) => {
  312. res.sendStatus(204);
  313. })
  314. .all(jwtdecode())
  315. /**
  316. * GET /api/nginx/certificates/123/download
  317. *
  318. * Renew certificate
  319. */
  320. .get(async (req, res, next) => {
  321. try {
  322. const result = await internalCertificate.download(res.locals.access, {
  323. id: Number.parseInt(req.params.certificate_id, 10),
  324. });
  325. res.status(200).download(result.fileName);
  326. } catch (err) {
  327. logger.debug(`${req.method.toUpperCase()} ${req.path}: ${err}`);
  328. next(err);
  329. }
  330. });
  331. export default router;