Register, Login, and Logout

This commit is contained in:
2026-05-13 19:27:59 -04:00
parent f591bdffb5
commit 86f94b7bf3
23 changed files with 737 additions and 25 deletions
+1
View File
@@ -1 +1,2 @@
node_modules
ndm.db
+83
View File
@@ -0,0 +1,83 @@
body {
width: 1200px;
margin: auto;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12px;
}
#Logo {
font-size: 20px;
}
#Header {
display: block;
margin-bottom: 16px;
font-size: 14px;
}
#Header-Left {
float: left;
}
#Header-Right {
float: right;
}
#PageContent {
display: block;
width: 900px;
min-height: 600px;
padding: 12px;
box-sizing: border-box;
background-color: #e5f1fd;
border-left: gray 2px solid;
border-right: gray 2px solid;
margin: auto;
}
.CenteredFocusHeader {
width: 400px;
margin: auto;
background-color: #4682b4;
color: white;
padding: 4px;
box-sizing: border-box;
font-size: 16px;
}
.CenteredFocusHeader h1,
.CenteredFocusHeader h2,
.CenteredFocusHeader h3,
.CenteredFocusHeader h4,
.CenteredFocusHeader h5,
.CenteredFocusHeader h6 {
margin: 0;
}
.CenteredFocusContent {
width: 400px;
background-color: white;
box-sizing: border-box;
border: 2px solid #4682b4;
margin: auto;
padding-top: 12px;
padding-bottom: 12px;
padding-left: 8px;
padding-right: 8px;
}
.VerticalInputForm {
display: inline-block;
}
.TextInput {
height: 14px;
font-size: 12px;
}
.VerticalInputForm input {
display: block;
margin-bottom: 8px;
}
+31
View File
@@ -0,0 +1,31 @@
// 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 doLogoutAction() {
var logoutForm = document.getElementById("Form_Auth_Logout");
logoutForm.submit();
}
// Once the page has fully loaded, connect each button to its code
function initMenu() {
var logoutButton = document.getElementById("Menu_Auth_Logout");
if(window.addEventListener) {
logoutButton.addEventListener("click", doLogoutAction);
} else {
logoutButton.attachEvent('onclick', doLogoutAction);
}
}
// Register load / onload event
if(window.addEventListener) {
window.addEventListener('load', initMenu);
} else if(window.attachEvent) {
window.attachEvent('onload', initMenu);
} else {
alert("Unsupported browser.");
}
+20
View File
@@ -0,0 +1,20 @@
// really simple IE6 compatible post function
function PostToEndpoint(url, params) {
var form = document.createElement("form");
form.method = "POST";
form.action = url;
for (var key in params) {
if (params.hasOwnProperty(key)) {
var input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = params[key];
form.appendChild(input);
}
}
document.body.appendChild(form);
form.submit();
}
+15
View File
@@ -0,0 +1,15 @@
const { csrfSync } = require("csrf-sync");
const {
invalidCsrfTokenError,
generateToken,
validateRequest,
csrfSynchronisedProtection
} = csrfSync({
getTokenFromRequest: (req) => req.body._csrf,
});
module.exports = {
generateToken,
csrfSynchronisedProtection,
invalidCsrfTokenError
};
+1 -13
View File
@@ -2,7 +2,7 @@ const Sequelize = require('sequelize');
const seqConn = new Sequelize({
dialect: 'sqlite',
storage: process.env.DB_STORAGE,
storage: `ndm.db`,
pool: {
max: 5,
min: 0,
@@ -12,17 +12,6 @@ const seqConn = new Sequelize({
logging: console.log,
});
////// Sessions, Users //////
// Sessions
const Session = seqConn.define('Session', {
sid: {
type: Sequelize.TEXT,
primaryKey: true
},
data: Sequelize.TEXT,
expires: Sequelize.DATE,
});
// User
const User = seqConn.define('User', {
username: {
@@ -58,7 +47,6 @@ const RegisteredDomain = seqConn.define('RegisteredDomain', {
module.exports = {
db: seqConn,
models: {
Session,
User,
RegisteredDomain
}
+5 -1
View File
@@ -1,5 +1,9 @@
# Server
SRV_PORT=5001
# Cookies
SESSKEY="87216653c5e7b5789b87a8b73e9d81b671d3b50d4f3699f8b1e756669ad6533fb1a740cdf5ae32fca7ed63b0dfb88e35c153097060ee43ac9dd4ca685e7bee39ee56284ba21f2fe408777c20d21c1c5c3a8a40f956272d336c6dbebe68c87c561a2225e9131e7f1976cf439903f616175896c21962631c2c1f72d2efb5e12db3cbc91941bff99a1e9d050b6badee79063439b1f5883b49e9285ed3e16d434e6deb2babdc838caa8c51d45db8fd7116c3d3ad5f20f955b115e7d5000d8f0454b151ed42519d3d8fcb38e7510976064d188c184a174d3537c47c7e968de55b563382317873b6dc4013dd33a0700bb9ba143fd19c4a42e76cda7bd2f738280c9643701a291b77fc0d4a05309d0e44272209020539fe3592660476b602e5edda5f496443b8ab82ff1035737f745df3d3be76ba95d83d772ac45989b2c61d8e7d0b"
CKYKEY="88ae945c555650606058d58284c1772b3d8b56cee370ddbfcb366c1c4f6be3681e2e218404a4ff0dba21597817e1301fdbfedd711ab3b99c4bc900a8583892f957ea9fc657811ec20fcde7c11a12201e5a8b8c62a4dd56e5a4c06e46b5853c172d010c9fe754f95e61be7fde60701f2de44c641078e3aa51ac74cc68573c7c4b51c1eb5d219d94fe6f6bc0139ea3a731d9097d381a4b00931f71d4615912db0355eb73a44d0ed873f85cc112f945a0ac5d776d9b161cacd9d823c7b770bdc3d5f77d9161e0b45dcd05cf7498eb8d4898a0e6436264f7c11643c19ef0624ac6b08ffe9bf093c19cdfc93d35646aa0b76f603be82e6939"
# Database
DB_STORAGE="data_devel.db"
DB_STORAGE="datadevel.db"
+3
View File
@@ -47,6 +47,9 @@ const hbsHelpers = {
},
getMetaInfString: () => {
return `${metaInf.name} | ${metaInf.stage}.${metaInf.version}.${metaInf.branch}-${process.env.NODE_ENV}`;
},
getScript: (src, nonce) => {
return new String(`<script src="${src}" type="text/javascript" nonce="${nonce}"></script>`);
}
}
+23 -9
View File
@@ -4,6 +4,12 @@ const app = express();
const exphbs = require ('express-handlebars');
const { SetupEnvironment } = require('./environ');
const SetupRouter = require('./router');
const bodyParser = require("body-parser");
const sessionMw = require('./session');
const csrf = require("./csrf");
// Setup the environment
SetupEnvironment();
// Database
const database = require('./database');
@@ -22,9 +28,6 @@ const { HBSHelpers } = require('./helpers');
// Security
const helmet = require('helmet');
// First things first, setup the environment
SetupEnvironment();
// Get what we need for starting the server
const serverPort = process.env.SRV_PORT;
@@ -32,21 +35,25 @@ const serverPort = process.env.SRV_PORT;
const db = database.db;
const sessionStore = new SequelizeStore({
db: db,
table: 'Session'
tableName: 'Session'
})
// Body parsing
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Helmet setup
app.use(
helmet.contentSecurityPolicy({
directives: {
directives: (req, res) => ({
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
scriptSrc: ["'strict-dynamic'", `'nonce-${res.locals.nonce}'`],
objectSrc: ["'none'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', '*'],
mediaSrc: ["'self'", 'data:', '*'],
connectSrc: ["'self'", 'data:', '*']
}
}),
})
);
@@ -76,14 +83,21 @@ app.use(session({
store: sessionStore,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'prod',
sameSite: 'strict'
secure: false,
sameSite: 'lax',
path: '/'
},
}));
// Setup Assets
app.use(express.static('assets'));
// Session middlware
app.use(sessionMw.PersistSession);
// CSRF protection
app.use(csrf.csrfSynchronisedProtection);
// Setup Router
SetupRouter(app);
+208
View File
@@ -9,18 +9,75 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"argon2": "^0.44.0",
"connect-session-sequelize": "^8.0.6",
"cookie-parser": "^1.4.7",
"csrf-sync": "^4.2.1",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"express-handlebars": "^9.0.1",
"express-session": "^1.19.0",
"helmet": "^8.1.0",
"joi": "^18.2.1",
"nodemon": "^3.1.14",
"sequelize": "^6.37.8",
"sqlite3": "^6.0.1"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"license": "MIT"
},
"node_modules/@hapi/address": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz",
"integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^11.0.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@hapi/formula": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz",
"integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/hoek": {
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
"integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/pinpoint": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz",
"integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/tlds": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz",
"integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@hapi/topo": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz",
"integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^11.0.2"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -33,6 +90,21 @@
"node": ">=18.0.0"
}
},
"node_modules/@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT"
},
"node_modules/@types/debug": {
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
@@ -111,6 +183,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/argon2": {
"version": "0.44.0",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz",
"integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@phc/format": "^1.0.0",
"cross-env": "^10.0.0",
"node-addon-api": "^8.5.0",
"node-gyp-build": "^4.8.4"
},
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -389,6 +477,67 @@
"node": ">=6.6.0"
}
},
"node_modules/cross-env": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/cross-spawn/node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/csrf-sync": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.2.1.tgz",
"integrity": "sha512-+q9tlUSCi/kbwr1NYwn5+MeuNhwxz3wSv1yl42BgIWfIuErZ3HajRwzvZTkfiyIqt1PZT8lQSlffhSYjCneN7g==",
"license": "ISC",
"dependencies": {
"http-errors": "^2.0.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1084,6 +1233,24 @@
"node": ">=20"
}
},
"node_modules/joi": {
"version": "18.2.1",
"resolved": "https://registry.npmjs.org/joi/-/joi-18.2.1.tgz",
"integrity": "sha512-2/OKlogiESf2Nh3TFCrRjrr9z1DRHeW0I+KReF67+4J0Ns+8hBtHRmoWAZ2OFU6I5+TWLEe6sVlSdXPjHm5UbQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/address": "^5.1.1",
"@hapi/formula": "^3.0.2",
"@hapi/hoek": "^11.0.7",
"@hapi/pinpoint": "^2.0.1",
"@hapi/tlds": "^1.1.1",
"@hapi/topo": "^6.0.2",
"@standard-schema/spec": "^1.1.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -1311,6 +1478,17 @@
"node": "^20.17.0 || >=22.9.0"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemon": {
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
@@ -1415,6 +1593,15 @@
"node": ">= 0.8"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
@@ -1809,6 +1996,27 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+3
View File
@@ -17,13 +17,16 @@
"wprod": "set NODE_ENV=prod&& nodemon index.js"
},
"dependencies": {
"argon2": "^0.44.0",
"connect-session-sequelize": "^8.0.6",
"cookie-parser": "^1.4.7",
"csrf-sync": "^4.2.1",
"dotenv": "^17.4.2",
"express": "^5.2.1",
"express-handlebars": "^9.0.1",
"express-session": "^1.19.0",
"helmet": "^8.1.0",
"joi": "^18.2.1",
"nodemon": "^3.1.14",
"sequelize": "^6.37.8",
"sqlite3": "^6.0.1"
+22
View File
@@ -0,0 +1,22 @@
const argon2 = require('argon2');
async function HashPassword(password) {
try {
return await argon2.hash(password);
} catch(err) {
throw err;
}
}
async function TestPassword(password, hash) {
try {
return await argon2.verify(hash, password);
} catch(err) {
throw err;
}
}
module.exports = {
HashPassword,
TestPassword
}
+5 -1
View File
@@ -1,5 +1,9 @@
# Server
SRV_PORT=5000
# Cookies
SESSKEY="87216653c5e7b5789b87a8b73e9d81b671d3b50d4f3699f8b1e756669ad6533fb1a740cdf5ae32fca7ed63b0dfb88e35c153097060ee43ac9dd4ca685e7bee39ee56284ba21f2fe408777c20d21c1c5c3a8a40f956272d336c6dbebe68c87c561a2225e9131e7f1976cf439903f616175896c21962631c2c1f72d2efb5e12db3cbc91941bff99a1e9d050b6badee79063439b1f5883b49e9285ed3e16d434e6deb2babdc838caa8c51d45db8fd7116c3d3ad5f20f955b115e7d5000d8f0454b151ed42519d3d8fcb38e7510976064d188c184a174d3537c47c7e968de55b563382317873b6dc4013dd33a0700bb9ba143fd19c4a42e76cda7bd2f738280c9643701a291b77fc0d4a05309d0e44272209020539fe3592660476b602e5edda5f496443b8ab82ff1035737f745df3d3be76ba95d83d772ac45989b2c61d8e7d0b"
CKYKEY="88ae945c555650606058d58284c1772b3d8b56cee370ddbfcb366c1c4f6be3681e2e218404a4ff0dba21597817e1301fdbfedd711ab3b99c4bc900a8583892f957ea9fc657811ec20fcde7c11a12201e5a8b8c62a4dd56e5a4c06e46b5853c172d010c9fe754f95e61be7fde60701f2de44c641078e3aa51ac74cc68573c7c4b51c1eb5d219d94fe6f6bc0139ea3a731d9097d381a4b00931f71d4615912db0355eb73a44d0ed873f85cc112f945a0ac5d776d9b161cacd9d823c7b770bdc3d5f77d9161e0b45dcd05cf7498eb8d4898a0e6436264f7c11643c19ef0624ac6b08ffe9bf093c19cdfc93d35646aa0b76f603be82e6939"
# Database
DB_STORAGE="data_prod.db"
DB_STORAGE="dataprod.db"
+9
View File
@@ -0,0 +1,9 @@
const express = require('express');
const router = express.Router();
router.get('/', async (req, res) => {
console.log(`nonce: ${res.locals.globalScriptNonce}`);
res.render('index', {title: 'Domain Manager'});
});
module.exports = router;
+63
View File
@@ -0,0 +1,63 @@
const express = require('express');
const router = express.Router();
const loginValidator = require('../validators/login');
const authMw = require('../session');
const pwMw = require('../password.js');
const database = require('../database.js');
const dbConnection = database.db;
const Sequelize = require('sequelize');
const pageTitle = 'Domain Manager | Login';
router.get('/login', authMw.AllowIfNotAuthenticated, async (req, res) => {
res.render('login', { title: pageTitle, csrfToken: req.csrfToken() });
});
router.post('/login', authMw.AllowIfNotAuthenticated, async (req, res, next) => {
const reqBody = req.body;
const validationResult = loginValidator.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 user = database.models.User.findOne({
where: {
username: reqBody.login_username
},
transaction: t
});
return user;
});
if(result) {
const doesPasswordMatch = await pwMw.TestPassword(reqBody.login_password, result.password);
if(doesPasswordMatch === true) {
await authMw.CreateSession(req, result);
return res.redirect('/');
} else {
errors.push({message: 'Invalid username or password.'});
}
} else {
errors.push({message: 'Invalid username or password.'});
}
}
} catch(error) {
error.status = 500;
return next(error);
}
return res.render('login', {title: pageTitle, errors: errors, csrfToken: req.csrfToken(true) });
});
router.post('/logout', authMw.AllowIfAuthenticated, async (req, res, next) => {
// Just destroy the session
req.session.destroy();
return res.redirect('/');
});
module.exports = router;
+56
View File
@@ -0,0 +1,56 @@
const express = require('express');
const router = express.Router();
const registerValidator = require('../validators/register');
const authMw = require('../session');
const pwMw = require('../password.js');
const database = require('../database.js');
const dbConnection = database.db;
const Sequelize = require('sequelize');
const pageTitle = 'Domain Manager | Register';
router.get('/register', authMw.AllowIfNotAuthenticated, async (req, res) => {
res.render('register', { title: pageTitle, csrfToken: req.csrfToken() });
});
router.post('/register', authMw.AllowIfNotAuthenticated, async (req, res, next) => {
const reqBody = req.body;
const validationResult = registerValidator.test(reqBody);
const validationError = validationResult.error;
let errors = [];
if(validationError !== undefined)
errors = validationError.details;
try {
if(errors.length === 0) {
const hashedPassword = await pwMw.HashPassword(reqBody.register_password);
const result = await dbConnection.transaction(async(t) => {
const user = await database.models.User.create({
username: reqBody.register_username,
password: hashedPassword,
}, {transaction: t});
return user;
});
if(result !== undefined) {
await authMw.CreateSession(req, result);
return res.redirect('/');
} else {
errors.push({message: 'Failed to create user.'})
}
}
} catch(error) {
if(error instanceof Sequelize.UniqueConstraintError) {
errors.push({message: 'Username is in use.'});
} 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('register', {title: pageTitle, errors: errors, csrfToken: req.csrfToken(true) });
});
module.exports = router;
+63
View File
@@ -0,0 +1,63 @@
const { generateToken } = require('./csrf');
const database = require('./database');
const crypto = require("crypto");
async function PersistSession(req, res, next) {
req.session.visited = true;
res.locals.nonce = crypto.randomBytes(16).toString('base64');
const isLoggedIn = req.session.isLoggedIn;
if(isLoggedIn) {
const userId = req.session.userId;
const username = req.session.username;
const power = req.session.power;
req.session.ipAddress = req.ip;
res.locals.isLoggedIn = isLoggedIn;
res.locals.userId = userId;
res.locals.username = username;
res.locals.power = power;
res.locals.csrfToken = generateToken(req);
}
next();
}
async function CreateSession(req, user) {
return new Promise(async (resolve, reject) => {
try {
req.session.isLoggedIn = true;
req.session.userId = user.id;
req.session.username = user.username;
req.session.power = user.power;
resolve();
} catch(error) {
reject(error);
}
});
}
function AllowIfNotAuthenticated(req, res, next) {
const isLoggedIn = req.session.isLoggedIn;
if(isLoggedIn)
return res.redirect('/');
next();
}
function AllowIfAuthenticated(req, res, next) {
const isLoggedIn = req.session.isLoggedIn;
if(!isLoggedIn)
return res.redirect('/');
else
next();
}
module.exports = {
PersistSession,
CreateSession,
AllowIfNotAuthenticated,
AllowIfAuthenticated
}
+11
View File
@@ -0,0 +1,11 @@
const Joi = require('joi');
const loginSchema = Joi.object().keys({
login_username: Joi.string().alphanum().min(3).max(24).required(),
login_password: Joi.string().min(8).max(256).required(),
}).unknown(true);
module.exports = {
test: (body) => {
return loginSchema.validate(body);
}
}
+13
View File
@@ -0,0 +1,13 @@
const Joi = require('joi');
const userCreateSchema = Joi.object().keys({
register_username: Joi.string().alphanum().min(3).max(24).required(),
register_password: Joi.string().min(8).max(256).required(),
register_confirm_password: Joi.any().valid(Joi.ref('register_password')).required().messages({'any.only': 'Passwords must match.'})
// token later
}).unknown(true);
module.exports = {
test: (body) => {
return userCreateSchema.validate(body);
}
}
+1
View File
@@ -0,0 +1 @@
<p>This is where you can register domains and manage DNS records.</p>
+50
View File
@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/main.css">
<title>{{title}}</title>
{{{getScript "/scripts/menu.js" nonce}}}
</head>
<body>
{{#if isLoggedIn}}
<form id="Form_Auth_Logout" action="/logout" method="post">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
</form>
{{/if}}
<div id="PageContent">
<div id="Logo">
<p>Domain Manager</p>
</div>
<div id="Header">
<div id="Header-Left">
<a href="/">Home</a>
{{#if isLoggedIn}}
<span>|</span>
<a href="/dns">DNS</a>
<span>|</span>
<a href="/domains">Domains</a>
{{/if}}
</div>
<div id="Header-Right">
{{#unless isLoggedIn}}
<a href="/login">Login</a>
<span>|</span>
<a href="/register">Register</a>
{{else}}
<span>{{username}}</span>
<span>|</span>
<a id="Menu_Auth_Logout" href="javascript://void();">Logout</a>
{{/unless}}
</div>
<br>
<hr>
{{{body}}}
</div>
</body>
</html>
+24
View File
@@ -0,0 +1,24 @@
<br>
<div>
{{#if errors}}
<ul>
{{#each errors}}
<li>{{this.message}}</li>
{{/each}}
</ul>
{{/if}}
<div class="CenteredFocusHeader">
<h4>Login</h4>
</div>
<div class="CenteredFocusContent">
<form class="VerticalInputForm" action="/login" method="post">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<label for="login_username">Username</label>
<input class="TextInput" name="login_username" type="text">
<label for="login_password">Password</label>
<input class="TextInput" name="login_password" type="password">
<input type="submit" value="Login">
</form>
</div>
</div>
+26
View File
@@ -0,0 +1,26 @@
<br>
<div>
{{#if errors}}
<ul>
{{#each errors}}
<li>{{this.message}}</li>
{{/each}}
</ul>
{{/if}}
<div class="CenteredFocusHeader">
<h4>Register</h4>
</div>
<div class="CenteredFocusContent">
<form class="VerticalInputForm" action="/register" method="post">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<label for="register_username">Username</label>
<input class="TextInput" name="register_username" type="text">
<label for="register_password">Password</label>
<input class="TextInput" name="register_password" type="password">
<label for="register_confirm_password">Confirm Password</label>
<input class="TextInput" name="register_confirm_password" type="password">
<input type="submit" value="Register">
</form>
</div>
</div>