certificates.js 7.3 KB

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