diff --git a/src/backend/internal/certificate.js b/src/backend/internal/certificate.js index 6b1bd1c..fbe5e25 100644 --- a/src/backend/internal/certificate.js +++ b/src/backend/internal/certificate.js @@ -1,5 +1,3 @@ -'use strict'; - const fs = require('fs'); const _ = require('lodash'); const logger = require('../logger').ssl; @@ -9,7 +7,7 @@ const internalAuditLog = require('./audit-log'); const tempWrite = require('temp-write'); const utils = require('../lib/utils'); const moment = require('moment'); -const debug_mode = process.env.NODE_ENV !== 'production'; +const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG ; const internalNginx = require('./nginx'); const internalHost = require('./host'); const certbot_command = '/usr/bin/certbot'; @@ -21,7 +19,7 @@ function omissions () { const internalCertificate = { allowed_ssl_files: ['certificate', 'certificate_key', 'intermediate_certificate'], - interval_timeout: 1000 * 60 * 60 * 12, // 12 hours + interval_timeout: 1000 * 60 * 60, // 1 hour interval: null, interval_processing: false, @@ -205,7 +203,7 @@ const internalCertificate = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {String} [data.email] * @param {String} [data.name] * @return {Promise} @@ -251,7 +249,7 @@ const internalCertificate = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {Array} [data.expand] * @param {Array} [data.omit] * @return {Promise} @@ -297,7 +295,7 @@ const internalCertificate = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {String} [data.reason] * @returns {Promise} */ @@ -381,7 +379,7 @@ const internalCertificate = { /** * Report use * - * @param {Integer} user_id + * @param {Number} user_id * @param {String} visibility * @returns {Promise} */ @@ -522,7 +520,7 @@ const internalCertificate = { /** * @param {Access} access * @param {Object} data - * @param {Integer} data.id + * @param {Number} data.id * @param {Object} data.files * @returns {Promise} */ @@ -734,6 +732,36 @@ const internalCertificate = { }); }, + /** + * @param {Access} access + * @param {Object} data + * @param {Number} data.id + * @returns {Promise} + */ + renew: (access, data) => { + return access.can('certificates:update', data) + .then(() => { + return internalCertificate.get(access, data); + }) + .then((certificate) => { + if (certificate.provider === 'letsencrypt') { + return internalCertificate.renewLetsEncryptSsl(certificate) + .then(() => { + return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem') + }) + .then(cert_info => { + return certificateModel + .query() + .patchAndFetchById(certificate.id, { + expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')') + }); + }); + } else { + throw new error.ValidationError('Only Let\'sEncrypt certificates can be renewed'); + } + }) + }, + /** * @param {Object} certificate the certificate row * @returns {Promise} @@ -762,17 +790,29 @@ const internalCertificate = { revokeLetsEncryptSsl: (certificate, throw_errors) => { logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', ')); - let cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (debug_mode ? '--staging' : ''); + let revoke_cmd = certbot_command + ' revoke --cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' + (debug_mode ? '--staging' : ''); + let delete_cmd = certbot_command + ' delete --cert-name "npm-' + certificate.id + '" ' + (debug_mode ? '--staging' : ''); if (debug_mode) { - logger.info('Command:', cmd); + logger.info('Command:', revoke_cmd); } - return utils.exec(cmd) - .then(result => { + return utils.exec(revoke_cmd) + .then((result) => { logger.info(result); return result; }) + .then(() => { + if (debug_mode) { + logger.info('Command:', delete_cmd); + } + + return utils.exec(delete_cmd) + .then((result) => { + logger.info(result); + return result; + }) + }) .catch(err => { if (debug_mode) { logger.error(err.message); @@ -796,7 +836,7 @@ const internalCertificate = { /** * @param {Object} in_use_result - * @param {Integer} in_use_result.total_count + * @param {Number} in_use_result.total_count * @param {Array} in_use_result.proxy_hosts * @param {Array} in_use_result.redirection_hosts * @param {Array} in_use_result.dead_hosts @@ -826,7 +866,7 @@ const internalCertificate = { /** * @param {Object} in_use_result - * @param {Integer} in_use_result.total_count + * @param {Number} in_use_result.total_count * @param {Array} in_use_result.proxy_hosts * @param {Array} in_use_result.redirection_hosts * @param {Array} in_use_result.dead_hosts diff --git a/src/backend/routes/api/nginx/certificates.js b/src/backend/routes/api/nginx/certificates.js index 04fafdf..4c873bc 100644 --- a/src/backend/routes/api/nginx/certificates.js +++ b/src/backend/routes/api/nginx/certificates.js @@ -1,5 +1,3 @@ -'use strict'; - const express = require('express'); const validator = require('../../../lib/validator'); const jwtdecode = require('../../../lib/express/jwt-decode'); @@ -94,13 +92,13 @@ router certificate_id: { $ref: 'definitions#/definitions/id' }, - expand: { + expand: { $ref: 'definitions#/definitions/expand' } } }, { certificate_id: req.params.certificate_id, - expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) + expand: (typeof req.query.expand === 'string' ? req.query.expand.split(',') : null) }) .then(data => { return internalCertificate.get(res.locals.access, { @@ -181,6 +179,34 @@ router } }); +/** + * Renew LE Certs + * + * /api/nginx/certificates/123/renew + */ +router + .route('/:certificate_id/renew') + .options((req, res) => { + res.sendStatus(204); + }) + .all(jwtdecode()) + + /** + * POST /api/nginx/certificates/123/renew + * + * Renew certificate + */ + .post((req, res, next) => { + internalCertificate.renew(res.locals.access, { + id: parseInt(req.params.certificate_id, 10) + }) + .then(result => { + res.status(200) + .send(result); + }) + .catch(next); + }); + /** * Validate Certs before saving * diff --git a/src/frontend/js/app/api.js b/src/frontend/js/app/api.js index c8d5719..74356f0 100644 --- a/src/frontend/js/app/api.js +++ b/src/frontend/js/app/api.js @@ -1,5 +1,3 @@ -'use strict'; - const $ = require('jquery'); const _ = require('underscore'); const Tokens = require('./tokens'); @@ -11,8 +9,8 @@ const Tokens = require('./tokens'); * @constructor */ const ApiError = function (message, debug, code) { - let temp = Error.call(this, message); - temp.name = this.name = 'ApiError'; + let temp = Error.call(this, message); + temp.name = this.name = 'ApiError'; this.stack = temp.stack; this.message = temp.message; this.debug = debug; @@ -35,7 +33,7 @@ ApiError.prototype = Object.create(Error.prototype, { * @param {Object} [options] * @returns {Promise} */ -function fetch (verb, path, data, options) { +function fetch(verb, path, data, options) { options = options || {}; return new Promise(function (resolve, reject) { @@ -55,7 +53,7 @@ function fetch (verb, path, data, options) { contentType: options.contentType || 'application/json; charset=UTF-8', processData: options.processData || true, crossDomain: true, - timeout: options.timeout ? options.timeout : 15000, + timeout: options.timeout ? options.timeout : 30000, xhrFields: { withCredentials: true }, @@ -99,7 +97,7 @@ function fetch (verb, path, data, options) { * @param {Array} expand * @returns {String} */ -function makeExpansionString (expand) { +function makeExpansionString(expand) { let items = []; _.forEach(expand, function (exp) { items.push(encodeURIComponent(exp)); @@ -114,7 +112,7 @@ function makeExpansionString (expand) { * @param {String} [query] * @returns {Promise} */ -function getAllObjects (path, expand, query) { +function getAllObjects(path, expand, query) { let params = []; if (typeof expand === 'object' && expand !== null && expand.length) { @@ -128,20 +126,7 @@ function getAllObjects (path, expand, query) { return fetch('get', path + (params.length ? '?' + params.join('&') : '')); } -/** - * @param {String} path - * @param {FormData} form_data - * @returns {Promise} - */ -function upload (path, form_data) { - console.log('UPLOAD:', path, form_data); - return fetch('post', path, form_data, { - contentType: 'multipart/form-data', - processData: false - }); -} - -function FileUpload (path, fd) { +function FileUpload(path, fd) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); let token = Tokens.getTopToken(); @@ -214,7 +199,7 @@ module.exports = { Users: { /** - * @param {Integer|String} user_id + * @param {Number|String} user_id * @param {Array} [expand] * @returns {Promise} */ @@ -639,6 +624,14 @@ module.exports = { */ validate: function (form_data) { return FileUpload('nginx/certificates/validate', form_data); + }, + + /** + * @param {Number} id + * @returns {Promise} + */ + renew: function (id) { + return fetch('post', 'nginx/certificates/' + id + '/renew'); } } }, diff --git a/src/frontend/js/app/nginx/certificates/list/item.ejs b/src/frontend/js/app/nginx/certificates/list/item.ejs index ad22fb5..dd79405 100644 --- a/src/frontend/js/app/nginx/certificates/list/item.ejs +++ b/src/frontend/js/app/nginx/certificates/list/item.ejs @@ -5,16 +5,23 @@