From 41919db8f88aefbcd80d239773fc079507479ca3 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Tue, 6 Aug 2024 17:52:00 -0400 Subject: [PATCH] wild card UI done --- nodejs/models/host.js | 73 +++++++++++++++++--------------- nodejs/public/js/app.js | 9 +++- nodejs/public/lib/js/app-base.js | 16 +++++++ nodejs/routes/host.js | 31 ++++++++++---- nodejs/utils/letsencrypt.js | 19 +++------ nodejs/views/hosts.ejs | 70 +++++++++++++++++++++++++----- 6 files changed, 149 insertions(+), 69 deletions(-) diff --git a/nodejs/models/host.js b/nodejs/models/host.js index 7ef9e95..36210e7 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -30,6 +30,7 @@ class Host extends Table{ 'is_wildcard': {default: false, isRequired: false, type: 'boolean',}, 'wildcard_status': {isRequired: false, type: 'string', min: 3, max: 500, default: 'Requesting'}, 'wildcard_parent': {isRequired: false, type: 'string', min: 3, max: 500}, + 'wildcard_expires': {isRequired: false, type: 'number'}, } static lookUpObj = {}; @@ -62,7 +63,7 @@ class Host extends Table{ parent: parentOBJ.host }); }catch(error){ - console.error('add cahce error', {...parentOBJ, host, is_cache: true}, error) + console.error('add cache error', {...parentOBJ, host, is_cache: true}, error) throw error; } } @@ -103,16 +104,18 @@ class Host extends Table{ try{ let host = this; + + await host.update({ + wildcard_status: 'Requesting', + }); let cert = await letsEncrypt.dnsWildcard(this.host, { challengeCreateFn: async (authz, challenge, keyAuthorization) => { - host.update({ - wildcard_status: `Adding record for ${authz.identifier.value}` + await host.update({ + wildcard_status: `Adding record` }); try{ let parts = tldExtract(authz.identifier.value); - console.log('adding DNS record for', `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`) - let res = await porkBun.createRecordForce( parts.domain, { @@ -121,56 +124,55 @@ class Host extends Table{ content: `${keyAuthorization}` } ); - console.log('porkbun res', res) }catch(error){ console.log('model Host challengeCreateFn error:', error) - host.update({ + await host.update({ wildcard_status: `Add DNS record failed` }); } }, onDnsCheck: async(authz, checkCount)=>{ - host.update({ - wildcard_status: `${checkCount} Checking DNS for ${authz.identifier.value}` + await host.update({ + wildcard_status: `${checkCount} Checking DNS` }); }, onDnsCheckFail: async(authz, error)=>{ - host.update({ + await host.update({ wildcard_status: `DNS check failed for ${authz.identifier.value}` }); }, onDnsCheckFound: async(authz)=>{ - host.update({ - wildcard_status: `DNS check found for ${authz.identifier.value}` + await host.update({ + wildcard_status: `DNS check found` }); }, onDnsCheckSuccess: async(authz)=>{ - host.update({ - wildcard_status: `DNS check success for ${authz.identifier.value}` + await host.update({ + wildcard_status: `DNS check success` }) }, onDnsCheckRemove: async(authz)=>{ - host.update({ - wildcard_status: `DNS remove record for ${authz.identifier.value}` + await host.update({ + wildcard_status: `DNS remove record` }) }, challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{ - host.update({ - wildcard_status: `DNS remove record for ${authz.identifier.value}` + await host.update({ + wildcard_status: `DNS remove record` }) try{ - // let parts = tldExtract(authz.identifier.value); - // await porkBun.deleteRecords( - // parts.domain, - // { - // type:'TXT', - // name: `_acme-challenge${parts.sub ? `.${parts.sub}` : '' - // }`, - // content: `${keyAuthorization}`} - // ); + let parts = tldExtract(authz.identifier.value); + await porkBun.deleteRecords( + parts.domain, + { + type:'TXT', + name: `_acme-challenge${parts.sub ? `.${parts.sub}` : '' + }`, + content: `${keyAuthorization}`} + ); }catch(error){ - host.update({ + await host.update({ wildcard_status: `DNS remove record failed for ${authz.identifier.value}` }) } @@ -188,8 +190,9 @@ class Host extends Table{ await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd)); - this.update({ - wildcard_status: `Done` + await this.update({ + wildcard_status: `Done`, + wildcard_expires: toAdd.real_expiry*1000, }); return this; @@ -204,8 +207,8 @@ class Host extends Table{ async update(...args){ try{ let out = await super.update(...args) - await this.bustCache(this.host) - await Host.buildLookUpObj() + await this.bustCache(this.host); + await Host.buildLookUpObj(); return out; } catch(error){ @@ -215,9 +218,9 @@ class Host extends Table{ async remove(...args){ try{ - let out = await super.remove(...args) - await Host.buildLookUpObj() - await this.bustCache(this.host) + let out = await super.remove(...args); + await Host.buildLookUpObj(); + await this.bustCache(this.host); return out; } catch(error){ diff --git a/nodejs/public/js/app.js b/nodejs/public/js/app.js index 096b7f4..a733b4c 100755 --- a/nodejs/public/js/app.js +++ b/nodejs/public/js/app.js @@ -26,10 +26,17 @@ app.host = (function(app){ function remove(args, callack){ app.api.delete('host/'+ args.host, function(error, data){ callack(error, data); - }) + }); + } + + function getCert(args, callack){ + app.api.get('cert/'+args.host, function(error, data){ + callack(error, data); + }); } return { + getCert, list: list, get: get, add: add, diff --git a/nodejs/public/lib/js/app-base.js b/nodejs/public/lib/js/app-base.js index b50d647..293842d 100644 --- a/nodejs/public/lib/js/app-base.js +++ b/nodejs/public/lib/js/app-base.js @@ -311,7 +311,23 @@ app.util = (function(app){ return obj; }; + function downloadFile(filename, text){ + // https://stackoverflow.com/a/18197341 + + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + } + return { + downloadFile: downloadFile, getUrlParameter: getUrlParameter, actionMessage: actionMessage } diff --git a/nodejs/routes/host.js b/nodejs/routes/host.js index 2ab5430..af001fd 100755 --- a/nodejs/routes/host.js +++ b/nodejs/routes/host.js @@ -29,7 +29,19 @@ router.post('/', async function(req, res, next){ } }); -router.get('/:item(*)', async function(req, res, next){ +router.get('/lookup/:item', async function(req, res, next){ + try{ + return res.json({ + string: req.params.item, + results: await Model.lookUp(req.params.item), + }); + + }catch(error){ + return next(error); + } +}); + +router.get('/:item', async function(req, res, next){ try{ return res.json({ @@ -41,7 +53,7 @@ router.get('/:item(*)', async function(req, res, next){ } }); -router.put('/:item(*)', async function(req, res, next){ +router.put('/:item', async function(req, res, next){ try{ req.body.updated_by = req.user.username; let item = await Model.get(req.params.item); @@ -59,7 +71,7 @@ router.put('/:item(*)', async function(req, res, next){ } }); -router.delete('/:item(*)', async function(req, res, next){ +router.delete('/:item', async function(req, res, next){ try{ let item = await Model.get(req.params.item); let count = await item.remove(); @@ -74,15 +86,16 @@ router.delete('/:item(*)', async function(req, res, next){ } }); -router.get('/lookup/:item(*)', async function(req, res, next){ +router.put('/:item/renew', async function(req, res, next){ try{ - return res.json({ - string: req.params.item, - results: await Model.lookUp(req.params.item), - }); + let item = await Model.get(req.params.item); + item.createWildcardCert(); + return res.json({ + message: `Requesting wildcard cert for ${req.params.item}`, + }) }catch(error){ - return next(error); + next(error); } }); diff --git a/nodejs/utils/letsencrypt.js b/nodejs/utils/letsencrypt.js index 8ea66f4..908aa77 100644 --- a/nodejs/utils/letsencrypt.js +++ b/nodejs/utils/letsencrypt.js @@ -7,10 +7,6 @@ const sleep = require('./sleep'); // https://dns.google/resolve?name=${name}&type=TXT -AcmeClient.setLogger((message) => { - console.log('ACME:', message); -}); - class LetsEncrypt{ static AcmeClient = AcmeClient; @@ -64,15 +60,12 @@ class LetsEncrypt{ skipChallengeVerification: true, challengeCreateFn: async (authz, challenge, keyAuthorization) => { try{ - console.log('challenge', challenge) - console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization} challenge=${challenge} googleDNS=https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`) - dnsToAdd++ + dnsToAdd++; let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){ - await sleep(1000); - dnsFound++ + dnsFound++; if(dnsFound === dnsToAdd){ - options.onDnsCheckFound(authz, dnsFound) + options.onDnsCheckFound(authz, dnsFound); } return; } @@ -83,13 +76,11 @@ class LetsEncrypt{ while(true){ options.onDnsCheck(authz, checkCount); let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); - // console.log(keyAuthorization, res.data); if(res.data.Answer && res.data.Answer.some(record => record.data === keyAuthorization)){ - dnsFound++ + dnsFound++; if(dnsFound === dnsToAdd){ - options.onDnsCheckFound(authz, dnsFound) + options.onDnsCheckFound(authz, dnsFound); } - // console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`) await sleep(10000); break; } diff --git a/nodejs/views/hosts.ejs b/nodejs/views/hosts.ejs index 00634c8..2b718e3 100755 --- a/nodejs/views/hosts.ejs +++ b/nodejs/views/hosts.ejs @@ -43,9 +43,21 @@ function parseHostRow(host) { host['updated_on_text'] = moment(host['updated_on'], "x").fromNow(); + host['wildcard_expires_text'] = moment(host['wildcard_expires'], "x").fromNow(); host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://'; host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://'; host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto'; + host['wildcard_text_bg'] = 'warning'; + if(!host['is_wildcard']){ + host['wildcard_text_bg'] = 'success'; + } + if(host['wildcard_status'] == 'Done'){ + host['wildcard_text_bg'] = 'success'; + host['wildcard_text'] = undefined; + } + if(host['wildcard_status'].includes('failed')){ + host['wildcard_text_bg'] = 'danger'; + } return host; } @@ -94,6 +106,13 @@ }); } + function downloadCert(host, type){ + app.host.getCert({host}, function(error, data){ + if(error) app.util.actionMessage(error.message, $.scope.hosts.$this, 'danger'); + app.util.downloadFile(`${host}-${type}.crt`, data[type]) + }); + } + $(document).ready(function(){ $editHostForm = $('#addHost').clone(); $editHostForm.find('hr.buttonBreak').nextAll().remove(); @@ -121,9 +140,9 @@ $el.slideUp(); }); - app.subscribe(/^model:Host/, function(data, topic){ - console.log(topic, data); - }); + // app.subscribe(/^model:Host/, function(data, topic){ + // console.log(topic, data); + // }); app.subscribe(/^model:Host:create/, function(data, topic){ let [a,b, action, host] = topic.split(':'); @@ -350,7 +369,7 @@ - SSL + SSL Expire Host Name @@ -365,10 +384,14 @@ Actions + - + {{{ wildcard_text }}} + {{#wildcard_expires}} + {{wildcard_expires_text}} + {{/wildcard_expires}} @@ -378,15 +401,42 @@ {{{ targetssl_text }}}{{ ip }}:{{ targetPort }} - + {{ updated_on_text }}
- + +
+ + +
+