changes
This commit is contained in:
278
foftickets.js
278
foftickets.js
@@ -10,8 +10,9 @@ app.set('view engine','ejs');
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.static('public'));
|
app.use(express.static('public'));
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
const PWSalt="!SaltyMagic7283715374";
|
const PWSalt ="!SaltyMagic7283715374";
|
||||||
const QRSalt="!SaltyMagic5392370662";
|
const EmailSalt="!SaltyMagic3562196239";
|
||||||
|
const QRSalt ="!SaltyMagic5392370662";
|
||||||
|
|
||||||
//
|
//
|
||||||
// ToDo, Actions:
|
// ToDo, Actions:
|
||||||
@@ -23,18 +24,41 @@ const QRSalt="!SaltyMagic5392370662";
|
|||||||
// + Camp Editor
|
// + Camp Editor
|
||||||
// + Use Ticket
|
// + Use Ticket
|
||||||
// + Claim Any Tickets
|
// + Claim Any Tickets
|
||||||
// Purge Revoked Function (Admin)
|
// + Purge Revoked Function (Admin)
|
||||||
// Signup
|
// + Issue Tickets from Camp Admin Page
|
||||||
// Change Password
|
// See how much each camp/ticket has paid (Admin)
|
||||||
// Issue Tickets from Camp Admin Page
|
// Send email when new tickets are issued/offered
|
||||||
//
|
//
|
||||||
// ToDo, Later
|
// ToDo, Later
|
||||||
// + Use a templating engine
|
// + Use a templating engine
|
||||||
// + Store password hashed and salted
|
// + Store password hashed and salted
|
||||||
// Make all HTML look nice
|
// Make all HTML look nice
|
||||||
// Logging and Replay system
|
// Logging and Replay system
|
||||||
|
// Stripe Integration
|
||||||
//
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Login workflow:
|
||||||
|
// There is always an implicit magic link that allows anyone to log in with their email address and hash(GlobalSalt+PerUserSalty+Email), except if the user sets a password for himself.
|
||||||
|
// We store a per-user salt, initially the empty string, in case we need to invalidate particular users' magic links.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Commands
|
||||||
|
// ISSUE campname email
|
||||||
|
// USE ticket
|
||||||
|
// STATUS ticket i/u/r
|
||||||
|
// CLAIM ticket email
|
||||||
|
//
|
||||||
|
|
||||||
|
function hashEmail(email) {
|
||||||
|
const hash0=crypto.createHash('sha256');
|
||||||
|
const usersalt=email in users ? (linksalt in users[email] ? users[email].linksalt : "") : "";
|
||||||
|
const hash1=hash0.update(email+EmailSalt+usersalt);
|
||||||
|
const hash=hash1.digest("base64");
|
||||||
|
return(hash);
|
||||||
|
}
|
||||||
|
|
||||||
function hashPW(pw) {
|
function hashPW(pw) {
|
||||||
const hash0=crypto.createHash('sha256');
|
const hash0=crypto.createHash('sha256');
|
||||||
const hash1=hash0.update(pw+PWSalt);
|
const hash1=hash0.update(pw+PWSalt);
|
||||||
@@ -54,8 +78,8 @@ function hashQR(t,ownername) {
|
|||||||
//
|
//
|
||||||
// There are two ways to log in: with a username/password or with a token.
|
// 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 },
|
const users = { "teppy@egenesis.com" : { password: hashPW("x6321872X.1"), superuser:true, linksalt:"" },
|
||||||
"fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false }
|
"fallsonfire@gmail.com" : { password: hashPW("indiantreeonfire"), superuser:false, linksalt:"boobsarefun" }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Status can be "i"=issued, "u"=used, "r"=revoked"
|
// Status can be "i"=issued, "u"=used, "r"=revoked"
|
||||||
@@ -69,11 +93,6 @@ const tickets = { "habitat-1" : { owner: "teppyx@egenesis.com" , offered: "", p
|
|||||||
|
|
||||||
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
const camps = { "habitat": { leader: "teppy@egenesis.com", lastid:6 } };
|
||||||
|
|
||||||
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
|
||||||
};
|
|
||||||
const tokensu = { "teppy@egenesis.com" : "abc"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Middleware setup
|
// Middleware setup
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
app.use(session({
|
app.use(session({
|
||||||
@@ -92,7 +111,6 @@ function requireLogin(req, res, next) {
|
|||||||
function requireSuperUser(req,res,next) {
|
function requireSuperUser(req,res,next) {
|
||||||
if (req.session.superuser) return next();
|
if (req.session.superuser) return next();
|
||||||
req.session.returnTo=req.originalUrl;
|
req.session.returnTo=req.originalUrl;
|
||||||
console.log("In requireSuperUser: ",req.session);
|
|
||||||
return res.redirect('/login');
|
return res.redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +131,10 @@ function generateSecureToken(length = 8) {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const postmark = require('postmark');
|
const postmark = require('postmark');
|
||||||
const client = new postmark.ServerClient('f45bcb9f-6556-4420-9e21-05d16739b5e8');
|
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) => {
|
app.get('/emaillink',(req,res) => {
|
||||||
res.send(`
|
res.send(`
|
||||||
<h1>Email me a login link</h1>
|
<h1>Email me a login link</h1>
|
||||||
@@ -135,31 +144,6 @@ app.get('/emaillink',(req,res) => {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
// These can be owned, offered, of offering
|
|
||||||
function categorizeTickets(username) {
|
|
||||||
let rval="none";
|
|
||||||
let numtickets=0;
|
|
||||||
let simpleowner=0;
|
|
||||||
let offering=0;
|
|
||||||
let simpleoffered=0;
|
|
||||||
let thet="";
|
|
||||||
for (const t in tickets) if (tickets[t].status=="i") {
|
|
||||||
if (tickets[t].owner==username && tickets[t].offered=="") simpleowner+=1;
|
|
||||||
else if (tickets[t].owner==username && tickets[t].offered!=username) offering+=1;
|
|
||||||
else if (tickets[t].owner!=username && tickets[t].offered==username) simpleoffered+=1;
|
|
||||||
if (tickets[t].owner==username || tickets[t].offered==username) numtickets+=1;
|
|
||||||
if (numtickets==1) thet=t; else thet="";
|
|
||||||
}
|
|
||||||
if (numtickets==0) return [ "none", ""];
|
|
||||||
if (numtickets >1) return [ "complex", ""];
|
|
||||||
if (simpleowner+offering+simpleoffered>1)
|
|
||||||
return [ "error", "" ]
|
|
||||||
if (simpleowner==1) return [ "simpleowner", thet ];
|
|
||||||
if (offering==1) return [ "simpleoffering", thet ];
|
|
||||||
if (simpleoffered==1) return [ "simpleoffered", thet ];
|
|
||||||
return [ "complex", "" ];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/camps',requireSuperUser, (req,res) => {
|
app.get('/camps',requireSuperUser, (req,res) => {
|
||||||
const camplist={};
|
const camplist={};
|
||||||
@@ -176,7 +160,7 @@ app.get('/camps',requireSuperUser, (req,res) => {
|
|||||||
if (tickets[t].status=="u") camplist[campname].used+=1;
|
if (tickets[t].status=="u") camplist[campname].used+=1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.render("camps",{ username:req.session.username, camps:camplist });
|
return res.render("camps",{ username:req.session.username, superuser:req.session.superuser, camps:camplist });
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post("/camps",requireSuperUser,(req,res) => {
|
app.post("/camps",requireSuperUser,(req,res) => {
|
||||||
@@ -186,37 +170,21 @@ app.post("/camps",requireSuperUser,(req,res) => {
|
|||||||
const qty=Number(req.body.qty);
|
const qty=Number(req.body.qty);
|
||||||
camps[campname]??={ leader:leader, lastid:0 };
|
camps[campname]??={ leader:leader, lastid:0 };
|
||||||
for (let i=0; i<qty; i++) {
|
for (let i=0; i<qty; i++) {
|
||||||
camps[campname].lastid+=1;
|
camps[campname].lastid+=1; // LOG
|
||||||
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" };
|
tickets[campname+'-'+camps[campname].lastid]={ owner: "", offered: leader, status:"i" }; // LOG
|
||||||
}
|
}
|
||||||
return res.redirect("/camps");
|
return res.redirect("/camps");
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
app.get('/issue', requireSuperUser, (req,res) => {
|
|
||||||
const camplist={};
|
|
||||||
for (const t in tickets) {
|
|
||||||
const parts=t.split("-");
|
|
||||||
const campname=parts[0];
|
|
||||||
const ticketnum=Number(parts[1]);
|
|
||||||
camplist[campname]??={ issued:0, claimed:0, used:0 };
|
|
||||||
if (tickets[t].status!="r") {
|
|
||||||
camplist[campname].issued+=1;
|
|
||||||
if (tickets[t].owner!="") camplist[campname].claimed+=1;
|
|
||||||
if (tickets[t].status=="u") camplist[campname].used+=1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.render("issue",{ camps:camplist });
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/camplead', requireLogin, (req,res) => {
|
app.get('/camplead', requireLogin, (req,res) => {
|
||||||
let username=req.session.username;
|
let username=req.session.username;
|
||||||
const edit={ tickets: {} };
|
const edit={ username:username, superuser:req.session.superuser, tickets: {} };
|
||||||
for (const t in tickets) if (tickets[t].owner==username) {
|
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status!='r') {
|
||||||
edit.tickets[t]={};
|
edit.tickets[t]={};
|
||||||
edit.tickets[t].owner=tickets[t].owner;
|
edit.tickets[t].owner=tickets[t].owner;
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
|
edit.tickets[t].used=tickets[t].status=='u';
|
||||||
}
|
}
|
||||||
return res.render("camplead",edit);
|
return res.render("camplead",edit);
|
||||||
})
|
})
|
||||||
@@ -224,7 +192,7 @@ app.get('/camplead', requireLogin, (req,res) => {
|
|||||||
|
|
||||||
app.get('/editcamp', requireSuperUser, (req,res) => {
|
app.get('/editcamp', requireSuperUser, (req,res) => {
|
||||||
let campname=req.query.campname;
|
let campname=req.query.campname;
|
||||||
const edit={ username:req.session.username, tickets: {} };
|
const edit={ username:req.session.username, superuser:req.session.superuser, campname:campname, leader:camps[campname].leader, tickets: {} };
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
const parts=t.split("-");
|
const parts=t.split("-");
|
||||||
const cname=parts[0];
|
const cname=parts[0];
|
||||||
@@ -239,9 +207,22 @@ app.get('/editcamp', requireSuperUser, (req,res) => {
|
|||||||
return res.render("editcamp",edit);
|
return res.render("editcamp",edit);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.post('/moretickets', requireSuperUser, (req,res) => {
|
||||||
|
const qty=Number(req.body.qty);
|
||||||
|
const campname=req.body.campname;
|
||||||
|
console.log("More tickets! ",campname in camps,qty);
|
||||||
|
if (campname in camps) for (let i=0; i<qty; i++) {
|
||||||
|
camps[campname].lastid++;
|
||||||
|
tickets[campname+'-'+camps[campname].lastid]={ owner:"", offered:camps[campname].leader, paid:0, status:"i" };
|
||||||
|
}
|
||||||
|
const referer = req.get('Referer');
|
||||||
|
if (referer) return res.redirect(referer);
|
||||||
|
else return res.redirect('/');
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/manytickets', requireLogin, (req,res) => {
|
app.get('/manytickets', requireLogin, (req,res) => {
|
||||||
let username=req.session.username;
|
let username=req.session.username;
|
||||||
const edit={ username:req.session.username, tickets: {} };
|
const edit={ username:req.session.username, superuser:req.session.superuser, tickets: {} };
|
||||||
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status=="i") {
|
for (const t in tickets) if (tickets[t].owner==username && tickets[t].status=="i") {
|
||||||
edit.tickets[t]={};
|
edit.tickets[t]={};
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
@@ -251,13 +232,14 @@ app.get('/manytickets', requireLogin, (req,res) => {
|
|||||||
|
|
||||||
|
|
||||||
app.get('/mytickets',requireLogin, async (req,res)=> {
|
app.get('/mytickets',requireLogin, async (req,res)=> {
|
||||||
|
console.log("In mytickets req.session is ",req.session);
|
||||||
let username=req.session.username;
|
let username=req.session.username;
|
||||||
let claimed=0;
|
let claimed=0;
|
||||||
let owned=0;
|
let owned=0;
|
||||||
let theticket="";
|
let theticket="";
|
||||||
const edit={ username:req.session.username, tickets: {} };
|
const edit={ username:req.session.username, superuser:req.session.superuser, tickets: {} };
|
||||||
for (const t in tickets) {
|
for (const t in tickets) {
|
||||||
if (tickets[t].status=="i" && tickets[t].offered==username) { claimed++; tickets[t].owner=username; tickets[t].offered=""; }
|
if (tickets[t].status=="i" && tickets[t].offered==username) { claimed++; tickets[t].owner=username; tickets[t].offered=""; } // LOG
|
||||||
if (tickets[t].status=="i" && tickets[t].owner==username) {
|
if (tickets[t].status=="i" && tickets[t].owner==username) {
|
||||||
owned++;
|
owned++;
|
||||||
if (owned==1) theticket=t; else theticket="";
|
if (owned==1) theticket=t; else theticket="";
|
||||||
@@ -266,13 +248,13 @@ app.get('/mytickets',requireLogin, async (req,res)=> {
|
|||||||
edit.tickets[t].offered=tickets[t].offered;
|
edit.tickets[t].offered=tickets[t].offered;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (owned==0) return res.render("message",{ username:username, message:"You have no unused tickets" });
|
if (owned==0) return res.render("message",{ username:username, superuser:req.session.superuser, message:"You have no unused tickets" });
|
||||||
else if (owned==1) {
|
else if (owned==1) {
|
||||||
const hash0=crypto.createHash('sha256');
|
const hash0=crypto.createHash('sha256');
|
||||||
const hash1=hash0.update(theticket+QRSalt);
|
const hash1=hash0.update(theticket+QRSalt);
|
||||||
const hash=hash1.digest("base64").slice(0,6);
|
const hash=hash1.digest("base64").slice(0,6);
|
||||||
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
||||||
return res.render("oneticket",{ username:username, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
return res.render("oneticket",{ username:username, superuser:req.session.superuser, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
||||||
}
|
}
|
||||||
else return res.render("manytickets",edit);
|
else return res.render("manytickets",edit);
|
||||||
});
|
});
|
||||||
@@ -282,60 +264,21 @@ app.post('/mytickets',requireLogin, async (req,res)=> {
|
|||||||
let theticket=req.body.ticket;
|
let theticket=req.body.ticket;
|
||||||
let offered=req.body.offered;
|
let offered=req.body.offered;
|
||||||
if (tickets[theticket].owner==username && tickets[theticket].status=="i") {
|
if (tickets[theticket].owner==username && tickets[theticket].status=="i") {
|
||||||
tickets[theticket].offered=offered;
|
tickets[theticket].offered=offered; // LOG
|
||||||
}
|
}
|
||||||
const hash0=crypto.createHash('sha256');
|
const hash0=crypto.createHash('sha256');
|
||||||
const hash1=hash0.update(theticket+QRSalt);
|
const hash1=hash0.update(theticket+QRSalt);
|
||||||
const hash=hash1.digest("base64").slice(0,6);
|
const hash=hash1.digest("base64").slice(0,6);
|
||||||
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+theticket+'&h='+hashQR(theticket,username));
|
||||||
return res.render("oneticket", { username:username, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
return res.render("oneticket", { username:username, superuser:req.session.superuser, ticket:theticket, offered:tickets[theticket].offered, qrcode:dataURL });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Big Kahuna
|
|
||||||
// If you have zero tickets, show something saying that
|
|
||||||
// For each ticket owned, display options to offer it, use it, or (eventually) pay for it.
|
|
||||||
// For each ticket offered, show a button to claim it.
|
|
||||||
// Have a button to claim all tickets, if there are multiple offered to you
|
|
||||||
app.get('/transfer', requireLogin, async (req,res) => {
|
|
||||||
let username=req.session.username;
|
|
||||||
const [ cat, ticket ] = categorizeTickets(username);
|
|
||||||
const simpledata={ username: username, ticket: ticket };
|
|
||||||
if (cat=="none") return res.render("notickets",simpledata);
|
|
||||||
if (cat=="error") return res.render("error",simpledata);
|
|
||||||
if (cat=="simpleoffered") return res.render("claimone",simpledata);
|
|
||||||
if (cat=="simpleoffering") return res.render("retractoffer",simpledata);
|
|
||||||
if (cat=="complex") {
|
|
||||||
const edit={ username: username, tickets: {} };
|
|
||||||
for (const t in tickets)
|
|
||||||
if (tickets[t].owner==username || tickets[t].offered==username) {
|
|
||||||
edit.tickets[t]={};
|
|
||||||
edit.tickets[t].owner=tickets[t].owner;
|
|
||||||
edit.tickets[t].offered=tickets[t].offered;
|
|
||||||
edit.tickets[t].used=tickets[t].status=="u";
|
|
||||||
}
|
|
||||||
return res.render("transfer",edit);
|
|
||||||
}
|
|
||||||
if (cat=="simpleowner") {
|
|
||||||
try {
|
|
||||||
const hash0=crypto.createHash('sha256');
|
|
||||||
const hash1=hash0.update(ticket+QRSalt);
|
|
||||||
const hash=hash1.digest("base64").slice(0,6);
|
|
||||||
const dataURL=await QRCode.toDataURL('localhost:3000/useticket?t='+ticket+'&h='+hashQR(ticket,username));
|
|
||||||
simpledata.qrcode=dataURL;
|
|
||||||
return res.render("simpleowner",simpledata);
|
|
||||||
}
|
|
||||||
catch(err) {
|
|
||||||
res.status(500).send('Error generating QR code');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cat=="complex") return res.render('transfer',simpledata);
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.post("/changestatus", requireSuperUser, (req,res) => {
|
app.post("/changestatus", requireSuperUser, (req,res) => {
|
||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
console.log("Changestatus ",ticket);
|
console.log("Changestatus ",ticket);
|
||||||
tickets[ticket].status=req.body.status;
|
tickets[ticket].status=req.body.status; // LOG
|
||||||
res.json({ message: 'Status received', status: req.body.status });
|
res.json({ message: 'Status received', status: req.body.status });
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -347,7 +290,7 @@ app.post("/updateoffered", requireLogin, (req,res) => {
|
|||||||
else if (tickets[ticket].status=="u") res.status(500).send("Ticket "+ticket+" has already been used");
|
else if (tickets[ticket].status=="u") res.status(500).send("Ticket "+ticket+" has already been used");
|
||||||
else if (tickets[ticket].status=="r") res.status(500).send("Ticket "+ticket+" was revoked");
|
else if (tickets[ticket].status=="r") res.status(500).send("Ticket "+ticket+" was revoked");
|
||||||
else {
|
else {
|
||||||
tickets[ticket].offered=offered;
|
tickets[ticket].offered=offered; // LOG
|
||||||
res.json({ message: 'Updated owner of '+ticket+' to '+offered });
|
res.json({ message: 'Updated owner of '+ticket+' to '+offered });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -357,8 +300,8 @@ app.post("/updateticketsu", requireSuperUser, (req,res) => {
|
|||||||
const ticket=req.body.ticket;
|
const ticket=req.body.ticket;
|
||||||
const owner=req.body.owner;
|
const owner=req.body.owner;
|
||||||
const offered=req.body.offered;
|
const offered=req.body.offered;
|
||||||
tickets[ticket].owner=req.body.owner;
|
tickets[ticket].owner=req.body.owner; // LOG
|
||||||
tickets[ticket].offered=req.body.offered;
|
tickets[ticket].offered=req.body.offered; // LOG
|
||||||
res.json({ message: 'Updated '+ticket });
|
res.json({ message: 'Updated '+ticket });
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -369,7 +312,7 @@ app.get("/useticket",(req,res) => {
|
|||||||
if (hashQR(ticket,req.session.username)!=hash) res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].username);
|
if (hashQR(ticket,req.session.username)!=hash) res.status(500).send("Ticket "+ticket+" was transferred to "+tickets[ticket].username);
|
||||||
else if (tickets[ticket].status!="i") res.status(500).send("Ticket "+ticket+" has already been used.");
|
else if (tickets[ticket].status!="i") res.status(500).send("Ticket "+ticket+" has already been used.");
|
||||||
else {
|
else {
|
||||||
tickets[ticket].status="u";
|
tickets[ticket].status="u"; // LOG
|
||||||
res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
|
res.send("<h1>Welcome, "+tickets[ticket].owner+" to Falls on Fire! Ticket "+ticket+" has now been used.</h1>");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -422,20 +365,12 @@ app.get('/testemail',
|
|||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
if (req.session.username) {
|
if (req.session.username) return res.redirect("/mytickets");
|
||||||
res.send(`
|
return res.render("login", { superuser:false });
|
||||||
<h1>Welcome, ${req.session.username}</h1>
|
});
|
||||||
<a href="/products">Products</a><br>
|
|
||||||
<a href="/faq">FAQ</a><br>
|
app.get('/login',(req,res) => {
|
||||||
<a href="/logout">Log Out</a>
|
return res.render("login",{ superuser:false });
|
||||||
`);
|
|
||||||
} else {
|
|
||||||
res.send(`
|
|
||||||
<h1>Welcome to our website!</h1>
|
|
||||||
<a href="/login">Log In</a><br>
|
|
||||||
<a href="/signup">Sign Up</a>
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/signup', (req, res) => {
|
app.get('/signup', (req, res) => {
|
||||||
@@ -451,24 +386,6 @@ app.get('/signup', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function consumeToken(tok) {
|
|
||||||
if (!(tok in tokens)) return false;
|
|
||||||
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) => {
|
app.post('/signup', (req, res) => {
|
||||||
@@ -502,21 +419,9 @@ app.post('/changepassword', (req, res) => {
|
|||||||
return res.send('Passwords do not match<a href="/">Back</a>');
|
return res.send('Passwords do not match<a href="/">Back</a>');
|
||||||
}
|
}
|
||||||
users[req.session.username].password=hashPW(password1);
|
users[req.session.username].password=hashPW(password1);
|
||||||
res.redirect('/login');
|
res.redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/login', (req, res) => {
|
|
||||||
console.log("In get /login: ",req.session);
|
|
||||||
res.send(`
|
|
||||||
<h1>Log In</h1>
|
|
||||||
<form method="POST" action="/login">
|
|
||||||
<label>Username: <input type="text" name="username" required></label><br>
|
|
||||||
<label>Password: <input type="password" name="password" required></label><br>
|
|
||||||
<button type="submit">Log In</button>
|
|
||||||
</form>
|
|
||||||
<a href="/signup">Sign Up</a>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/login', (req, res) => {
|
app.post('/login', (req, res) => {
|
||||||
const { username, password, superuser } = req.body;
|
const { username, password, superuser } = req.body;
|
||||||
@@ -526,7 +431,7 @@ app.post('/login', (req, res) => {
|
|||||||
console.log("In post /login",req.session);
|
console.log("In post /login",req.session);
|
||||||
const redir=req.session.returnTo;
|
const redir=req.session.returnTo;
|
||||||
delete req.session.returnTo;
|
delete req.session.returnTo;
|
||||||
return res.redirect(redir || "/transfer");
|
return res.redirect(redir || "/mytickets");
|
||||||
}
|
}
|
||||||
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
||||||
});
|
});
|
||||||
@@ -552,28 +457,23 @@ app.post('/qrcodesu',requireSuperUser,async (req,res) => {
|
|||||||
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
return res.send({ owner:tickets[ticket].owner, qrcode: URL });
|
||||||
})
|
})
|
||||||
|
|
||||||
// Protected routes
|
app.get('/settings',requireSuperUser, (req,res) => {
|
||||||
app.get('/products', requireLogin, (req, res) => {
|
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "" })
|
||||||
res.send(`
|
});
|
||||||
<h1>Products Page</h1>
|
|
||||||
<p>Here is a list of products!</p>
|
|
||||||
<a href="/">Home</a>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/tickets', requireLogin, (req, res) => {
|
app.post('/wipedb',requireSuperUser, (req,res) => {
|
||||||
res.write(`<h1>Tickets for `+req.session.username+`</h1>`);
|
for (const key in users ) delete users[key];
|
||||||
res.write(`<a href="/">Home</a>`);
|
for (const key in tickets) delete tickets[key];
|
||||||
res.end();
|
for (const key in camps ) delete camps[key];
|
||||||
});
|
res.render('settings',{ username:req.session.username, superuser:req.session.superuser, message: "Wiped the database, but not the logfile." })
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/purge',requireSuperUser, (req,res) => {
|
||||||
|
let count=0;
|
||||||
|
for (const t in tickets) if (tickets[t].status=='r') { count++; delete tickets[t]; }
|
||||||
|
res.render('message',{ username:req.session.username, superuser:req.session.superuser, message: "Purged "+count+" revoked tickets" })
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/faq', requireLogin, (req, res) => {
|
|
||||||
res.send(`
|
|
||||||
<h1>FAQ Page</h1>
|
|
||||||
<p>Here are some frequently asked questions.</p>
|
|
||||||
<a href="/">Home</a>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
|
|||||||
@@ -12,28 +12,31 @@
|
|||||||
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1>Welcome, <%= username %>!</h1>
|
<div class="content">
|
||||||
<form id="editor">
|
<h1>Welcome, <%= username %>!</h1>
|
||||||
<div id="server-response">Server Ready</div>
|
<form id="editor">
|
||||||
<table border="1">
|
<div id="server-response">Server Ready</div>
|
||||||
<tr>
|
<table border="1">
|
||||||
<th>Ticket#</th>
|
<tr>
|
||||||
<th>Owner</th>
|
<th>Ticket#</th>
|
||||||
<th>Offered To</th>
|
<th>Owner</th>
|
||||||
<th>Action</th>
|
<th>Offered To</th>
|
||||||
</tr>
|
<th>Action</th>
|
||||||
<% for (const t in tickets) { %>
|
</tr>
|
||||||
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><%=t%></td>
|
||||||
<td><%=tickets[t].owner%></td>
|
<td><%=tickets[t].owner%></td>
|
||||||
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
||||||
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|||||||
@@ -6,16 +6,17 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="grid">
|
<body class="grid">
|
||||||
<%- include('partials/nav') %>
|
<%- include('partials/nav') %>
|
||||||
<form id="editor" method="post">
|
<div class="content">
|
||||||
<table border="1">
|
<form id="editor" method="post">
|
||||||
<tr>
|
<table border="1">
|
||||||
<th>Camp</th>
|
<tr>
|
||||||
<th>Leader</th>
|
<th>Camp</th>
|
||||||
<th>#Issued</th>
|
<th>Leader</th>
|
||||||
<th>#Claimed</th>
|
<th>#Issued</th>
|
||||||
<th>#Used</th>
|
<th>#Claimed</th>
|
||||||
</tr>
|
<th>#Used</th>
|
||||||
<% for (const c in camps) { %>
|
</tr>
|
||||||
|
<% for (const c in camps) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/editcamp?campname=<%= c %>"><%=c%></td>
|
<td><a href="/editcamp?campname=<%= c %>"><%=c%></td>
|
||||||
<td><%=camps[c].leader%> </td>
|
<td><%=camps[c].leader%> </td>
|
||||||
@@ -23,15 +24,16 @@
|
|||||||
<td><%=camps[c].claimed%> </td>
|
<td><%=camps[c].claimed%> </td>
|
||||||
<td><%=camps[c].used%> </td>
|
<td><%=camps[c].used%> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<br><b>Create New Camp:</b><br>
|
<br><b>Create New Camp:</b><br>
|
||||||
Group Name: <input type="edit" name="campname"><br>
|
Group Name: <input type="edit" name="campname"><br>
|
||||||
Lead Email: <input type="edit" name="leader"><br>
|
Lead Email: <input type="edit" name="leader"><br>
|
||||||
Initial Qty (Optional): <input type="edit" name="qty"><br>
|
Initial Qty (Optional): <input type="edit" name="qty"><br>
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -12,17 +12,22 @@
|
|||||||
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="editor">
|
<div class="content">
|
||||||
<div id="server-response">Server Ready</div>
|
<form id="editor">
|
||||||
<table border="1">
|
<div id="server-response">Server Ready</div>
|
||||||
<tr>
|
<br>
|
||||||
<th>Ticket#</th>
|
Camp Name: <%=campname%><br>
|
||||||
<th>Owner</th>
|
Camp Leader: <%=leader%>
|
||||||
<th>Offered To</th>
|
|
||||||
<th>Status</th>
|
<table border="1">
|
||||||
<th>Action</th>
|
<tr>
|
||||||
</tr>
|
<th>Ticket#</th>
|
||||||
<% for (const t in tickets) { %>
|
<th>Owner</th>
|
||||||
|
<th>Offered To</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><%=t%></td>
|
||||||
<td><input type="edit" value="<%=tickets[t].owner%>" id="<%=t%>-owner"> </td>
|
<td><input type="edit" value="<%=tickets[t].owner%>" id="<%=t%>-owner"> </td>
|
||||||
@@ -36,12 +41,19 @@
|
|||||||
</td>
|
</td>
|
||||||
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<form action='/moretickets' method='post'>
|
||||||
|
<input type="hidden" name="campname" value="<%=campname%>">
|
||||||
|
Issue Tickets (Qty): <input type="edit" name="qty"><br>
|
||||||
|
<button type="submit">Issue</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
views/login.ejs
Normal file
19
views/login.ejs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Login</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<div class="content">
|
||||||
|
Most users log in with an email link, not a password.
|
||||||
|
<form id="editor" method="POST" action="/login">
|
||||||
|
Email:<input type="email" name="username"><br>
|
||||||
|
Password:<input type="password" name="password"><br>
|
||||||
|
<button id="Submit" type="submit">Log In</button>
|
||||||
|
<br>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -12,25 +12,28 @@
|
|||||||
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
<img class="qrcode-image" id="QRCodeImage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgMBAAeWBjwAAAAASUVORK5CYII=" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="editor">
|
<div class="content">
|
||||||
<div id="server-response">Server Ready</div>
|
<form id="editor">
|
||||||
<table border="1">
|
<div id="server-response">Server Ready</div>
|
||||||
<tr>
|
Tickets owned by <%=username%>
|
||||||
<th>Ticket#</th>
|
<table border="1">
|
||||||
<th>Offered To</th>
|
<tr>
|
||||||
<th>Action</th>
|
<th>Ticket#</th>
|
||||||
</tr>
|
<th>Offered To</th>
|
||||||
<% for (const t in tickets) { %>
|
<th>Action</th>
|
||||||
|
</tr>
|
||||||
|
<% for (const t in tickets) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=t%></td>
|
<td><%=t%></td>
|
||||||
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
||||||
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
<tr>
|
<tr>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%- include('partials/nav') %>
|
<%- include('partials/nav') %>
|
||||||
<%=message%>
|
<div class="content">
|
||||||
|
<%=message%>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<!-- views/partials/nav.ejs -->
|
<!-- views/partials/nav.ejs -->
|
||||||
<aside>
|
<aside>
|
||||||
<nav class="nav-links">
|
<nav class="nav-links">
|
||||||
Welcome, <%= username %>!
|
Falls on Fire
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/mytickets">View My Tickets</a></li>
|
<li><a href="/mytickets">View My Tickets</a></li>
|
||||||
|
<%if (superuser) {%>
|
||||||
<li><a href="/camps">View Camps (Admin)</a></li>
|
<li><a href="/camps">View Camps (Admin)</a></li>
|
||||||
|
<li><a href="/settings">Settings (Admin)</a></li>
|
||||||
|
<%}%>
|
||||||
|
<li><a href="/logout">Log Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
19
views/settings.ejs
Normal file
19
views/settings.ejs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Settings</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%- include('partials/nav') %>
|
||||||
|
<div class="content">
|
||||||
|
<form action='/purge' method='post'>
|
||||||
|
<button type="submit" >Purge Revoked Tickets</button>
|
||||||
|
</form>
|
||||||
|
<form action='/wipedb' method='post'>
|
||||||
|
<button type="submit" >Wipe the Database</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
Reference in New Issue
Block a user