certificates.js 7.3 KB

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