diff --git a/.gitignore b/.gitignore index 9e5b648..26152bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -ndm.db \ No newline at end of file +ndm.db +*.env \ No newline at end of file diff --git a/assets/css/main.css b/assets/css/main.css index 260727a..cd76759 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -68,6 +68,86 @@ body { padding-right: 8px; } +.InsetContentBox { + border: 1px inset; + min-height: 200px; + background-color: white; + display: inline-block; + box-sizing: border-box; + vertical-align: middle; +} + +.ContentBox { + border: 2px solid #4682b4; + min-height: 200px; + background-color: white; + display: inline-block; + box-sizing: border-box; + vertical-align: middle; + padding: 8px; +} + +#DomainOptions { + text-align: center; + float: right; +} + +#DomainSelection { + overflow: scroll; + zoom: 1; +} + +#DomainSelection li:hover { + background-color: #2e69cb; + color: white; + cursor: pointer; +} + +.SelectedListItem { + background-color: #2e69cb; + color: white; +} + +ul { + margin: 0; + padding: 0; +} + +li { + list-style: none; + padding: 8px; +} + +.MutedText { + color: #afafaf; +} + +.TitleText { + font-weight: bold; + font-size: 14px; +} + +.Hidden { + display: none; +} + +a { + text-decoration: none; +} + +a:hover { + color: #800000; +} + +a { + color: #0000ee; +} + +.DefaultButton { + background-color: #f5cd2f; + cursor: pointer; +} + .VerticalInputForm { display: inline-block; } diff --git a/assets/scripts/domainmgmt.js b/assets/scripts/domainmgmt.js new file mode 100644 index 0000000..27857bb --- /dev/null +++ b/assets/scripts/domainmgmt.js @@ -0,0 +1,72 @@ +// This script is designed to support at a minimum IE 6 +// +// All menu functions are defined outside of initMenu. +// +// This is so I'm not duplicating functions between- +// checks for what browser we have + +function resetSelection() { + var elements = document.getElementsByName("domain_selection"); + + for (var i = 0; i < elements.length; i++) { + (function(li) { + li.className = ""; + + + })(elements[i]); + } +} + +function toggleSelection(li) { + var domainId = li.value; + var editDNS = document.getElementById("options_edit_dns"); + var unreg = document.getElementById("options_unregister"); + + if(li.className == "SelectedListItem") { + editDNS.className = "Hidden"; + unreg.className = "Hidden"; + li.className = ""; + return; + } + + resetSelection(); + + li.className = "SelectedListItem"; + + + + editDNS.className = ""; + editDNS.href = "/dns/edit/" + domainId; + + unreg.className = ""; + unreg.href = "/domains/unregister/" + domainId; +} + +// Once the page has fully loaded, connect each button to its code +function initDomainSelection() { + // Register selection events + var elements = document.getElementsByName("domain_selection"); + + for (var i = 0; i < elements.length; i++) { + (function(li) { + if(window.addEventListener) { + li.addEventListener("click", function() { + toggleSelection(li); + }); + } else { + li.attachEvent("onclick", function() { + toggleSelection(li); + }); + } + })(elements[i]); + } +} + +// Register load / onload event +if(window.addEventListener) { + window.addEventListener('load', initDomainSelection); +} else if(window.attachEvent) { + window.attachEvent('onload', initDomainSelection); +} else { + alert("Unsupported browser."); +} diff --git a/coredns_bridge.js b/coredns_bridge.js new file mode 100644 index 0000000..a69915d --- /dev/null +++ b/coredns_bridge.js @@ -0,0 +1,42 @@ +function FQDNToCoreDNSPath(fqdn) { + +} + +function RegisterNewDomain(domainLabel) { + +} + +function GetAllRecords() { + +} + +function CreateARecord(domain, host) { + +} + +function DeleteARecord(domain, host) { + +} + +function UpdateARecord(domain, host) { + +} + +function CreateMXRecord(domain, host) { + +} + +function DeleteMXRecord(domain, host) { + +} + +function UpdateMXRecord(domain, host) { + +} + +module.exports = { + RegisterNewDomain, + GetAllRecords, + CreateARecord, + DeleteARecord +} \ No newline at end of file diff --git a/database.js b/database.js index 75f84d2..1873a61 100644 --- a/database.js +++ b/database.js @@ -34,13 +34,23 @@ const User = seqConn.define('User', { const RegisteredDomain = seqConn.define('RegisteredDomain', { domain: { type: Sequelize.TEXT, - unique: true, + allowNull: false + }, + tld: { + type: Sequelize.TEXT, allowNull: false }, owner: { type: Sequelize.BIGINT.UNSIGNED, allowNull: false - } + }, +}, { + indexes: [ + { + unique: true, + fields: ['domain', 'tld'], + }, + ], }); ////// Exports ////// diff --git a/routes/dns.js b/routes/dns.js new file mode 100644 index 0000000..20dc9bd --- /dev/null +++ b/routes/dns.js @@ -0,0 +1,30 @@ +const express = require('express'); +const router = express.Router(); +const domainValidator = require('../validators/domain'); +const authMw = require('../session'); +const database = require('../database.js'); +const dbConnection = database.db; +const Sequelize = require('sequelize'); +const pageTitle = 'Domain Manager | Edit DNS'; + + +// Manage domains +router.get('/dns/edit/:domainId', authMw.AllowIfAuthenticated, async (req, res, next) => { + const result = await dbConnection.transaction(async(t) => { + const ownedDomain = await database.models.RegisteredDomain.findOne({ + where: { + id: req.params.domainId, + owner: req.session.userId + } + }, {transaction: t}); + + return ownedDomain; + }); + + if(!result) + return next(); + + res.render('dns', {title: pageTitle, domain: result}); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/domains.js b/routes/domains.js new file mode 100644 index 0000000..542e3ec --- /dev/null +++ b/routes/domains.js @@ -0,0 +1,86 @@ +const express = require('express'); +const router = express.Router(); +const domainValidator = require('../validators/domain'); +const authMw = require('../session'); +const database = require('../database.js'); +const dbConnection = database.db; +const Sequelize = require('sequelize'); +const pageTitle = 'Domain Manager | Register New Domain'; + +const supportedTLDs = [ + "local", + "tomato", + "secret", + "money", + "lol", + "lmao" +]; + +// Manage domains +router.get('/domains', authMw.AllowIfAuthenticated, async (req, res) => { + const result = await dbConnection.transaction(async(t) => { + const ownedDomains = await database.models.RegisteredDomain.findAll({ + where: { + owner: req.session.userId + } + }, {transaction: t}); + + return ownedDomains; + }); + + let registeredDomains = result; + + res.render('domains', {title: 'Domain Manager | Your Domains', registeredDomains: registeredDomains}); +}); + +//// Register domains //// + +// GET +// Frontend page to register a new domain +router.get('/domains/new', authMw.AllowIfAuthenticated, async(req, res) => { + res.render('newdomain', {title: 'Domain Manager | Register Domain', supportedTLDs: supportedTLDs}); +}); + +// POST +// Backend post handler to register the domain +router.post('/domains/new', authMw.AllowIfAuthenticated, async(req, res, next) => { + const reqBody = req.body; + const validationResult = domainValidator.test(reqBody); + const validationError = validationResult.error; + let errors = []; + + if(validationError !== undefined) + errors = validationError.details; + + try { + if(errors.length === 0) { + const result = await dbConnection.transaction(async(t) => { + const newDomain = await database.models.RegisteredDomain.create({ + domain: reqBody.register_domain_label, + tld: reqBody.register_domain_tld, + owner: req.session.userId + }, {transaction: t}); + + return newDomain; + }); + + if(result !== undefined) { + return res.redirect('/domains'); + } else { + errors.push({message: 'Failed to register new domain.'}) + } + } + } catch(error) { + if(error instanceof Sequelize.UniqueConstraintError) { + errors.push({message: 'Domain is not available.'}); + } else { + error.status = 500; + return next(error); + } + } + + // if we're here we failed, I specify true for csrfToken to force reset it + return res.render('newdomain', {title: pageTitle, supportedTLDs: supportedTLDs, errors: errors, csrfToken: req.csrfToken(true) }); +}); + +module.exports = router; \ No newline at end of file diff --git a/validators/domain.js b/validators/domain.js new file mode 100644 index 0000000..34c202b --- /dev/null +++ b/validators/domain.js @@ -0,0 +1,11 @@ +const Joi = require('joi'); +const domainRegistrySchema = Joi.object().keys({ + register_domain_label: Joi.string().alphanum().min(1).max(63).required(), + register_domain_tld: Joi.string().valid("local", "tomato", "secret", "money", "lol", "lmao").required() +}).unknown(true); + +module.exports = { + test: (body) => { + return domainRegistrySchema.validate(body); + } +} \ No newline at end of file diff --git a/views/dns.handlebars b/views/dns.handlebars new file mode 100644 index 0000000..942dffd --- /dev/null +++ b/views/dns.handlebars @@ -0,0 +1 @@ +

Configuring DNS records for {{domain.domain}}.{{domain.tld}}

\ No newline at end of file diff --git a/views/domains.handlebars b/views/domains.handlebars new file mode 100644 index 0000000..25282af --- /dev/null +++ b/views/domains.handlebars @@ -0,0 +1,34 @@ +{{{getScript "/scripts/domainmgmt.js" nonce}}} + +

Manage your registered domains

+ +{{#if errors}} + +{{/if}} + +
+
+ +
+ +
+
+

Options

+ +
\ No newline at end of file diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars index 165e7e0..5c22f59 100644 --- a/views/layouts/main.handlebars +++ b/views/layouts/main.handlebars @@ -16,33 +16,32 @@ {{/if}}
- -