diff --git a/foftickets.js b/foftickets.js
new file mode 100644
index 0000000..915adf6
--- /dev/null
+++ b/foftickets.js
@@ -0,0 +1,415 @@
+const express = require('express');
+const bodyParser = require('body-parser');
+const session = require('express-session');
+const QRCode=require('qrcode');
+const crypto=require('crypto');
+
+const app = express();
+app.set('view engine','ejs');
+app.use(express.json());
+const PORT = 3000;
+const PWSalt="!SaltyMagic7283715374";
+const QRSalt="!SaltyMagic5392370662";
+
+//
+// ToDo, Actions:
+// + Login with Password
+// + Login with Token
+// + Email Link
+// Signup
+// Set Password
+// Issue Tickets
+// Transfer Tickets Complicated page. (One ticket: static with transfer and QR code. Multi: buttons to transfer or show QR code)
+// Claim Tickets
+// + Use Ticket
+// Edit Tickets
+//
+// 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,username) {
+ const hash0=crypto.createHash('sha256');
+ const hash1=hash0.update(pw+QRSalt);
+ 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:1 },
+ "fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:1 }
+ };
+
+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 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 res.redirect('/login');
+ }
+ next();
+}
+
+
+
+
+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('/loginpassword',(req,res) => {
+ res.send(`
Sign Up
+ `);
+})
+
+app.post('/loginpassword', (req, res) => {
+ const { username, password } = req.body;
+ if (username in users && hashPW(password)==users[username].password) {
+ req.session.username=username;
+ req.session.superuser=users[username].superuser;
+ res.redirect('/transfer');
+ } else { res.redirect('/transfer'); }
+})
+
+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= tokenData.superuser;
+ res.redirect('/transfer');
+ });
+
+app.get('/emaillink',(req,res) => {
+ res.send(`
+ Email me a login link
+
+ Log In
+ `);
+});
+
+
+function consumeToken(tok) {
+ console.log("ConsumeToken 1");
+ if (!(tok in tokens)) return false;
+ console.log("ConsumeToken 2");
+ const rval=tokens[tok].expires==0 || tokens[tok].expires>=Date.now();
+ delete tokensu[tokens[tok].username];
+ delete tokens[tok];
+ return rval;
+}
+
+function createToken(username) {
+ const token=generateSecureToken();
+ tokens[token]={ username: username, expires:0 };
+ tokensu[username]=token;
+ return token;
+}
+
+app.get('/test', (req, res) => {
+ res.send("Test worked:" + consumeToken("abc"));
+})
+
+
+app.post('/signup', (req, res) => {
+ const { username, password } = req.body;
+ if (users[username]) {
+ return res.send('User already exists. Try again');
+ }
+ users[username] = { password };
+ res.redirect('/login');
+});
+
+app.get('/changepassword', (req, res) => {
+ res.send(`
+ Change Password
+
+ Home
+ `);
+});
+
+app.post('/changepassword', (req, res) => {
+ const { password1, password2 } = req.body;
+ if (!req.session.username) {
+ return res.send('You are not logged inBack');
+ }
+ if (password1!=password2) {
+ return res.send('Passwords do not matchBack');
+ }
+ users[req.session.username].password=hashPW(password1);
+ res.redirect('/login');
+});
+
+app.get('/login', (req, res) => {
+ res.send(`
+ Log In
+
+ Sign Up
+ `);
+});
+
+app.post('/login', (req, res) => {
+ const { username, password } = req.body;
+ if (users[username] && users[username].password === hashPW(password)) {
+ req.session.username = username;
+ return res.redirect('/transfer');
+ }
+ res.send('Invalid username or password. Try again');
+});
+
+app.get('/logout', (req, res) => {
+ req.session.destroy(() => {
+ res.redirect('/');
+ });
+});
+
+// Protected routes
+app.get('/products', requireLogin, (req, res) => {
+ res.send(`
+ Products Page
+ Here is a list of products!
+ Home
+ `);
+});
+
+app.get('/tickets', requireLogin, (req, res) => {
+ res.write(`Tickets for `+req.session.username+`
`);
+ res.write(`Home`);
+ res.end();
+});
+
+app.get('/faq', requireLogin, (req, res) => {
+ res.send(`
+ FAQ Page
+ Here are some frequently asked questions.
+ Home
+ `);
+});
+
+// Start the server
+app.listen(PORT, () => {
+ console.log(`Server is running at http://localhost:${PORT}`);
+});
diff --git a/views/error.ejs b/views/error.ejs
new file mode 100644
index 0000000..666f1aa
--- /dev/null
+++ b/views/error.ejs
@@ -0,0 +1,9 @@
+
+
+
+ Error Page
+
+
+ Some kind of error
+
+
diff --git a/views/error.ejs~ b/views/error.ejs~
new file mode 100644
index 0000000..65cedf5
--- /dev/null
+++ b/views/error.ejs~
@@ -0,0 +1,9 @@
+
+
+
+ You don't have any tickets
+
+
+ No tickets for you!!
+
+
diff --git a/views/notickets.ejs b/views/notickets.ejs
new file mode 100644
index 0000000..65cedf5
--- /dev/null
+++ b/views/notickets.ejs
@@ -0,0 +1,9 @@
+
+
+
+ You don't have any tickets
+
+
+ No tickets for you!!
+
+
diff --git a/views/notickets.ejs~ b/views/notickets.ejs~
new file mode 100644
index 0000000..c6403f7
--- /dev/null
+++ b/views/notickets.ejs~
@@ -0,0 +1,27 @@
+
+
+
+ Your Tickets
+
+
+ Welcome, <%= username %>!
+
+
+ | Ticket |
+ Owner |
+ Offered To |
+ QRCode |
+
+ <% for (const t in tlist) { %>
+
+ | <%= t %> |
+ <%= tlist[t].owner %> |
+ <%= tlist[t].offered %> |
+ <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> |
+
+ <% } %>
+
+
+
+
+
diff --git a/views/simpleowner.ejs b/views/simpleowner.ejs
new file mode 100644
index 0000000..eb82754
--- /dev/null
+++ b/views/simpleowner.ejs
@@ -0,0 +1,11 @@
+
+
+
+ You have a Ticket!
+
+
+ Welcome, <%= username %>!
+ Your ticket is:
+ <%= ticket %>
+
+
diff --git a/views/simpleowner.ejs~ b/views/simpleowner.ejs~
new file mode 100644
index 0000000..f6ac4ef
--- /dev/null
+++ b/views/simpleowner.ejs~
@@ -0,0 +1,28 @@
+
+
+
+ Your Tickets
+
+
+ Welcome, <%= username %>!
+
+
+ | Ticket |
+ Owner |
+ Offered To |
+ QRCode |
+
+ <% for (const t in tlist) { %>
+
+ | <%= t %> |
+ <%= tlist[t].owner %> |
+ <%= tlist[t].offered %> |
+ <%= simpledata %> |
+ <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> |
+
+ <% } %>
+
+
+
+
+
diff --git a/views/transfer.ejs b/views/transfer.ejs
new file mode 100644
index 0000000..c19fa18
--- /dev/null
+++ b/views/transfer.ejs
@@ -0,0 +1,66 @@
+
+
+
+ Your Tickets
+
+
+ Welcome, <%= username %>!
+
+
+
+
diff --git a/views/transfer.ejs~ b/views/transfer.ejs~
new file mode 100644
index 0000000..f6ac4ef
--- /dev/null
+++ b/views/transfer.ejs~
@@ -0,0 +1,28 @@
+
+
+
+ Your Tickets
+
+
+ Welcome, <%= username %>!
+
+
+ | Ticket |
+ Owner |
+ Offered To |
+ QRCode |
+
+ <% for (const t in tlist) { %>
+
+ | <%= t %> |
+ <%= tlist[t].owner %> |
+ <%= tlist[t].offered %> |
+ <%= simpledata %> |
+ <%= QRCode.toDataURL("https://abc.com", function(err,url) { if (err) { console.error(err); return; } console.log(url); }); %> |
+
+ <% } %>
+
+
+
+
+