diff --git a/nodejs/models/dnsProvider.js b/nodejs/models/dnsProvider.js new file mode 100644 index 0000000..1b7f810 --- /dev/null +++ b/nodejs/models/dnsProvider.js @@ -0,0 +1,154 @@ +'use strict'; + +const crypto = require("crypto"); + +const conf = require('../conf'); +const Table = require('../utils/redis_model'); +const ModelPs = require('../utils/model_pubsub'); + +const tldExtract = require('tld-extract').parse_host; + +const providers = { + PorkBun: require('./dns_provider/porkbun'), + DigitalOcean: require('./dns_provider/digitalocean'), +}; + +class Domain extends Table{ + static _key = 'domain'; + static _keyMap = { + 'created_by': {isRequired: true, type: 'string', min: 3, max: 500}, + 'created_on': {default: function(){return (new Date).getTime()}}, + 'updated_by': {default:"__NONE__", isRequired: false, type: 'string',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'domain': {isRequired: true, type: 'string'}, + 'dnsProvider_id': {isRequired: true, type: 'string'}, + } + + static async get(data, ...args){ + let instance = await super.get(data, ...args); + // instance.provider = (await DnsProvider.get(instance.dnsProvider_id)).provider; + + return instance; + } + + static async getByProviderId(id){ + let domains = await this.listDetail(); + let results = []; + for(let domain of domains){ + if(domain.dnsProvider_id == id) results.push(domain); + } + + return results; + } +} + +class DnsProvider extends Table{ + static _key = 'id'; + static _keyMap = { + 'created_by': {isRequired: true, type: 'string', min: 3, max: 500}, + 'created_on': {default: function(){return (new Date).getTime()}}, + 'updated_by': {default:"__NONE__", isRequired: false, type: 'string',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'id': {default: ()=>crypto.randomBytes(8).toString("hex")}, + 'name': {isRequired: true, type: 'string'}, + 'dnsProvider': {isRequired: true, type: 'string'}, + } + + static __intraModel(provider){ + if(!Object.keys(providers).includes(provider)) throw new Error('Invalid DNS provider') + let Provider = providers[provider]; + let _keyMap = {...this._keyMap, ...Provider._keyMap}; + + return ({ + [this.name] : class extends this { + static _keyMap = _keyMap; + static Provider = Provider; + } + })[this.name]; + } + + static listProviders(){ + let out = []; + for(let provider in providers){ + out.push({ + name: provider, + displayName: providers[provider].displayName || provider, + displayIconUni: providers[provider].displayIconUni || '?', + displayIconHtml: providers[provider].displayIconHtml || '', + fields: providers[provider]._keyMap, + }); + } + return out; + } + + static async create(data, ...args){ + let __intraModel = this.__intraModel(data.dnsProvider) + + let instance = await super.create.call(__intraModel, data); + if(!data.noUpdate) instance.updateDomains(); + return instance; + } + + static async get(data, ...args){ + let instance = await super.get(data); + let __intraModel = this.__intraModel(instance.dnsProvider) + + instance = await super.get.call(__intraModel, data); + instance.provider = new __intraModel.Provider(instance); + // instance.domains = Domain.getByProviderId(this.id); + + return instance; + } + + async updateDomains(){ + try{ + let domains = await this.provider.listDomains(); + console.log('got domains', domains) + for(let domain of domains){ + if(!(await Domain.exists(domain.domain))){ + await Domain.create({ + created_by: this.created_by, + domain: domain.domain, + dnsProvider_id: this.id + }); + } + } + }catch(error){ + console.error('updateDomains error', error) + } + } + + async getDomains(){ + return Domains.getByProviderId(this.id) + } + + async remove(){ + let id = this.id + let instance = await super.remove() + // get all domains for this provider and delete them + + return instance + } + + toJSON(){ + return { + ...super.toJSON(), + domains: this.domains, + } + } +} + +Domain = ModelPs(Domain); +DnsProvider = ModelPs(DnsProvider); + +module.exports = {Domain, DnsProvider} + +if(require.main === module){(async function(){try{ + const conf = require('../conf'); + + + // console.log(aw) + +}catch(error){ + console.log('IIFE Error:', error) +}})()} diff --git a/nodejs/utils/porkbun.js b/nodejs/models/dns_provider/porkbun.js similarity index 81% rename from nodejs/utils/porkbun.js rename to nodejs/models/dns_provider/porkbun.js index 897b459..23c3d8f 100644 --- a/nodejs/utils/porkbun.js +++ b/nodejs/models/dns_provider/porkbun.js @@ -2,12 +2,18 @@ const axios = require('axios'); + class PorkBun{ + static _keyMap = { + 'apiKey': {isRequired: true, type: 'string', isPrivate: true, displayName: 'API key'}, + 'secretApiKey': {isRequired: true, type: 'string', isPrivate: true, displayName: 'API Secret key'}, + } + baseUrl = 'https://api.porkbun.com/api/json/v3'; - constructor(apiKey, secretApiKey){ - this.apiKey = apiKey; - this.secretApiKey = secretApiKey; + constructor(args){ + this.apiKey = args.apiKey; + this.secretApiKey = args.secretApiKey; } async post(url, data){ @@ -40,6 +46,7 @@ class PorkBun{ async getRecords(domain, options){ let res = await this.post(`/dns/retrieve/${domain}`); if(!options) return res.data.records; + if(options.data) options.content = options.data; if(options.type) this.__typeCheck(options.type); if(options.name) options.name = this.__parseName(domain, options.name); @@ -50,8 +57,8 @@ class PorkBun{ for(let option in options){ if(record[option] === options[option] && ++matchCount === Object.keys(options).length){ records.push(record) + break; } - // console.log('option', option, options[option], record[option], matchCount) } } @@ -59,7 +66,7 @@ class PorkBun{ } async deleteRecordById(domain, id){ - let res = this.post(`/dns/delete/${domain}/${id}`); + let res = await this.post(`/dns/delete/${domain}/${id}`); return res.data; } @@ -77,7 +84,7 @@ class PorkBun{ // if(options.name) options.name = this.__parseName(domain, options.name); // console.log('PorkBun.createRecord to send:', domain, options) - let res = this.post(`/dns/create/${domain}`, options); + let res = await this.post(`/dns/create/${domain}`, options); return res.data; } @@ -93,6 +100,11 @@ class PorkBun{ } return await this.createRecord(domain, options) } + + async listDomains(){ + let res = await this.post(`/domain/listAll`, {"includeLabels": "yes"}); + return res.data.domains; + } } module.exports = PorkBun; @@ -100,8 +112,11 @@ module.exports = PorkBun; if(require.main === module){(async function(){try{ const conf = require('../conf'); + // let porkBun = new PorkBun(conf.porkBun.apiKey, conf.porkBun.secretApiKey); + // console.log(await porkBun.listDomains()) + // console.log(await porkBun.deleteRecordById('holycore.quest', '415509355')) // console.log('IIFE', await porkBun.createRecordForce('holycore.quest', {type:'A', name: 'testapi', content: '127.0.0.5'})) // console.log('IIFE', await porkBun.getRecords('holycore.quest', {type:'A', name: 'testapi'})) diff --git a/nodejs/models/host.js b/nodejs/models/host.js index 0453899..7e6bd4f 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -4,7 +4,7 @@ const Table = require('../utils/redis_model'); const ModelPs = require('../utils/model_pubsub'); const tldExtract = require('tld-extract').parse_host; -const PorkBun = require('../utils/porkbun'); +const PorkBun = require('./dns_provider/porkbun'); const LetsEncrypt = require('../utils/letsencrypt'); const conf = require('../conf'); @@ -353,8 +353,8 @@ try{ // console.log('IIFE res:\n', res) // console.log(Host.test(55)) - console.log(await Host.list()) - console.log(await Cached.listDetail()) + // console.log(await Host.list()) + // console.log(await Cached.listDetail()) // console.log('IIFE lookup:', Host.lookUp('bld3324sdf.test.holycore.quest')) diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js index 3993eff..a096ded 100644 --- a/nodejs/models/user_redis.js +++ b/nodejs/models/user_redis.js @@ -12,7 +12,7 @@ class User extends Table{ 'updated_by': {default:"__NONE__", isRequired: false, type: 'string',}, 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, 'username': {isRequired: true, type: 'string', min: 3, max: 500}, - 'password': {isRequired: true, type: 'string', min: 3, max: 500}, + 'password': {isRequired: true, type: 'string', min: 3, max: 500, isPrivate: true}, 'backing': {default:"redis", isRequired: false, type: 'string',}, } diff --git a/nodejs/public/lib/js/app-base.js b/nodejs/public/lib/js/app-base.js index 8b785aa..e8a4fbe 100644 --- a/nodejs/public/lib/js/app-base.js +++ b/nodejs/public/lib/js/app-base.js @@ -135,6 +135,25 @@ app.api = (function(app){ }); } + function options(url, callback){ + $.ajax({ + type: 'OPTIONS', + url: baseURL+url, + headers:{ + 'auth-token': app.auth.getToken() + }, + contentType: "application/json; charset=utf-8", + dataType: "json", + complete: function(res, text){ + callback( + text !== 'success' ? res.statusText : null, + JSON.parse(res.responseText), + res.status + ) + } + }); + } + function get(url, callback){ $.ajax({ type: 'GET', @@ -154,7 +173,7 @@ app.api = (function(app){ }); } - return {post: post, get: get, put: put, delete: remove} + return {post: post, get: get, put: put, delete: remove, options: options,} })(app) app.auth = (function(app){ diff --git a/nodejs/public/lib/js/val.js b/nodejs/public/lib/js/val.js index 1c340ee..b372555 100755 --- a/nodejs/public/lib/js/val.js +++ b/nodejs/public/lib/js/val.js @@ -50,6 +50,9 @@ var value = this.val(); //link to input value var message; + if(this.prop('disabled')) return true; + + //checks if field is required, and length if(!isNaN(options) && value.length < options){ message = `Must be ${options} characters`; diff --git a/nodejs/routes/api.js b/nodejs/routes/api.js index 5f51432..6e97c92 100644 --- a/nodejs/routes/api.js +++ b/nodejs/routes/api.js @@ -13,6 +13,8 @@ router.use('/user', middleware.auth, require('./user')); // API routes for working with hosts. All endpoints need to be have valid user. router.use('/host', middleware.auth, require('./host')); +router.use('/dns', middleware.auth, require('./dns')); + // API routes for working with hosts. All endpoints need to be have valid user. router.use('/cert', middleware.auth, require('./cert')); diff --git a/nodejs/routes/dns.js b/nodejs/routes/dns.js new file mode 100644 index 0000000..3387968 --- /dev/null +++ b/nodejs/routes/dns.js @@ -0,0 +1,145 @@ +'use strict'; + +const router = require('express').Router(); +const {DnsProvider, Domain} = require('../models/dnsProvider'); + +const Model = DnsProvider; + +router.get('/', async function(req, res, next){ + try{ + return res.json({ + results: await Model[req.query.detail ? "listDetail" : "list"]() + }); + }catch(error){ + return next(error); + } +}); + +router.options('/', async function(req, res, next){ + try{ + return res.json({ + results: await Model.listProviders() + }); + }catch(error){ + return next(error); + } +}); + +router.post('/', async function(req, res, next){ + try{ + req.body.created_by = req.user.username; + let item = await Model.create(req.body); + + return res.json({ + message: `"${item[Model._key]}" added.`, + ...item, + }); + } catch (error){ + next(error); + } +}); + +router.get('/domain', async function(req, res, next){ + try{ + return res.json({ + results: await Domain[req.query.detail ? "listDetail" : "list"]() + }); + }catch(error){ + return next(error); + } +}); + +router.get('/domain/byProvider/:item', async function(req, res, next){ + try{ + console.log('byProvider', req.params.item, await Domain.getByProviderId(req.params.item)) + return res.json({ + results: await Domain.getByProviderId(req.params.item) + }); + }catch(error){ + return next(error); + } +}); + +router.post('/domain/refresh/:item', async function(req, res, next){ + try{ + let item = await Model.get(req.params.item); + item.updateDomains(); + return res.json({}); + }catch(error){ + next(error); + } +}) + + + +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({ + item: req.params.item, + results: await Model.get(req.params.item) + }); + }catch(error){ + return next(error); + } +}); + +router.put('/:item', async function(req, res, next){ + try{ + req.body.updated_by = req.user.username; + let item = await Model.get(req.params.item); + item = await item.update(req.body); + + return res.json({ + message: `"${req.params.item}" updated.`, + __requestedHost: req.params.item, + ...item, + }); + + }catch(error){ + return next(error); + + } +}); + +router.delete('/:item', async function(req, res, next){ + try{ + let item = await Model.get(req.params.item); + let count = await item.remove(); + + return res.json({ + message: `${req.params.item} deleted`, + ...item, + }); + + }catch(error){ + return next(error); + } +}); + +router.put('/:item/renew', async function(req, res, next){ + try{ + let item = await Model.get(req.params.item); + item.createWildcardCert(); + + return res.json({ + message: `Requesting wildcard cert for ${req.params.item}`, + }) + }catch(error){ + next(error); + } +}); + +module.exports = router; diff --git a/nodejs/routes/render.js b/nodejs/routes/render.js index f2995e5..8366ff4 100644 --- a/nodejs/routes/render.js +++ b/nodejs/routes/render.js @@ -32,6 +32,11 @@ router.get('/hosts', async function(req, res, next) { res.render('hosts', {...values}); }); +router.get('/dns', async function(req, res, next) { + res.render('dns', {...values}); +}); + + router.get('/users', async function(req, res, next) { res.render('users', {...values}); }); diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index a99cb85..3dc7af5 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -22,6 +22,7 @@ class Table{ static async get(index){ try{ + if(typeof index === 'object'){ index = index[this._key]; } @@ -210,6 +211,17 @@ class Table{ } }; + toJSON(){ + let result = {}; + for (const [key, keyProps] of Object.entries(this.constructor._keyMap)) { + if(!keyProps.isPrivate) result[key] = this[key]; + } + + return result + + // return JSON.stringify(result); + } + } diff --git a/nodejs/views/dns.ejs b/nodejs/views/dns.ejs new file mode 100644 index 0000000..4918deb --- /dev/null +++ b/nodejs/views/dns.ejs @@ -0,0 +1,218 @@ +<%- include('top') %> + + + + + + +<%- include('bottom') %> diff --git a/nodejs/views/top.ejs b/nodejs/views/top.ejs index 404b824..82fcba8 100755 --- a/nodejs/views/top.ejs +++ b/nodejs/views/top.ejs @@ -41,6 +41,9 @@ +