fixed issue with redis models

This commit is contained in:
2024-08-06 13:11:55 -04:00
parent e38504457c
commit 84db245f4a
20 changed files with 558 additions and 323 deletions
+11 -3
View File
@@ -53,20 +53,28 @@ app.use('/api', require('./routes/api'));
// Catch 404 and forward to error handler. If none of the above routes are // Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called. // used, this is what will be called.
app.use(function(req, res, next) { app.use(async function(req, res, next) {
try{
var err = new Error('Not Found'); var err = new Error('Not Found');
err.message = 'Page not found' err.message = 'Page not found'
err.status = 404; err.status = 404;
next(err); next(err);
}catch(error){
console.log('app 404 catch error', error)
}
}); });
// Error handler. This is where `next()` will go on error // Error handler. This is where `next()` will go on error
app.use(function(err, req, res, next) { app.use(async function(err, req, res, next) {
try{
console.error(err.status || res.status, err.name, req.method, req.url); console.error(err.status || res.status, err.name, req.method, req.url);
console.error(err.message); console.error(err.message);
console.error(err.stack); console.error(err.stack);
console.error('========================================='); console.error('=========================================');
res.status(err.status || 500); res.status(err.status || 500);
res.json({name: err.name, message: err.message}); res.json({name: err.name, message: err.message, keys: err.keys});
}catch(error){
console.log('error in the catch all error fn....', error);
}
}); });
-8
View File
@@ -45,12 +45,4 @@ class Auth{
} }
} }
Auth.logOut = async function(data){
try{
}catch(error){
throw error;
}
}
module.exports = {Auth}; module.exports = {Auth};
+83 -9
View File
@@ -28,6 +28,7 @@ class Host extends Table{
'targetssl': {isRequired: false, default: false, type: 'boolean'}, 'targetssl': {isRequired: false, default: false, type: 'boolean'},
'is_cache': {default: false, isRequired: false, type: 'boolean',}, 'is_cache': {default: false, isRequired: false, type: 'boolean',},
'is_wildcard': {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}, '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{ try{
let out = await super.create(data, ...args) let out = await super.create(data, ...args);
await this.buildLookUpObj() await this.buildLookUpObj();
if(out.is_wildcard) await out.createWildcardCert() if(out.is_wildcard) out.createWildcardCert();
return out; return out;
@@ -100,14 +101,79 @@ class Host extends Table{
async createWildcardCert(){ async createWildcardCert(){
if(!this.host.startsWith('*.')) throw new Error('not wild card'); if(!this.host.startsWith('*.')) throw new Error('not wild card');
try{
let host = this;
let cert = await letsEncrypt.dnsWildcard(this.host, { let cert = await letsEncrypt.dnsWildcard(this.host, {
challengeCreateFn: async (authz, challenge, keyAuthorization) => { challengeCreateFn: async (authz, challenge, keyAuthorization) => {
host.update({
wildcard_status: `Adding record for ${authz.identifier.value}`
});
try{
let parts = tldExtract(authz.identifier.value); let parts = tldExtract(authz.identifier.value);
let res = await porkBun.createRecordForce(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`});
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)=>{ challengeRemoveFn: async (authz, challenge, keyAuthorization)=>{
let parts = tldExtract(authz.identifier.value); host.update({
await porkBun.deleteRecords(parts.domain, {type:'TXT', name: `_acme-challenge${parts.sub ? `.${parts.sub}` : ''}`, content: `${keyAuthorization}`}); 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}`
})
}
}, },
}); });
@@ -122,9 +188,17 @@ class Host extends Table{
await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd)); await this.constructor.redisClient.SET(`${this.host}:latest`, JSON.stringify(toAdd));
return toAdd; this.update({
wildcard_status: `Done`
});
// Get new wildcard cert from return this;
}catch(error){
console.log('le failed', error)
this.update({
wildcard_status: `LE failed`
});
}
} }
async update(...args){ async update(...args){
+1 -1
View File
@@ -37,7 +37,7 @@ class AuthToken extends Token{
return await User.get(this.created_by); return await User.get(this.created_by);
} }
static async add(data){ static async create(data){
data.created_by = data.username; data.created_by = data.username;
return super.create(data) return super.create(data)
+4 -39
View File
@@ -1,7 +1,6 @@
'use strict'; 'use strict';
const Table = require('../utils/redis_model'); const Table = require('../utils/redis_model');
// const {Token, InviteToken} = require('./token');
const bcrypt = require('bcrypt'); const bcrypt = require('bcrypt');
const saltRounds = 10; const saltRounds = 10;
@@ -19,9 +18,11 @@ class User extends Table{
static backing = 'redis' static backing = 'redis'
static async add(data) { static async create(data) {
try{ try{
console.log('hash?')
data['password'] = await bcrypt.hash(data['password'], saltRounds); data['password'] = await bcrypt.hash(data['password'], saltRounds);
console.log('hashed password', data.password);
data['backing'] = data['backing'] || 'redis'; 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){ async setPassword(data){
try{ try{
data['password'] = await bcrypt.hash(data['password'], saltRounds); 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){ static async login(data){
try{ try{
@@ -109,7 +74,7 @@ module.exports = {User};
(async function(){ (async function(){
var defaultUser = 'proxyadmin3' var defaultUser = 'proxyadmin2'
try{ try{
let user = await User.get(defaultUser); let user = await User.get(defaultUser);
}catch(error){ }catch(error){
+1 -1
View File
@@ -15,7 +15,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^4.19.2", "express": "^4.18.2",
"extend": "^3.0.2", "extend": "^3.0.2",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"ldapts": "^2.12.0", "ldapts": "^2.12.0",
+1 -1
View File
@@ -18,7 +18,7 @@
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^4.19.2", "express": "^4.18.2",
"extend": "^3.0.2", "extend": "^3.0.2",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"ldapts": "^2.12.0", "ldapts": "^2.12.0",
+28 -6
View File
@@ -277,7 +277,7 @@ app.util = (function(app){
}) })
}else{ }else{
if(type) $target.addClass('bg-' + type); if(type) $target.addClass('bg-' + type);
message = '<span class="align-middle">' + message + '</span><button class="action-close btn btn-sm btn-outline-dark float-right"><i class="fa-solid fa-xmark"></i></button>' message = '<span class="align-middle">' + message + '</span><button class="action-close btn btn-sm btn-outline-dark float-end"><i class="fa-solid fa-xmark"></i></button>'
$target.html(message).slideDown('fast'); $target.html(message).slideDown('fast');
} }
setTimeout(callback,10) setTimeout(callback,10)
@@ -290,13 +290,23 @@ app.util = (function(app){
for(var i = 0; i < arr.length; i++){ for(var i = 0; i < arr.length; i++){
if(obj[arr[i].name] === undefined) { if(obj[arr[i].name] === undefined) {
if(!arr[i].value) continue;
obj[arr[i].name] = arr[i].value; 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 { } else {
if(!(obj[arr[i].name] instanceof Array)) { if(!(obj[arr[i].name] instanceof Array)) {
obj[arr[i].name] = [obj[arr[i].name]]; obj[arr[i].name] = [obj[arr[i].name]];
} }
obj[arr[i].name].push(arr[i].value); obj[arr[i].name].push(arr[i].value);
} }
} }
return obj; return obj;
}; };
@@ -342,12 +352,12 @@ function formAJAX(btn, del){
event.preventDefault(); // avoid to execute the actual submit of the form. event.preventDefault(); // avoid to execute the actual submit of the form.
var $form = $(btn).closest('[action]'); // gets the 'form' parent var $form = $(btn).closest('[action]'); // gets the 'form' parent
var formData = $form.find('[name]').serializeObject(); // builds query formDataing 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()){ if($form.validate && !$form.validate()){
// app.util.actionMessage('Please fix the form errors.', $form, 'danger') app.util.actionMessage('Please fix the form errors.', $form, 'danger')
// return false; return false;
// } }
app.util.actionMessage( app.util.actionMessage(
'<div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div>', '<div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div>',
@@ -357,9 +367,21 @@ function formAJAX(btn, del){
app.api[method]($form.attr('action'), formData, function(error, data){ app.api[method]($form.attr('action'), formData, function(error, data){
app.util.actionMessage(data.message, $form, error ? 'danger' : 'success'); //re-populate table app.util.actionMessage(data.message, $form, error ? 'danger' : 'success'); //re-populate table
$form.validateClear();
if(!error){ if(!error){
$form.trigger("reset"); $form.trigger("reset");
eval($form.attr('evalAJAX')); //gets JS to run after completion 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);
}
}
} }
}); });
} }
+36 -13
View File
@@ -109,6 +109,7 @@ MIT license
//set and return new array //set and return new array
return Array.prototype.splice.apply(this, toProto); return Array.prototype.splice.apply(this, toProto);
}; };
result.push = function(){ result.push = function(){
//add one or more objects to the array //add one or more objects to the array
@@ -123,19 +124,25 @@ MIT license
//return new array length //return new array length
return this.length; return this.length;
}; };
result.pop = function(){ result.pop = function(){
//remove and return array element //remove and return array element
return this.splice( -1, 1 )[0]; 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++ ){ result.reverse = function() {
this.push( temp[i] ); 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; return this;
}; };
@@ -149,6 +156,10 @@ MIT license
return this.splice( 0, 1 )[0]; return this.splice( 0, 1 )[0];
}; };
result.unshift = function(data){
return this.splice(0,0, data)
}
result.loop = function(){ result.loop = function(){
var temp = this[0]; var temp = this[0];
this.splice( 0,1 ); this.splice( 0,1 );
@@ -156,13 +167,17 @@ MIT license
return temp; return temp;
}; };
result.loopUp = function(){ result.loopUp = function(){
var temp = this[this.length-1]; var temp = this[this.length-1];
this.splice( -1, 1 ); this.splice( -1, 1 );
this.splice( 0, 0, temp ); this.splice( 0, 0, temp );
return temp; return temp;
}; };
result.indexOf = function( key, value ){ result.indexOf = function( key, value ){
if(typeof key === 'number') return key;
if( typeof value !== 'string' ){ if( typeof value !== 'string' ){
value = arguments[0]; value = arguments[0];
key = this.__index; key = this.__index;
@@ -175,7 +190,8 @@ MIT license
} }
return -1; return -1;
}; };
result.update = function( key, value, update ){
result.update = function(key, value, data){
//set variables using sting for index //set variables using sting for index
// If update is called with no index/key, assume its the 0 // If update is called with no index/key, assume its the 0
@@ -187,24 +203,31 @@ MIT license
} }
if(typeof value !== 'string'){ if(typeof value !== 'string'){
update = arguments[1]; data = arguments[1];
if(typeof key !== 'number'){
value = arguments[0]; value = arguments[0];
key = this.__index; key = this.__index;
} }
}
var index = this.indexOf( key, value ); var index = this.indexOf( key, value );
if(index === -1) { if(index === -1) {
return []; return [];
} }
var object = $.extend( true, {}, this[index], update ); this[index] = $.extend( true, this[index], data );
var $render = $(Mustache.render(this.__jqTemplate, object)); var $render = $(Mustache.render(this.__jqTemplate, this[index]));
$render.attr('jq-repeat-index', index); $render.attr('jq-repeat-index', index);
this[index].__jq_$el.replaceWith($render);
this.__update(this[index].__jq_$el, $render, this[index], this);
this[index].__jq_$el = $render; 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){ result.__put = function($el, item, list){
$el.show(); $el.show();
}; };
@@ -213,8 +236,8 @@ MIT license
$el.remove(); $el.remove();
}; };
result.__update = function($el, item, list){ result.__update = function($el, $render, item, list){
console.log('here', $el) $el.replaceWith($render);
$el.show(); $el.show();
}; };
+50 -67
View File
@@ -9,88 +9,71 @@
} }
} }
}, },
form: {
alertCount: false, //pop-up with error count
alertCountMessage: " errors!"
},
processValidation: function ( error_message, $input ) {
if ( typeof error_message == 'undefined' || error_message == true ) {
return;
}
$( '<b>' ).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 ) { if(this.is('[validate]')) return this.validateField(event);
var attr = $input.attr( 'validate' ).split( ':' ), //array of params
requirement = attr[1],
value = $input.val(), //link to input value
rule = attr[0];
$input.siblings( 'label' ).children( 'b' ).remove(); //removes old error if(!this.attr('isValid')){
$input.parent().removeClass( "has-error" ); //removes has-error class 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 //checks if field is required, and length
if (isNaN(requirement) === false && requirement && value.length < requirement) { if(!isNaN(options) && value.length < options){
return thisSettings.processValidation( 'Must be ' + requirement + ' characters', $input ); message = `Must be ${options} characters`;
} }
//checks if empty to stop processing //checks if empty to stop processing
if ( isNaN( requirement ) === false && value.length === 0 ) { if(!isNaN(options) && value.length === 0) {
return; }else if(rule in settings.rule){
let message = settings.rule[rule].apply(this, [value, options]);
} }
if ( rule in thisSettings.rule ) { this.validateMessage(message)
return thisSettings.processValidation( thisSettings.rule[rule].apply( this, [value, requirement] ), $input ); return !message;
}
} }
$.fn.validate = function( settingsObj, event ) { $.fn.validateMessage = function(message){
event = event || window.event; if(message && message !== true){
this.closest('.form-group').find('b.invalid-feedback').html(message);
failedCount = 0; this.addClass('is-invalid');
var thisForm = false,
thisSettings = $.extend( true, settings, settingsObj );
if ( this.is( '[validate]' ) ) {
processRule( thisSettings, this );
}else{ }else{
thisForm = true; this.removeClass('is-invalid');
this.find( '[validate]' ).each( function () { this.addClass('is-valid');
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;
} }
return this;
}; };
jQuery.extend({ jQuery.extend({
+1
View File
@@ -3,6 +3,7 @@
const router = require('express').Router(); const router = require('express').Router();
const { Auth } = require('../controller/auth'); const { Auth } = require('../controller/auth');
router.post('/login', async function(req, res, next){ router.post('/login', async function(req, res, next){
try{ try{
let auth = await Auth.login(req.body); let auth = await Auth.login(req.body);
+1 -1
View File
@@ -25,7 +25,7 @@ router.post('/', async function(req, res, next){
...item, ...item,
}); });
} catch (error){ } catch (error){
return next(error); next(error);
} }
}); });
+3
View File
@@ -40,4 +40,7 @@ router.get('/login', async function(req, res, next) {
res.render('login', {...values, redirect: req.query.redirect}); 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; module.exports = router;
+35 -5
View File
@@ -7,6 +7,11 @@ const sleep = require('./sleep');
// https://dns.google/resolve?name=${name}&type=TXT // https://dns.google/resolve?name=${name}&type=TXT
AcmeClient.setLogger((message) => {
console.log('ACME:', message);
});
class LetsEncrypt{ class LetsEncrypt{
static AcmeClient = AcmeClient; static AcmeClient = AcmeClient;
@@ -43,32 +48,57 @@ class LetsEncrypt{
try{ try{
domain = domain.replace(/^\*\./, ''); domain = domain.replace(/^\*\./, '');
const [key, csr] = await AcmeClient.crypto.createCsr({ const [key, csr] = await AcmeClient.crypto.createCsr({
altNames: [domain, `*.${domain}`], altNames: [domain, `*.${domain}`],
}); });
let dnsToAdd = 0;
let dnsFound = 0;
const cert = await this.client.auto({ const cert = await this.client.auto({
csr, csr,
email: 'wmantly@gmail.com', email: 'wmantly@gmail.com',
termsOfServiceAgreed: true, termsOfServiceAgreed: true,
challengePriority: ['dns-01'], challengePriority: ['dns-01'],
skipChallengeVerification: true,
challengeCreateFn: async (authz, challenge, keyAuthorization) => { challengeCreateFn: async (authz, challenge, keyAuthorization) => {
// console.log(`start TXT record key=_acme-challenge.${authz.identifier.value} value=${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++
let resCheck = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); 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; if(resCheck.data.Answer && resCheck.data.Answer.some(record => record.data === keyAuthorization)){
await sleep(1000);
dnsFound++
if(dnsFound === dnsToAdd){
options.onDnsCheckFound(authz, dnsFound)
}
return;
}
await options.challengeCreateFn(authz, challenge, keyAuthorization); await options.challengeCreateFn(authz, challenge, keyAuthorization);
let checkCount = 0; let checkCount = 0;
while(true){ while(true){
await sleep(1500); options.onDnsCheck(authz, checkCount);
let res = await axios.get(`https://dns.google/resolve?name=_acme-challenge.${authz.identifier.value}&type=TXT`); 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(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}`) // console.log(`found record for key=_acme-challenge.${authz.identifier.value} value=${keyAuthorization}`)
await sleep(10000);
break; break;
} }
if(checkCount++ > 60) throw new Error('challengeCreateFn validation timed out'); 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, challengeRemoveFn: options.challengeRemoveFn,
+11 -1
View File
@@ -12,9 +12,13 @@ function ModelPs(model){
} }
function publish(prop, res, req){ function publish(prop, res, req){
try{
if(!['add', 'create', 'update', 'remove'].includes(prop)) return; 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, { return new Proxy(model, {
@@ -28,16 +32,22 @@ function ModelPs(model){
const targetValue = Reflect.get(target, propKey, receiver); const targetValue = Reflect.get(target, propKey, receiver);
if (typeof targetValue === 'function') { if (typeof targetValue === 'function') {
return function(...args){ return function(...args){
try{
// let res = targetValue.apply(this, args); // (A) // let res = targetValue.apply(this, args); // (A)
let res = Reflect.apply(targetValue, this, args); var res = Reflect.apply(targetValue, this, args);
if(targetValue.constructor.name === 'AsyncFunction'){ if(targetValue.constructor.name === 'AsyncFunction'){
res.then(function(res){ res.then(function(res){
publish(propKey, res, ...args); publish(propKey, res, ...args);
}).catch(function(error){
console.log('toDo, publish errors...')
}); });
}else{ }else{
publish(propKey, res, ...args) publish(propKey, res, ...args)
} }
return res; return res;
}catch(error){
console.log("grrrr")
}
} }
} else { } else {
return targetValue; return targetValue;
+3 -1
View File
@@ -33,6 +33,8 @@ function processKeys(map, data, partial){
continue; 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); out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default);
if(data.hasOwnProperty(key) && process_type[map[key].type]){ if(data.hasOwnProperty(key) && process_type[map[key].type]){
@@ -46,7 +48,6 @@ function processKeys(map, data, partial){
} }
if(errors.length !== 0){ if(errors.length !== 0){
console.log('errors', errors)
throw new ObjectValidateError(errors); throw new ObjectValidateError(errors);
return {__errors__: errors}; return {__errors__: errors};
} }
@@ -82,6 +83,7 @@ function parseToString(data){
function ObjectValidateError(message){ function ObjectValidateError(message){
this.name = 'ObjectValidateError'; this.name = 'ObjectValidateError';
this.message = (message || {}); this.message = (message || {});
this.keys = (message || {})
this.status = 422; this.status = 422;
} }
+35 -11
View File
@@ -47,17 +47,17 @@ class Table{
}catch(error){ }catch(error){
throw error; throw error;
} }
} }
static async exists(index){ static async exists(index){
try{ if(typeof index === 'object'){
await this.get(data); index = index[this._key];
return true;
}catch(error){
return false;
} }
return await client.SISMEMBER(
redisPrefix(this.prototype.constructor.name),
index
);
} }
static async list(){ static async list(){
@@ -86,6 +86,7 @@ class Table{
static async create(data){ static async create(data){
// Add a entry to this redis table. // Add a entry to this redis table.
try{ try{
// Validate the passed data by the keyMap schema. // Validate the passed data by the keyMap schema.
data = objValidate.processKeys(this._keyMap, data); data = objValidate.processKeys(this._keyMap, data);
@@ -94,6 +95,10 @@ class Table{
let error = new Error('EntryNameUsed'); let error = new Error('EntryNameUsed');
error.name = 'EntryNameUsed'; error.name = 'EntryNameUsed';
error.message = `${this.prototype.constructor.name}:${data[this._key]} already exists`; 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; error.status = 409;
throw error; throw error;
@@ -107,7 +112,7 @@ class Table{
// Add the values for this entry. // Add the values for this entry.
for(let key of Object.keys(data)){ for(let key of Object.keys(data)){
if(!data[key]) continue; if(data[key] === undefined) continue;
await client.HSET( await client.HSET(
redisPrefix(`${this.prototype.constructor.name}_${data[this._key]}`), redisPrefix(`${this.prototype.constructor.name}_${data[this._key]}`),
key, key,
@@ -125,9 +130,26 @@ class Table{
async update(data, key){ async update(data, key){
// Update an existing entry. // Update an existing entry.
try{ try{
// Validate the passed data, ignoring required fields.
data = objValidate.processKeys(this.constructor._keyMap, data, true);
// Check to see if entry name changed. // Check to see if entry name changed.
if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){ if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){
// Remove the index key from the tables members list. // 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( await client.SREM(
redisPrefix(this.constructor.name), redisPrefix(this.constructor.name),
this[this.constructor._key] this[this.constructor._key]
@@ -139,12 +161,14 @@ class Table{
data[this.constructor._key] 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. // 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 // Loop over the data fields and apply them to redis
for(let key of Object.keys(data)){ for(let key of Object.keys(data)){
this[key] = data[key]; this[key] = data[key];
+95 -71
View File
@@ -5,7 +5,7 @@
</script> </script>
<style type="text/css"> <style type="text/css">
label.control-label{ label.form-label{
font-weight: bold; font-weight: bold;
margin-bottom: 1px; margin-bottom: 1px;
} }
@@ -14,7 +14,10 @@
font-weight: bold; font-weight: bold;
} }
div.editWindow{ div.hostEditPanel{
margin-bottom: 1em;
}
div.form-group{
margin-bottom: 1em; margin-bottom: 1em;
} }
/* my Div class for my search bar */ /* my Div class for my search bar */
@@ -36,11 +39,13 @@
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
var $editHostForm;
function parseHostRow(host) { function parseHostRow(host) {
host['updated_on_text'] = moment(host['updated_on'], "x").fromNow(); host['updated_on_text'] = moment(host['updated_on'], "x").fromNow();
host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://'; host['targetssl_text'] = host['targetssl'] ? 'https://' : 'http://';
host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://'; host['forcessl_text'] = host['forcessl'] ? 'https://' : 'http://';
host['wildcard_text'] = host['is_wildcard'] ? host['wildcard_status'] : 'Auto';
return host; return host;
} }
@@ -66,40 +71,34 @@
$('#hostsTable').find('tr').each(function(idx, el){ $('#hostsTable').find('tr').each(function(idx, el){
$(el).removeClass('table-warning'); $(el).removeClass('table-warning');
}); });
$('.editWindow').slideUp('fast'); $.scope.editHost.remove();
} }
function editHost(btn, host){ function editHost(btn, host){
cancleHostEdit(); cancleHostEdit();
$(btn).closest('tr').addClass('table-warning'); $(btn).closest('tr').addClass('table-warning');
$('.editWindow .card-title').html("Edit "+ host); host = $.scope.hosts.getByKey(host);
$.scope.editHost.update({...host, form: $editHostForm.html()});
$('div.editWindow .card-body span').html($('#addHost').html()); $.each(host, function( key, value ) { if(typeof value == "boolean"){
$('div.editWindow .card-body span button').remove(); $(".hostEditPanel #"+ key +"-"+ value).prop('checked', true)
$(".editWindow input[name='edit_host']").val(host);
$('.editWindow').slideDown('fast');
app.host.get(host, function(error, data){
$.each( data.results, function( key, value ) {
if(typeof value == "boolean"){
$(".editWindow #"+ key +"-"+ value).prop('checked', true)
}else{ }else{
$(".editWindow input[name='" + key + "']").val(value); $(".hostEditPanel input[name='" + key + "']").val(value);
} }
}); });
});
}; };
function deleteHost(host){ function deleteHost(host){
app.host.remove({host: host}, function(err, data){ app.host.remove({host: host}, function(error, data){
// app.util.actionMessage(`Host ${host} deleted!`, $.scope.hosts.$this, 'danger'); if(error) app.util.actionMessage(error.message, $.scope.hosts.$this, 'danger');
$.scope.hosts.remove(host);
}); });
} }
$(document).ready(function(){ $(document).ready(function(){
$editHostForm = $('#addHost').clone();
$editHostForm.find('hr.buttonBreak').nextAll().remove();
// $editHostForm.find('.autoSll').addClass('bg-secondary');
$editHostForm.find('[name=is_wildcard').attr('disabled', true);
populateHosts(); //populate the table populateHosts(); //populate the table
$.scope.hosts.__setTake(function($el, item, list){ $.scope.hosts.__setTake(function($el, item, list){
@@ -109,6 +108,19 @@
}); });
}); });
$.scope.hosts.__setUpdate(function($el, $render, item, list){
$render.show()
$el.replaceWith($render);
});
$.scope.editHost.__setPut(function($el, item, list){
$el.slideDown();
});
$.scope.editHost.__setTake(function($el, item, list){
$el.slideUp();
});
app.subscribe(/^model:Host/, function(data, topic){ app.subscribe(/^model:Host/, function(data, topic){
console.log(topic, data); console.log(topic, data);
}); });
@@ -119,7 +131,7 @@
if($.scope.hosts.indexOf(host) >= 0){ if($.scope.hosts.indexOf(host) >= 0){
$.scope.hosts.update(host, parseHostRow(data)); $.scope.hosts.update(host, parseHostRow(data));
}else{ }else{
$.scope.hosts.splice(0,0, parseHostRow(data)) $.scope.hosts.unshift(parseHostRow(data));
} }
}); });
@@ -129,11 +141,10 @@
if($.scope.hosts.indexOf(host) >= 0){ if($.scope.hosts.indexOf(host) >= 0){
$.scope.hosts.update(host, parseHostRow(data)); $.scope.hosts.update(host, parseHostRow(data));
}else{ }else{
$.scope.hosts.splice(0,0, parseHostRow(data)) $.scope.hosts.unshift(parseHostRow(data));
} }
}); });
app.subscribe(/^model:Host:remove/, function(data, topic){ app.subscribe(/^model:Host:remove/, function(data, topic){
let [a,b, action, host] = topic.split(':'); let [a,b, action, host] = topic.split(':');
@@ -156,38 +167,6 @@
} }
} }
}); });
$('form.addHost').on('submit', function(){
event.preventDefault();
$form = $(this);
var action = $($form.find('button[type="submit"]')[0]).data('type');
app.util.actionMessage('', $form);
if($form.attr('isValid') === 'true'){
var formdata = $form.serializeObject();
if(formdata.targetPort) formdata.targetPort = Number(formdata.targetPort);
if(formdata.targetssl) formdata.targetssl = formdata.targetssl == 'true' ? true : false;
if(formdata.forcessl) formdata.forcessl = formdata.forcessl == 'true' ? true : false;
app.host[action](formdata, function(error, data){
if(error){
app.util.actionMessage(error + data.message, $form, 'danger');
return;
}
// if($.scope.hosts.indexOf(data.__requestedHost) >= 0){
// $.scope.hosts.update(data.__requestedHost, parseHostRow(data));
// }else{
// // $.scope.hosts.splice(0,0, parseHostRow(data))
// }
if(action == 'edit') $('.editWindow').slideUp('fast');
$form.trigger('reset');
})
}
});
}); });
</script> </script>
<div class="row" style="display:none"> <div class="row" style="display:none">
@@ -195,7 +174,7 @@
<!-- <!--
left column left column
--> -->
<div class="card shadow-lg editWindow" style="display:none"> <div jq-repeat="editHost" class="card shadow-lg border-warning hostEditPanel" style="display:none">
<!-- <!--
Edit host card Edit host card
--> -->
@@ -204,6 +183,7 @@
<i class="fa-solid fa-pencil"></i> <i class="fa-solid fa-pencil"></i>
</span> </span>
<span class="card-title"> <span class="card-title">
Edit {{ host }}
</span> </span>
<span class="float-end"> <span class="float-end">
<i class="fa-solid fa-circle-minus"></i> <i class="fa-solid fa-circle-minus"></i>
@@ -212,8 +192,8 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<form class="addHost" onsubmit="$(this).validate()"> <form class="addHost" method="PUT" action="/host/{{ host }}" onsubmit="formAJAX(this)" evalAJAX="cancleHostEdit()">
<span></span> {{{ form }}}
<input type="hidden" name="edit_host" /> <input type="hidden" name="edit_host" />
<button type="submit" data-type="edit" class="btn btn-warning"> <button type="submit" data-type="edit" class="btn btn-warning">
<i class="fa-solid fa-pencil"></i> <i class="fa-solid fa-pencil"></i>
@@ -227,7 +207,7 @@
</div> </div>
</div> </div>
<div class="card shadow-lg"> <div class="card shadow-lg hostAddPanel">
<!-- <!--
Add new host card Add new host card
--> -->
@@ -246,10 +226,13 @@
<div class="card-header actionMessage" style="display:none"></div> <div class="card-header actionMessage" style="display:none"></div>
<div class="card-body"> <div class="card-body">
<form class="addHost" id="addHost" onsubmit="$(this).validate()"> <form class="addHost" id="addHost" method="POST" action="host" onsubmit="formAJAX(this)">
<div class="form-group"> <div class="form-group">
<label class="control-label">Incoming SSL</label> <label class="form-label">
Incoming SSL
</label>
<br />
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="forcessl" id="forcessl-true" value="true" checked> <input type="radio" name="forcessl" id="forcessl-true" value="true" checked>
@@ -264,23 +247,54 @@
</div> </div>
</div> </div>
<div class="form-group autoSll">
<label class="form-label">
Auto SSL
</label>
<div class="radio">
<label>
<input type="radio" name="is_wildcard" id="is_wildcard-false" value="false" checked>
On demand certs
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="is_wildcard" id="is_wildcard-true" value="true">
Request Wildcard cert
</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Incoming Host Name</label> <label for='host' class="form-label">
Incoming Host Name
</label>
<div>
<input type="text" name="host" class="form-control" placeholder="ex: proxy.cloud-ops.net" validate=":3" > <input type="text" name="host" class="form-control" placeholder="ex: proxy.cloud-ops.net" validate=":3" >
<b class="invalid-feedback"></b>
</div>
</div> </div>
<div class="form-group"> <div class="mb-3 form-group">
<label class="control-label">Target IP or Host Name</label> <label for="ip" class="form-label">
Target IP or Host Name
</label>
<input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10" validate=":3" /> <input type="text" name="ip" class="form-control" placeholder="ex: 10.10.10.10" validate=":3" />
<b class="invalid-feedback"></b>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Target TCP Port</label> <label for="targetPort" class="form-label">
Target TCP Port
</label>
<input type="number" name="targetPort" class="form-control" value="80" min="0" max="65535" /> <input type="number" name="targetPort" class="form-control" value="80" min="0" max="65535" />
<b class="invalid-feedback"></b>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Target SSL</label> <label class="form-label">
Target SSL
</label>
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="targetssl" id="targetssl-true" value="true"> <input type="radio" name="targetssl" id="targetssl-true" value="true">
@@ -293,9 +307,10 @@
Proxy to HTTP <b>Recommended</b> Proxy to HTTP <b>Recommended</b>
</label> </label>
</div> </div>
<b class="invalid-feedback"></b>
</div> </div>
<hr /> <hr class="buttonBreak" />
<button type="submit" data-type="add" class="btn btn-success"> <button type="submit" class="btn btn-success">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus"></i>
Add Add
</button> </button>
@@ -308,7 +323,7 @@
<!-- <!--
Right column Right column
--> -->
<div class="card shadow-lg"> <div class="card shadow-lg hostListPanel">
<!-- <!--
List current hosts List current hosts
--> -->
@@ -326,12 +341,17 @@
<div class="card-header actionMessage" style="display:none"></div> <div class="card-header actionMessage" style="display:none"></div>
<div class="card-body search-wrapper"> <div class="card-body search-wrapper">
<label class="control-label" for="search" style="margin-left: -5px;">Search Hosts: </label> <label class="form-label" for="search" style="margin-left: -5px;">
Search Hosts:
</label>
<input type="search" id="search" host-search> <input type="search" id="search" host-search>
</div> </div>
<table class="card-body table table-striped" style="margin-bottom:0"> <table class="card-body table table-striped" style="margin-bottom:0">
<thead> <thead>
<th>
SSL
</th>
<th> <th>
Host Name Host Name
</th> </th>
@@ -347,6 +367,9 @@
</thead> </thead>
<tbody id="hostsTable"> <tbody id="hostsTable">
<tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none"> <tr action="api" jq-repeat="hosts" jq-repeat-index='host' style="display:none">
<td>
{{{ wildcard_text }}}
</td>
<td> <td>
<a target="_blank" href="{{ forcessl_text }}{{ host }}"> <a target="_blank" href="{{ forcessl_text }}{{ host }}">
{{{ forcessl_text }}}{{ host }} {{{ forcessl_text }}}{{ host }}
@@ -365,7 +388,8 @@
Cert Cert
</button> --> </button> -->
<button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning"> <button type="button" onclick="editHost(this, '{{ host }}');" class="btn btn-sm btn-warning">
<i class="fa-solid fa-pencil"></i> Edit <i class="fa-solid fa-pencil"></i>
Edit
</button> </button>
<button type="button" onclick="deleteHost('{{ host }}')" class="btn btn-sm btn-danger"> <button type="button" onclick="deleteHost('{{ host }}')" class="btn btn-sm btn-danger">
<i class="fa-solid fa-trash-can"></i> <i class="fa-solid fa-trash-can"></i>
+74
View File
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<!-- need to load jq-query cdn or file. -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<!-- need to load mustache cdn or file. -->
<script src="
https://cdn.jsdelivr.net/npm/mustache@4.2.0/mustache.min.js
"></script>
<!-- need to load jq-repeat cdn or file -->
<script type="text/javascript" src='/static/lib/js/jq-repeat_new.js'></script>
<script>
//on document ready. the logic would execute.
$(document).ready(function () {
$.scope.toDo.__setPut(function($el, item, list){
$el.slideDown('slow');
})
// $.scope.toDo.__setUpdate(function($el, $render, item, list){
// $el.fadeOut(2000, function() {
// $(this).html($render.html()).fadeIn(2000);
// });
// });
// $.scope.toDo.__setUpdate(function($el, $render, item, list){
// $el.animate({'opacity': 0}, 400, function(){
// $(this).html($render.html()).animate({'opacity': 1}, 400);
// });
// });
$.scope.toDo.__setUpdate(function($el, $render, item, list){
$el.slideUp(function(){
$(this).replaceWith($render)
$render.slideDown()
})
});
$.scope.toDo.push({ item: "Get milk", done: "Yes" }); // 0
$.scope.toDo.push({ item: "Do laundry", done: "No" }); // 1
//should take array id or array key
// - **howMany** _Type: Number_
// Number of repeat objects that will be removed. If there are non to be removed, it is not required to use this argument.
// - **update** _Type: Array_
// This is the array of repeat objects to add. If there are none to this is not required.
//remove works
// $.scope.toDo.splice("Get milk" , "1")
//update
$.scope.toDo.splice(-1,0, { item: "Get Bread", done: "Yes" })
});
</script>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul>
<li jq-repeat="toDo" jq-repeat-index="item" style="display:none;"><span class="item">{{ item }}</span>: {{ done }}</li>
</ul>
</body>
</html>
+2 -2
View File
@@ -67,7 +67,7 @@
<div class="card-header actionMessage" style="display:none"></div> <div class="card-header actionMessage" style="display:none"></div>
<div class="card-body"> <div class="card-body">
<form action="user/" evalAJAX=" <form action="user/" onsubmit="formAJAX(this)" evalAJAX="
$.scope.users.splice(0, 0, data); $.scope.users.splice(0, 0, data);
"> ">
<input type="hidden" class="form-control" name="delete" value="false" /> <input type="hidden" class="form-control" name="delete" value="false" />
@@ -84,7 +84,7 @@
<input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/> <input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/>
</div> </div>
<hr /> <hr />
<button type="button" onclick="formAJAX(this)" class="btn btn-info"> <button type="button" class="btn btn-info">
Add Add
</button> </button>
</form> </form>