From 84db245f4a1b0446ecd495c526f616f958fe30c1 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Tue, 6 Aug 2024 13:11:55 -0400 Subject: [PATCH] fixed issue with redis models --- nodejs/app.js | 32 +++-- nodejs/controller/auth.js | 8 -- nodejs/models/host.js | 128 +++++++++++++++---- nodejs/models/token.js | 2 +- nodejs/models/user_redis.js | 45 +------ nodejs/package-lock.json | 2 +- nodejs/package.json | 2 +- nodejs/public/lib/js/app-base.js | 72 +++++++---- nodejs/public/lib/js/jq-repeat_new.js | 65 +++++++--- nodejs/public/lib/js/val.js | 123 ++++++++---------- nodejs/routes/auth.js | 1 + nodejs/routes/host.js | 2 +- nodejs/routes/render.js | 3 + nodejs/utils/letsencrypt.js | 60 ++++++--- nodejs/utils/model_pubsub.js | 30 +++-- nodejs/utils/object_validate.js | 6 +- nodejs/utils/redis_model.js | 46 +++++-- nodejs/views/hosts.ejs | 176 +++++++++++++++----------- nodejs/views/test.ejs | 74 +++++++++++ nodejs/views/users.ejs | 4 +- 20 files changed, 558 insertions(+), 323 deletions(-) create mode 100644 nodejs/views/test.ejs diff --git a/nodejs/app.js b/nodejs/app.js index c278ff0..769dfd9 100755 --- a/nodejs/app.js +++ b/nodejs/app.js @@ -53,20 +53,28 @@ app.use('/api', require('./routes/api')); // Catch 404 and forward to error handler. If none of the above routes are // used, this is what will be called. -app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.message = 'Page not found' - err.status = 404; - next(err); +app.use(async function(req, res, next) { + try{ + var err = new Error('Not Found'); + err.message = 'Page not found' + err.status = 404; + next(err); + }catch(error){ + console.log('app 404 catch error', error) + } }); // Error handler. This is where `next()` will go on error -app.use(function(err, req, res, next) { - console.error(err.status || res.status, err.name, req.method, req.url); - console.error(err.message); - console.error(err.stack); - console.error('========================================='); +app.use(async function(err, req, res, next) { + try{ + console.error(err.status || res.status, err.name, req.method, req.url); + console.error(err.message); + console.error(err.stack); + console.error('========================================='); - res.status(err.status || 500); - res.json({name: err.name, message: err.message}); + res.status(err.status || 500); + res.json({name: err.name, message: err.message, keys: err.keys}); + }catch(error){ + console.log('error in the catch all error fn....', error); + } }); diff --git a/nodejs/controller/auth.js b/nodejs/controller/auth.js index f57b140..0749b98 100644 --- a/nodejs/controller/auth.js +++ b/nodejs/controller/auth.js @@ -45,12 +45,4 @@ class Auth{ } } - -Auth.logOut = async function(data){ - try{ - }catch(error){ - throw error; - } -} - module.exports = {Auth}; diff --git a/nodejs/models/host.js b/nodejs/models/host.js index 1c74eca..7ef9e95 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -28,6 +28,7 @@ class Host extends Table{ 'targetssl': {isRequired: false, default: false, type: 'boolean'}, 'is_cache': {default: false, isRequired: false, type: 'boolean',}, '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}, } @@ -84,11 +85,11 @@ class Host extends Table{ } } - static async add(data, ...args){ + static async create(data, ...args){ try{ - let out = await super.create(data, ...args) - await this.buildLookUpObj() - if(out.is_wildcard) await out.createWildcardCert() + let out = await super.create(data, ...args); + await this.buildLookUpObj(); + if(out.is_wildcard) out.createWildcardCert(); return out; @@ -100,31 +101,104 @@ class Host extends Table{ async createWildcardCert(){ if(!this.host.startsWith('*.')) throw new Error('not wild card'); - let cert = await letsEncrypt.dnsWildcard(this.host, { - challengeCreateFn: async (authz, challenge, keyAuthorization) => { - let parts = tldExtract(authz.identifier.value); - let res = await porkBun.createRecordForce(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`}); - }, - challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{ - let parts = tldExtract(authz.identifier.value); - await porkBun.deleteRecords(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`}); - }, - }); + try{ + let host = this; + let cert = await letsEncrypt.dnsWildcard(this.host, { + challengeCreateFn: async (authz, challenge, keyAuthorization) => { + host.update({ + wildcard_status: `Adding record for ${authz.identifier.value}` + }); + try{ + let parts = tldExtract(authz.identifier.value); - let toAdd = { - cert_pem: cert.cert.split('\n\n')[0], - fullchain_pem: cert.cert, - privkey_pem: cert.key.toString(), - csr_pem: cert.csr.toString(), - expiry: 4120307657, - real_expiry: +LetsEncrypt.AcmeClient.crypto.readCertificateInfo(cert.cert).notAfter/1000, + console.log('adding DNS record for', `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`) + + let res = await porkBun.createRecordForce( + parts.domain, + { + type:'TXT', + name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, + content: `${keyAuthorization}` + } + ); + console.log('porkbun res', res) + }catch(error){ + console.log('model Host challengeCreateFn error:', error) + host.update({ + wildcard_status: `Add DNS record failed` + }); + } + }, + onDnsCheck: async(authz, checkCount)=>{ + host.update({ + wildcard_status: `${checkCount} Checking DNS for ${authz.identifier.value}` + }); + }, + onDnsCheckFail: async(authz, error)=>{ + 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}` + }); + }, + onDnsCheckSuccess: async(authz)=>{ + host.update({ + wildcard_status: `DNS check success for ${authz.identifier.value}` + }) + }, + onDnsCheckRemove: async(authz)=>{ + host.update({ + wildcard_status: `DNS remove record for ${authz.identifier.value}` + }) + }, + challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{ + host.update({ + wildcard_status: `DNS remove record for ${authz.identifier.value}` + }) + try{ + // 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({ + wildcard_status: `DNS remove record failed for ${authz.identifier.value}` + }) + } + }, + }); + + let toAdd = { + cert_pem: cert.cert.split('\n\n')[0], + fullchain_pem: cert.cert, + privkey_pem: cert.key.toString(), + csr_pem: cert.csr.toString(), + expiry: 4120307657, + real_expiry: +LetsEncrypt.AcmeClient.crypto.readCertificateInfo(cert.cert).notAfter/1000, + } + + + await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd)); + this.update({ + wildcard_status: `Done` + }); + + return this; + }catch(error){ + console.log('le failed', error) + this.update({ + wildcard_status: `LE failed` + }); } - - - await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd)); - return toAdd; - - // Get new wildcard cert from } async update(...args){ diff --git a/nodejs/models/token.js b/nodejs/models/token.js index 845e891..ee566a2 100644 --- a/nodejs/models/token.js +++ b/nodejs/models/token.js @@ -37,7 +37,7 @@ class AuthToken extends Token{ return await User.get(this.created_by); } - static async add(data){ + static async create(data){ data.created_by = data.username; return super.create(data) diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js index afefecb..5373d8f 100644 --- a/nodejs/models/user_redis.js +++ b/nodejs/models/user_redis.js @@ -1,7 +1,6 @@ 'use strict'; const Table = require('../utils/redis_model'); -// const {Token, InviteToken} = require('./token'); const bcrypt = require('bcrypt'); const saltRounds = 10; @@ -19,9 +18,11 @@ class User extends Table{ static backing = 'redis' - static async add(data) { + static async create(data) { try{ + console.log('hash?') data['password'] = await bcrypt.hash(data['password'], saltRounds); + console.log('hashed password', data.password); data['backing'] = data['backing'] || 'redis'; @@ -32,31 +33,6 @@ class User extends Table{ } } -/* static async addByInvite(data){ - try{ - let token = await InviteToken.get(data.token); - - if(!token.is_valid){ - let error = new Error('Token Invalid'); - error.name = 'Token Invalid'; - error.message = `Token is not valid or as allready been used. ${data.token}`; - error.status = 401; - throw error; - } - - let user = await this.create(data); - - if(user){ - await token.consume({claimed_by: user.username}); - return user; - } - - }catch(error){ - throw error; - } - - };*/ - async setPassword(data){ try{ data['password'] = await bcrypt.hash(data['password'], saltRounds); @@ -67,17 +43,6 @@ class User extends Table{ } } -/* async invite(){ - try{ - let token = await InviteToken.create({created_by: this.username}); - - return token; - - }catch(error){ - throw error; - } - }*/ - static async login(data){ try{ @@ -109,7 +74,7 @@ module.exports = {User}; (async function(){ - var defaultUser = 'proxyadmin3' + var defaultUser = 'proxyadmin2' try{ let user = await User.get(defaultUser); }catch(error){ @@ -117,7 +82,7 @@ module.exports = {User}; let user = await User.create({ username:defaultUser, password: defaultUser, - created_by:defaultUser + created_by: defaultUser }); console.log(defaultUser, 'created', user); }catch(error){ diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 1c6c0ed..f044463 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -15,7 +15,7 @@ "bcrypt": "^5.1.1", "bootstrap": "^5.3.3", "ejs": "^3.1.10", - "express": "^4.19.2", + "express": "^4.18.2", "extend": "^3.0.2", "jquery": "^3.7.1", "ldapts": "^2.12.0", diff --git a/nodejs/package.json b/nodejs/package.json index 709de97..fc7615b 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -18,7 +18,7 @@ "bcrypt": "^5.1.1", "bootstrap": "^5.3.3", "ejs": "^3.1.10", - "express": "^4.19.2", + "express": "^4.18.2", "extend": "^3.0.2", "jquery": "^3.7.1", "ldapts": "^2.12.0", diff --git a/nodejs/public/lib/js/app-base.js b/nodejs/public/lib/js/app-base.js index e061205..b50d647 100644 --- a/nodejs/public/lib/js/app-base.js +++ b/nodejs/public/lib/js/app-base.js @@ -252,10 +252,10 @@ app.user = (function(app){ app.util = (function(app){ function getUrlParameter(name){ - name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); - var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); - var results = regex.exec(location.search); - return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); + name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); + var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'); + var results = regex.exec(location.search); + return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); }; function actionMessage(message, $target, type, callback){ @@ -277,28 +277,38 @@ app.util = (function(app){ }) }else{ if(type) $target.addClass('bg-' + type); - message = '' + message + '' + message = '' + message + '' $target.html(message).slideDown('fast'); } setTimeout(callback,10) } $.fn.serializeObject = function(){ - var - arr = $(this).serializeArray(), - obj = {}; - - for(var i = 0; i < arr.length; i++){ - if(obj[arr[i].name] === undefined) { - obj[arr[i].name] = arr[i].value; - } else { - if(!(obj[arr[i].name] instanceof Array)) { - obj[arr[i].name] = [obj[arr[i].name]]; - } - obj[arr[i].name].push(arr[i].value); - } - } - return obj; + var + arr = $(this).serializeArray(), + obj = {}; + + for(var i = 0; i < arr.length; i++){ + if(obj[arr[i].name] === undefined) { + if(!arr[i].value) continue; + obj[arr[i].name] = arr[i].value; + let type = $(this).parent().find(`[name="${arr[i].name}"]`).attr('type'); + if(['number', 'range'].includes(type)){ + obj[arr[i].name] = Number(arr[i].value); + } + + if(['radio'].includes(type) && ['true', 'false'].includes(arr[i].value)){ + obj[arr[i].name] = arr[i].value == 'true' ? true : false; + } + } else { + if(!(obj[arr[i].name] instanceof Array)) { + obj[arr[i].name] = [obj[arr[i].name]]; + } + obj[arr[i].name].push(arr[i].value); + } + + } + return obj; }; return { @@ -342,12 +352,12 @@ function formAJAX(btn, del){ event.preventDefault(); // avoid to execute the actual submit of the form. var $form = $(btn).closest('[action]'); // gets the 'form' parent var formData = $form.find('[name]').serializeObject(); // builds query formDataing - var method = $form.attr('method') || 'post'; + var method = ($form.attr('method') || 'post').toLowerCase(); - // if( !$form.validate()){ - // app.util.actionMessage('Please fix the form errors.', $form, 'danger') - // return false; - // } + if($form.validate && !$form.validate()){ + app.util.actionMessage('Please fix the form errors.', $form, 'danger') + return false; + } app.util.actionMessage( '
Loading...
', @@ -357,9 +367,21 @@ function formAJAX(btn, del){ app.api[method]($form.attr('action'), formData, function(error, data){ app.util.actionMessage(data.message, $form, error ? 'danger' : 'success'); //re-populate table + $form.validateClear(); if(!error){ $form.trigger("reset"); eval($form.attr('evalAJAX')); //gets JS to run after completion + }else{ + console.log('formAJAX res error', error, data) + if(data && data.name === 'ObjectValidateError'){ + app.util.actionMessage('Please fix the form errors', $form, 'danger'); //re-populate table + } + if(data && data.keys){ + console.log('form key errors', data.keys) + for(let keyError of data.keys){ + $form.find(`[name=${keyError.key}]`).validateMessage(keyError.message); + } + } } }); } diff --git a/nodejs/public/lib/js/jq-repeat_new.js b/nodejs/public/lib/js/jq-repeat_new.js index 7834eaa..fb8e16e 100644 --- a/nodejs/public/lib/js/jq-repeat_new.js +++ b/nodejs/public/lib/js/jq-repeat_new.js @@ -10,7 +10,7 @@ MIT license $.scope = {}; } - var make = function( element ){ + var make = function(element){ var result = []; result.splice = function(inputValue, ...args){ @@ -20,9 +20,9 @@ MIT license var index; //if a string is submitted as the index, try to match it to index number - if( typeof arguments[0] === 'string' ){ + if(typeof arguments[0] === 'string'){ index = this.indexOf( arguments[0] );//set where to start - if ( index === -1 ) { + if (index === -1) { return []; } }else{ @@ -109,6 +109,7 @@ MIT license //set and return new array return Array.prototype.splice.apply(this, toProto); }; + result.push = function(){ //add one or more objects to the array @@ -123,19 +124,25 @@ MIT license //return new array length return this.length; }; + result.pop = function(){ //remove and return array element return this.splice( -1, 1 )[0]; }; - result.reverse = function() { - var temp = this.splice( 0 ); - Array.prototype.reverse.apply( temp ); - for( var i = 0; i < temp.length; i++ ){ - this.push( temp[i] ); + result.reverse = function() { + let hold = []; + for(let item of this){ + hold.push(item.__jq_$el.html()) } + for(let idx in hold.reverse()){ + this[idx].__jq_$el.html(hold[idx]); + } + + Array.prototype.reverse.apply( this ); + return this; }; @@ -149,6 +156,10 @@ MIT license return this.splice( 0, 1 )[0]; }; + result.unshift = function(data){ + return this.splice(0,0, data) + } + result.loop = function(){ var temp = this[0]; this.splice( 0,1 ); @@ -156,13 +167,17 @@ MIT license return temp; }; + result.loopUp = function(){ var temp = this[this.length-1]; this.splice( -1, 1 ); this.splice( 0, 0, temp ); return temp; }; + result.indexOf = function( key, value ){ + if(typeof key === 'number') return key; + if( typeof value !== 'string' ){ value = arguments[0]; key = this.__index; @@ -175,7 +190,8 @@ MIT license } return -1; }; - result.update = function( key, value, update ){ + + result.update = function(key, value, data){ //set variables using sting for index // If update is called with no index/key, assume its the 0 @@ -186,24 +202,31 @@ MIT license return this.splice(0, 1, key); } - if( typeof value !== 'string' ){ - update = arguments[1]; - value = arguments[0]; - key = this.__index; + if(typeof value !== 'string'){ + data = arguments[1]; + if(typeof key !== 'number'){ + value = arguments[0]; + key = this.__index; + } } + var index = this.indexOf( key, value ); + if(index === -1) { return []; } - var object = $.extend( true, {}, this[index], update ); - - var $render = $(Mustache.render(this.__jqTemplate, object)); - $render.attr('jq-repeat-index', index); - this[index].__jq_$el.replaceWith($render); + this[index] = $.extend( true, this[index], data ); + var $render = $(Mustache.render(this.__jqTemplate, this[index])); + $render.attr('jq-repeat-index', index); + + this.__update(this[index].__jq_$el, $render, this[index], this); this[index].__jq_$el = $render; - this.__update(this[index].__jq_$el, this[index], this); }; + + result.getByKey = function(key, value){ + return this[this.indexOf(key, value)]; + } result.__put = function($el, item, list){ $el.show(); @@ -213,8 +236,8 @@ MIT license $el.remove(); }; - result.__update = function($el, item, list){ - console.log('here', $el) + result.__update = function($el, $render, item, list){ + $el.replaceWith($render); $el.show(); }; diff --git a/nodejs/public/lib/js/val.js b/nodejs/public/lib/js/val.js index d5a75bc..1c340ee 100755 --- a/nodejs/public/lib/js/val.js +++ b/nodejs/public/lib/js/val.js @@ -1,96 +1,79 @@ ( function( $ ) { var settings = { rule: { - eq: function( value, options ) { - var compare = $( '[name=' + options + ']' ).val(); + eq: function(value, options){ + var compare = $('[name=' + options + ']').val(); if ( value != compare ) { return "Miss-match"; } } }, - - form: { - alertCount: false, //pop-up with error count - alertCountMessage: " errors!" - }, - - processValidation: function ( error_message, $input ) { - if ( typeof error_message == 'undefined' || error_message == true ) { - return; - } - - $( '' ).html( ' - ' + error_message ).appendTo( $input.siblings( 'label' ) ); - $input.parent().addClass("has-error"); - failedCount++; - return false; - } }; - var failedCount = 0; + $.fn.validate = function(event) { + // let thisSettings = $.extend(true, settings, settingsObj); + let hasErrors = false; - function processRule( thisSettings, $input ) { - var attr = $input.attr( 'validate' ).split( ':' ), //array of params - requirement = attr[1], - value = $input.val(), //link to input value - rule = attr[0]; + if(this.is('[validate]')) return this.validateField(event); - $input.siblings( 'label' ).children( 'b' ).remove(); //removes old error - $input.parent().removeClass( "has-error" ); //removes has-error class + if(!this.attr('isValid')){ + console.log('adding reset event') + this.on('reset', function(){ + $(this).attr('isValid', false); + $(this).validateClear(); + }) + } + + this.find('[validate]').each(function(){ + if(!$(this).validateField()) hasErrors = true; + }); + + this.attr('isValid', !hasErrors); + + if(hasErrors && event) event.preventDefault(); + + return !hasErrors; + }; + + $.fn.validateClear = function(){ + $(this).find('input').each(function(){ + $(this).removeClass('is-invalid'); + $(this).removeClass('is-valid'); + }) + } + + $.fn.validateField = function(){ + var attr = this.attr('validate').split(':'); //array of params + var rule = attr[0]; + var options = attr[1]; + var value = this.val(); //link to input value + var message; //checks if field is required, and length - if (isNaN(requirement) === false && requirement && value.length < requirement) { - return thisSettings.processValidation( 'Must be ' + requirement + ' characters', $input ); + if(!isNaN(options) && value.length < options){ + message = `Must be ${options} characters`; } //checks if empty to stop processing - if ( isNaN( requirement ) === false && value.length === 0 ) { - return; + if(!isNaN(options) && value.length === 0) { + }else if(rule in settings.rule){ + let message = settings.rule[rule].apply(this, [value, options]); } - if ( rule in thisSettings.rule ) { - return thisSettings.processValidation( thisSettings.rule[rule].apply( this, [value, requirement] ), $input ); - } + this.validateMessage(message) + return !message; } - - $.fn.validate = function( settingsObj, event ) { - event = event || window.event; - - failedCount = 0; - var thisForm = false, - thisSettings = $.extend( true, settings, settingsObj ); - if ( this.is( '[validate]' ) ) { - processRule( thisSettings, this ); - } else { - thisForm = true; - this.find( '[validate]' ).each( function () { - if(!processRule( thisSettings, $( this ) )){ - // failedCount++; - } - }); - } - - this.attr('isValid', !failedCount); - if ( failedCount === 0 ) { //no errors - return true; - } else { //errors - if ( thisForm ){ - if(thisSettings.form.alertCount){ - alert( failedCount + thisSettings.form.alertCountMessage ); - } - /* if(event) event.returnValue = false; - if(event) event.preventDefault(); - return false; - - if(event.preventDefault) if(event)*/ - //event.returnValue = false; - event.preventDefault(); - event.defaultPrevented; - - } - return false; + $.fn.validateMessage = function(message){ + if(message && message !== true){ + this.closest('.form-group').find('b.invalid-feedback').html(message); + this.addClass('is-invalid'); + }else{ + this.removeClass('is-invalid'); + this.addClass('is-valid'); } + return this; }; jQuery.extend({ diff --git a/nodejs/routes/auth.js b/nodejs/routes/auth.js index d651cb8..be6e7f8 100755 --- a/nodejs/routes/auth.js +++ b/nodejs/routes/auth.js @@ -3,6 +3,7 @@ const router = require('express').Router(); const { Auth } = require('../controller/auth'); + router.post('/login', async function(req, res, next){ try{ let auth = await Auth.login(req.body); diff --git a/nodejs/routes/host.js b/nodejs/routes/host.js index 6b9b8e5..2ab5430 100755 --- a/nodejs/routes/host.js +++ b/nodejs/routes/host.js @@ -25,7 +25,7 @@ router.post('/', async function(req, res, next){ ...item, }); } catch (error){ - return next(error); + next(error); } }); diff --git a/nodejs/routes/render.js b/nodejs/routes/render.js index c5d47d4..1a8a0f7 100644 --- a/nodejs/routes/render.js +++ b/nodejs/routes/render.js @@ -40,4 +40,7 @@ router.get('/login', async function(req, res, next) { res.render('login', {...values, redirect: req.query.redirect}); }); +router.get('/test', async function(req, res, next) { + res.render('test', {...values, redirect: req.query.redirect}); +}); module.exports = router; diff --git a/nodejs/utils/letsencrypt.js b/nodejs/utils/letsencrypt.js index a1141f3..8ea66f4 100644 --- a/nodejs/utils/letsencrypt.js +++ b/nodejs/utils/letsencrypt.js @@ -7,6 +7,11 @@ const sleep = require('./sleep'); // https://dns.google/resolve?name=${name}&type=TXT +AcmeClient.setLogger((message) => { + console.log('ACME:', message); +}); + + class LetsEncrypt{ static AcmeClient = AcmeClient; @@ -43,32 +48,57 @@ class LetsEncrypt{ try{ domain = domain.replace(/^\*\./, ''); + const [key, csr] = await AcmeClient.crypto.createCsr({ altNames: [domain, `*.${domain}`], }); + let dnsToAdd = 0; + let dnsFound = 0; + const cert = await this.client.auto({ csr, email: 'wmantly@gmail.com', termsOfServiceAgreed: true, challengePriority: ['dns-01'], + skipChallengeVerification: true, challengeCreateFn: async (authz, challenge, keyAuthorization) => { - // console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`) - - let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); - if(resCheck.data.Answer.some(record => record.data === keyAuthorization)) return; - - await options.challengeCreateFn(authz, challenge, keyAuthorization); - - let checkCount = 0; - while(true){ - await sleep(1500); - let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); - if(res.data.Answer.some(record => record.data === keyAuthorization)){ - // console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`) - break; + 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++ + 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++ + if(dnsFound === dnsToAdd){ + options.onDnsCheckFound(authz, dnsFound) + } + return; } - if(checkCount++ > 60) throw new Error('challengeCreateFn validation timed out'); + + await options.challengeCreateFn(authz, challenge, keyAuthorization); + + let checkCount = 0; + 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++ + if(dnsFound === dnsToAdd){ + options.onDnsCheckFound(authz, dnsFound) + } + // console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`) + await sleep(10000); + break; + } + if(checkCount++ > 60) throw new Error('challengeCreateFn validation timed out'); + await sleep(1500); + } + }catch(error){ + console.log('dns check failed error:', error) + options.onDnsCheckFail(authz, error) } }, challengeRemoveFn: options.challengeRemoveFn, diff --git a/nodejs/utils/model_pubsub.js b/nodejs/utils/model_pubsub.js index aac4056..924fb4f 100644 --- a/nodejs/utils/model_pubsub.js +++ b/nodejs/utils/model_pubsub.js @@ -12,9 +12,13 @@ function ModelPs(model){ } function publish(prop, res, req){ - if(!['add', 'create', 'update', 'remove'].includes(prop)) return; + try{ + if(!['add', 'create', 'update', 'remove'].includes(prop)) return; - ps.publish(`model:${Model.name}:${prop}:${getIndex(res, req)}`, res); + ps.publish(`model:${Model.name}:${prop}:${getIndex(res, req)}`, res); + }catch(error){ + console.log('ModelPs.publish ERROR', error) + } } return new Proxy(model, { @@ -28,16 +32,22 @@ function ModelPs(model){ const targetValue = Reflect.get(target, propKey, receiver); if (typeof targetValue === 'function') { return function(...args){ + try{ // let res = targetValue.apply(this, args); // (A) - let res = Reflect.apply(targetValue, this, args); - if(targetValue.constructor.name === 'AsyncFunction'){ - res.then(function(res){ - publish(propKey, res, ...args); - }); - }else{ - publish(propKey, res, ...args) + var res = Reflect.apply(targetValue, this, args); + if(targetValue.constructor.name === 'AsyncFunction'){ + res.then(function(res){ + publish(propKey, res, ...args); + }).catch(function(error){ + console.log('toDo, publish errors...') + }); + }else{ + publish(propKey, res, ...args) + } + return res; + }catch(error){ + console.log("grrrr") } - return res; } } else { return targetValue; diff --git a/nodejs/utils/object_validate.js b/nodejs/utils/object_validate.js index 85ac9d0..35c3d21 100644 --- a/nodejs/utils/object_validate.js +++ b/nodejs/utils/object_validate.js @@ -33,6 +33,8 @@ function processKeys(map, data, partial){ continue; } + // console.log(key, data[key], map[key].default, data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default)) + out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default); if(data.hasOwnProperty(key) && process_type[map[key].type]){ @@ -46,7 +48,6 @@ function processKeys(map, data, partial){ } if(errors.length !== 0){ - console.log('errors', errors) throw new ObjectValidateError(errors); return {__errors__: errors}; } @@ -79,9 +80,10 @@ function parseToString(data){ return (types[typeof(data)] || String)(data); } -function ObjectValidateError(message) { +function ObjectValidateError(message){ this.name = 'ObjectValidateError'; this.message = (message || {}); + this.keys = (message || {}) this.status = 422; } diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index d540e5f..a99cb85 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -47,17 +47,17 @@ class Table{ }catch(error){ throw error; } - } static async exists(index){ - try{ - await this.get(data); - - return true; - }catch(error){ - return false; + if(typeof index === 'object'){ + index = index[this._key]; } + + return await client.SISMEMBER( + redisPrefix(this.prototype.constructor.name), + index + ); } static async list(){ @@ -86,6 +86,7 @@ class Table{ static async create(data){ // Add a entry to this redis table. try{ + // Validate the passed data by the keyMap schema. data = objValidate.processKeys(this._keyMap, data); @@ -94,6 +95,10 @@ class Table{ let error = new Error('EntryNameUsed'); error.name = 'EntryNameUsed'; error.message = `${this.prototype.constructor.name}:${data[this._key]} already exists`; + error.keys = [{ + key: this._key, + message: `${this.prototype.constructor.name}:${data[this._key]} already exists` + }] error.status = 409; throw error; @@ -107,7 +112,7 @@ class Table{ // Add the values for this entry. for(let key of Object.keys(data)){ - if(!data[key]) continue; + if(data[key] === undefined) continue; await client.HSET( redisPrefix(`${this.prototype.constructor.name}_${data[this._key]}`), key, @@ -125,9 +130,26 @@ class Table{ async update(data, key){ // Update an existing entry. try{ + // Validate the passed data, ignoring required fields. + data = objValidate.processKeys(this.constructor._keyMap, data, true); + // Check to see if entry name changed. if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){ // Remove the index key from the tables members list. + + if(data[this.constructor._key] && await this.constructor.exists(data)){ + let error = new Error('EntryNameUsed'); + error.name = 'EntryNameUsed'; + error.message = `${this.constructor.name}:${data[this.constructor._key]} already exists`; + error.keys = [{ + key: this.constructor._key, + message: `${this.constructor.name}:${data[this.constructor._key]} already exists` + }] + error.status = 409; + + throw error; + } + await client.SREM( redisPrefix(this.constructor.name), this[this.constructor._key] @@ -139,12 +161,14 @@ class Table{ data[this.constructor._key] ); + await client.RENAME( + redisPrefix(`${this.constructor.name}_${this[this.constructor._key]}`), + redisPrefix(`${this.constructor.name}_${data[this.constructor._key]}`), + ); + } // Update what ever fields that where passed. - // Validate the passed data, ignoring required fields. - data = objValidate.processKeys(this.constructor._keyMap, data, true); - // Loop over the data fields and apply them to redis for(let key of Object.keys(data)){ this[key] = data[key]; diff --git a/nodejs/views/hosts.ejs b/nodejs/views/hosts.ejs index bab83ff..00634c8 100755 --- a/nodejs/views/hosts.ejs +++ b/nodejs/views/hosts.ejs @@ -5,7 +5,7 @@