const express = require('express'); const bodyParser = require('body-parser'); const session = require('express-session'); const QRCode=require('qrcode'); const crypto=require('crypto'); const path=require('path'); const app = express(); app.set('view engine','ejs'); app.use(express.json()); app.use(express.static('public')); const PORT = 3000; const PWSalt="!SaltyMagic7283715374"; const QRSalt="!SaltyMagic5392370662"; // // ToDo, Actions: // + Login with Password // + Login with Token // + Email Link // + Signup // Change Password // + Issue Tickets // + Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code) // Split Transfer into admin (change owner, unused/used/cancelled pulldown) and public (no Used column) versions // Claim Tickets // + Use Ticket // // ToDo, Later // + Use a templating engine // + Store password hashed and salted // Make all HTML look nice // Logging and Replay system // function hashPW(pw) { const hash0=crypto.createHash('sha256'); const hash1=hash0.update(pw+PWSalt); const hash=hash1.digest("base64"); return(hash); } function hashQR(t,ownername) { const hash0=crypto.createHash('sha256'); const hash1=hash0.update(t+QRSalt+ownername); const hash=hash1.digest("base64").slice(0,6); return(hash); } // // In-memory data structures // // There are two ways to log in: with a username/password or with a token. // const users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"), superuser:true }, "fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false } }; const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00, used: false }, "habitat-2" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 }, "habitat-3" : { owner: "ginachen94@gmail.com", offered: "teppy@egenesis.com", paid: 0.00 }, "habitat-4" : { owner: "teppy@egenesis.com" , offered: "noahstern00@gmail.com", paid: 0.00 }, "habitat-5" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00, used: true }, "habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 }, }; const camps = { "habitat": { issued: 6 } }; const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 } }; const tokensu = { "teppy@egenesis.com" : "abc" }; // Middleware setup app.use(bodyParser.urlencoded({ extended: true })); app.use(session({ secret: 'supersecretkey', resave: false, saveUninitialized: false, })); // Middleware to protect routes function requireLogin(req, res, next) { if (req.session.username) return next(); return res.redirect('/login'); } function requireSuperUser(req,res,next) { if (req.session.superuser) return next(); req.session.returnTo=req.originalUrl; console.log("In requireSuperUser: ",req.session); return res.redirect('/login'); } app.get('/styles.css', (req, res) => { res.setHeader('Content-Type', 'text/css'); res.sendFile(path.join(__dirname, 'public', 'styles.css')); }); function generateSecureToken(length = 8) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let token = ''; const array = new Uint8Array(length); crypto.getRandomValues(array); for (let i = 0; i < length; i++) { token += characters.charAt(array[i] % characters.length); } return token; } const postmark = require('postmark'); const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8'); app.get('/logintoken', (req, res) => { const tok=req.query.token; const tokenData = tokens[tok]; if (!consumeToken(tok)) return res.status(200).send("Missing, Invalid or Expired Token."); req.session.username = tokenData.username; req.session.superuser= users[tokenData.username].superuser; res.redirect('/transfer'); }); app.get('/emaillink',(req,res) => { res.send(`