mirror of
https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git
synced 2025-02-08 12:38:14 -05:00
Merge pull request #1343 from ssrahul96/develop
Added support to download Let's Encrypt Certificate
This commit is contained in:
commit
1626c8edd1
@ -13,6 +13,8 @@ const internalHost = require('./host');
|
|||||||
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
const letsencryptStaging = process.env.NODE_ENV !== 'production';
|
||||||
const letsencryptConfig = '/etc/letsencrypt.ini';
|
const letsencryptConfig = '/etc/letsencrypt.ini';
|
||||||
const certbotCommand = 'certbot';
|
const certbotCommand = 'certbot';
|
||||||
|
const archiver = require('archiver');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
function omissions() {
|
function omissions() {
|
||||||
return ['is_deleted'];
|
return ['is_deleted'];
|
||||||
@ -335,6 +337,71 @@ const internalCertificate = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Access} access
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Number} data.id
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
download: (access, data) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
access.can('certificates:get', data)
|
||||||
|
.then(() => {
|
||||||
|
return internalCertificate.get(access, data);
|
||||||
|
})
|
||||||
|
.then((certificate) => {
|
||||||
|
if (certificate.provider === 'letsencrypt') {
|
||||||
|
const zipDirectory = '/etc/letsencrypt/live/npm-' + data.id;
|
||||||
|
|
||||||
|
if (!fs.existsSync(zipDirectory)) {
|
||||||
|
throw new error.ItemNotFoundError('Certificate ' + certificate.nice_name + ' does not exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
let certFiles = fs.readdirSync(zipDirectory)
|
||||||
|
.filter((fn) => fn.endsWith('.pem'))
|
||||||
|
.map((fn) => fs.realpathSync(path.join(zipDirectory, fn)));
|
||||||
|
const downloadName = 'npm-' + data.id + '-' + `${Date.now()}.zip`;
|
||||||
|
const opName = '/tmp/' + downloadName;
|
||||||
|
internalCertificate.zipFiles(certFiles, opName)
|
||||||
|
.then(() => {
|
||||||
|
logger.debug('zip completed : ', opName);
|
||||||
|
const resp = {
|
||||||
|
fileName: opName
|
||||||
|
};
|
||||||
|
resolve(resp);
|
||||||
|
}).catch((err) => reject(err));
|
||||||
|
} else {
|
||||||
|
throw new error.ValidationError('Only Let\'sEncrypt certificates can be downloaded');
|
||||||
|
}
|
||||||
|
}).catch((err) => reject(err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} source
|
||||||
|
* @param {String} out
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
zipFiles(source, out) {
|
||||||
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||||
|
const stream = fs.createWriteStream(out);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
source
|
||||||
|
.map((fl) => {
|
||||||
|
let fileName = path.basename(fl);
|
||||||
|
logger.debug(fl, 'added to certificate zip');
|
||||||
|
archive.file(fl, { name: fileName });
|
||||||
|
});
|
||||||
|
archive
|
||||||
|
.on('error', (err) => reject(err))
|
||||||
|
.pipe(stream);
|
||||||
|
|
||||||
|
stream.on('close', () => resolve());
|
||||||
|
archive.finalize();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Access} access
|
* @param {Access} access
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
"main": "js/index.js",
|
"main": "js/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "^6.12.0",
|
"ajv": "^6.12.0",
|
||||||
|
"archiver": "^5.3.0",
|
||||||
"batchflow": "^0.4.0",
|
"batchflow": "^0.4.0",
|
||||||
"bcrypt": "^5.0.0",
|
"bcrypt": "^5.0.0",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
|
@ -209,6 +209,35 @@ router
|
|||||||
.catch(next);
|
.catch(next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download LE Certs
|
||||||
|
*
|
||||||
|
* /api/nginx/certificates/123/download
|
||||||
|
*/
|
||||||
|
router
|
||||||
|
.route('/:certificate_id/download')
|
||||||
|
.options((req, res) => {
|
||||||
|
res.sendStatus(204);
|
||||||
|
})
|
||||||
|
.all(jwtdecode())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/nginx/certificates/123/download
|
||||||
|
*
|
||||||
|
* Renew certificate
|
||||||
|
*/
|
||||||
|
.get((req, res, next) => {
|
||||||
|
internalCertificate.download(res.locals.access, {
|
||||||
|
id: parseInt(req.params.certificate_id, 10)
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
res.status(200)
|
||||||
|
.download(result.fileName);
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate Certs before saving
|
* Validate Certs before saving
|
||||||
*
|
*
|
||||||
|
@ -152,6 +152,51 @@ function FileUpload(path, fd) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ref : https://codepen.io/chrisdpratt/pen/RKxJNo
|
||||||
|
function DownloadFile(verb, path, filename) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
let api_url = '/api/';
|
||||||
|
let url = api_url + path;
|
||||||
|
let token = Tokens.getTopToken();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: verb,
|
||||||
|
crossDomain: true,
|
||||||
|
xhrFields: {
|
||||||
|
withCredentials: true,
|
||||||
|
responseType: 'blob'
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeSend: function (xhr) {
|
||||||
|
xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null));
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function (data) {
|
||||||
|
var a = document.createElement('a');
|
||||||
|
var url = window.URL.createObjectURL(data);
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.append(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function (xhr, status, error_thrown) {
|
||||||
|
let code = 400;
|
||||||
|
|
||||||
|
if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') {
|
||||||
|
error_thrown = xhr.responseJSON.error.message;
|
||||||
|
code = xhr.responseJSON.error.code || 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new ApiError(error_thrown, xhr.responseText, code));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
status: function () {
|
status: function () {
|
||||||
return fetch('get', '');
|
return fetch('get', '');
|
||||||
@ -638,6 +683,14 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
renew: function (id, timeout = 180000) {
|
renew: function (id, timeout = 180000) {
|
||||||
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
|
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Number} id
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
download: function (id) {
|
||||||
|
return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
<span class="dropdown-header"><%- i18n('audit-log', 'certificate') %> #<%- id %></span>
|
<span class="dropdown-header"><%- i18n('audit-log', 'certificate') %> #<%- id %></span>
|
||||||
<% if (provider === 'letsencrypt') { %>
|
<% if (provider === 'letsencrypt') { %>
|
||||||
<a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a>
|
<a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a>
|
||||||
|
<a href="#" class="download dropdown-item"><i class="dropdown-icon fe fe-download"></i> <%- i18n('certificates', 'download') %></a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
<a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a>
|
||||||
|
@ -11,7 +11,8 @@ module.exports = Mn.View.extend({
|
|||||||
ui: {
|
ui: {
|
||||||
host_link: '.host-link',
|
host_link: '.host-link',
|
||||||
renew: 'a.renew',
|
renew: 'a.renew',
|
||||||
delete: 'a.delete'
|
delete: 'a.delete',
|
||||||
|
download: 'a.download'
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
events: {
|
||||||
@ -29,6 +30,11 @@ module.exports = Mn.View.extend({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
|
let win = window.open($(e.currentTarget).attr('rel'), '_blank');
|
||||||
win.focus();
|
win.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
'click @ui.download': function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
App.Api.Nginx.Certificates.download(this.model.get('id'))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -188,6 +188,7 @@
|
|||||||
"other-certificate-key": "Certificate Key",
|
"other-certificate-key": "Certificate Key",
|
||||||
"other-intermediate-certificate": "Intermediate Certificate",
|
"other-intermediate-certificate": "Intermediate Certificate",
|
||||||
"force-renew": "Renew Now",
|
"force-renew": "Renew Now",
|
||||||
|
"download": "Download",
|
||||||
"renew-title": "Renew Let'sEncrypt Certificate"
|
"renew-title": "Renew Let'sEncrypt Certificate"
|
||||||
},
|
},
|
||||||
"access-lists": {
|
"access-lists": {
|
||||||
|
Loading…
Reference in New Issue
Block a user