changes
This commit is contained in:
@@ -3,6 +3,7 @@ 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');
|
||||
@@ -16,8 +17,8 @@ const QRSalt="!SaltyMagic5392370662";
|
||||
// + Login with Password
|
||||
// + Login with Token
|
||||
// + Email Link
|
||||
// Signup
|
||||
// Set Password
|
||||
// + 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)
|
||||
// Claim Tickets
|
||||
@@ -50,8 +51,8 @@ function hashQR(t,username) {
|
||||
//
|
||||
// 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 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 },
|
||||
@@ -62,6 +63,8 @@ const tickets = { "habitat-1" : { owner: "teppy@egenesis.com" , offered: "", pa
|
||||
"habitat-6" : { owner: "teppy@egenesis.com" , offered: "", paid: 0.00 },
|
||||
};
|
||||
|
||||
const camps = { "habitat": 0 };
|
||||
|
||||
const tokens = { "abc" : { username: "teppy@egenesis.com", expires: 0 }
|
||||
};
|
||||
const tokensu = { "teppy@egenesis.com" : "abc"
|
||||
@@ -77,14 +80,21 @@ app.use(session({
|
||||
|
||||
// Middleware to protect routes
|
||||
function requireLogin(req, res, next) {
|
||||
if (!req.session.username) {
|
||||
return res.redirect('/login');
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
if (!req.session.username) return res.redirect('/login');
|
||||
next();
|
||||
}
|
||||
|
||||
function requireSuperUser(req,res,next) {
|
||||
console.log(req.session);
|
||||
if (!req.session.superuser) return res.status(403).send(JSON.stringify( { error: "Forbidden without superuser flag" } ));
|
||||
else next();
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
@@ -123,7 +133,7 @@ app.get('/logintoken', (req, res) => {
|
||||
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;
|
||||
req.session.superuser= users[tokenData.username].superuser;
|
||||
res.redirect('/transfer');
|
||||
});
|
||||
|
||||
@@ -163,7 +173,6 @@ function categorizeTickets(username) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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.
|
||||
@@ -205,8 +214,7 @@ app.get('/transfer', requireLogin, async (req,res) => {
|
||||
if (cat=="complex") return res.render('transfer',simpledata);
|
||||
})
|
||||
|
||||
app.post("/toggle", (req,res) => {
|
||||
console.log("Here is Tickets...",tickets);
|
||||
app.post("/toggle", requireSuperUser, (req,res) => {
|
||||
const ticket=req.body.ticket;
|
||||
const isChecked = req.body.checked;
|
||||
tickets[ticket].used=isChecked;
|
||||
@@ -214,6 +222,17 @@ app.post("/toggle", (req,res) => {
|
||||
res.json({ message: 'Checkbox state received', checked: isChecked });
|
||||
})
|
||||
|
||||
app.post("/updateoffered", requireLogin, (req,res) => {
|
||||
const ticket=req.body.ticket;
|
||||
const offered=req.body.offered;
|
||||
console.log("Ticket: ",ticket);
|
||||
if (tickets[ticket].owner!=req.session.username) res.status(500).send("Ticket "+ticket+" owned by someone else");
|
||||
else if (tickets[ticket].used) res.status(500).send("Ticket "+ticket+" has already been used");
|
||||
else {
|
||||
tickets[ticket].offered=offered;
|
||||
res.json({ message: 'Updated owner of '+ticket+' to '+offered });
|
||||
}
|
||||
})
|
||||
|
||||
app.get("/useticket",(req,res) => {
|
||||
let ticket=req.t;
|
||||
@@ -254,7 +273,6 @@ app.post('emaillink',(req,res) => {
|
||||
})
|
||||
|
||||
|
||||
|
||||
app.get('/testemail',
|
||||
(req,res) => {
|
||||
client.sendEmail({ From: 'tickets@fallsonfire.net',
|
||||
@@ -331,7 +349,8 @@ app.post('/signup', (req, res) => {
|
||||
if (users[username]) {
|
||||
return res.send('User already exists. <a href="/signup">Try again</a>');
|
||||
}
|
||||
users[username] = { password };
|
||||
users[username] = { password: hashPW(password) };
|
||||
console.log("Created new account:",username);
|
||||
res.redirect('/login');
|
||||
});
|
||||
|
||||
@@ -372,10 +391,11 @@ app.get('/login', (req, res) => {
|
||||
});
|
||||
|
||||
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');
|
||||
const { username, password, superuser } = req.body;
|
||||
if (users[username] && users[username].password === hashPW(password)) {
|
||||
req.session.username = username;
|
||||
req.session.superuser = users[username].superuser;
|
||||
return res.redirect('/transfer');
|
||||
}
|
||||
res.send('Invalid username or password. <a href="/login">Try again</a>');
|
||||
});
|
||||
|
||||
31
public/styles.css
Normal file
31
public/styles.css
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
/* Modal container */
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1000; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Black with opacity */
|
||||
}
|
||||
|
||||
/* Modal content */
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 15% auto; /* Center the modal */
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%; /* Adjust as needed */
|
||||
}
|
||||
|
||||
/* Close button */
|
||||
.close {
|
||||
color: black;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
9
views/superuser.ejs
Normal file
9
views/superuser.ejs
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Error: Not Superuser</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Error: Not Superuser</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,13 +2,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Your Tickets</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome, <%= username %>!</h1>
|
||||
<form id="editor">
|
||||
<div id="server-response">Server Ready</div>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Ticket</th>
|
||||
<th>Ticket#</th>
|
||||
<th>Owner</th>
|
||||
<th>Offered To</th>
|
||||
<th>Used</th>
|
||||
@@ -17,10 +19,10 @@
|
||||
<% for (const t in tickets) { %>
|
||||
<tr>
|
||||
<td><%=t%></td>
|
||||
<td><%=tickets[t].owner%></td>
|
||||
<td><input type="edit" value="<%=tickets[t].owner%>" id="<%=t%>-owner"> </td>
|
||||
<td><input type="edit" value="<%=tickets[t].offered%>" id="<%=t%>-offered"></td>
|
||||
<td><input type="checkbox" id="<%=t%>-used"<%=tickets[t].used ? ' checked' : ''%>></td>
|
||||
<td><button id="<%=t%>-action"> QRCode </button></td>
|
||||
<td><input type="checkbox" id="<%=t%>-used"<%=tickets[t].used ? ' checked' : ''%>></td>
|
||||
<td><button id="<%=t%>-action" type="button"> QRCode </button></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
<tr>
|
||||
@@ -29,37 +31,68 @@
|
||||
</form>
|
||||
<script>
|
||||
// JavaScript to change the form element
|
||||
const EditOffer = document.getElementById('habitat-1-offered');
|
||||
const ActionButton = document.getElementById('habitat-1-action');
|
||||
const ResponseDisplay = document.getElementById("server-response");
|
||||
let ResponseStack = 0;
|
||||
let ResponseError="";
|
||||
let ooEdits={}; // Owner or Offer has been edited
|
||||
|
||||
function UpdateSR(delta) {
|
||||
ResponseStack+=delta;
|
||||
if (ResponseError!="") ResponseDisplay.textContent=ResponseError;
|
||||
else if (ResponseStack>0) ResponseDisplay.textContent="Waiting for Server";
|
||||
else ResponseDisplay.textContent="Server Ready";
|
||||
}
|
||||
|
||||
|
||||
function toggleUsed(el) {
|
||||
const id=el.target.id.slice(0,-5);
|
||||
const isChecked=this.checked;
|
||||
const js=JSON.stringify( { ticket: id, checked: isChecked } );
|
||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: js };
|
||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||
console.log(fetchtable);
|
||||
UpdateSR(1);
|
||||
fetch('/toggle',fetchtable)
|
||||
.then( response => response.json() )
|
||||
.then( data => console.log('Server Response: ',data) )
|
||||
.catch( error => console.error('Error: ',error));
|
||||
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||
.then( data => UpdateSR(-1) )
|
||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||
}
|
||||
|
||||
const elements = document.querySelectorAll("[id$=-used]");
|
||||
document.body.addEventListener("click", event => {
|
||||
const id=event.target.id;
|
||||
if (id.endsWith("-action")) {
|
||||
const id0=id.slice(0,-7);
|
||||
if (ooEdits[id0]) {
|
||||
UpdateSR(1);
|
||||
const offeredEdit=document.getElementById(id0+"-offered");
|
||||
const js=JSON.stringify( { ticket: id0, offered: offeredEdit.value } );
|
||||
const fetchtable={ method:'POST', headers: { 'Content-Type': 'application/json' }, body: js };
|
||||
fetch('/updateoffered',fetchtable)
|
||||
.then( response => { if (!response.ok) throw new Error(`Server responded with status ${response.status}`); else return response.json(); } )
|
||||
.then( data => { UpdateSR(-1); event.target.textContent="QRCode"; } )
|
||||
.catch( error => { console.log("Here is the error!"+error); ResponseError=error; UpdateSR(-1); })
|
||||
} else {
|
||||
|
||||
console.log("QRCode ",id0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
elements.forEach((el) => {
|
||||
const checkboxes = document.querySelectorAll("[id$=-used]");
|
||||
checkboxes.forEach(el => {
|
||||
const UsedCheckbox = document.getElementById(el.id);
|
||||
UsedCheckbox.addEventListener("change",toggleUsed);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
EditOffer.addEventListener('input', () => {
|
||||
ActionButton.textContent = "Update";
|
||||
});
|
||||
|
||||
const owners = document.querySelectorAll("[id$=-owner]");
|
||||
owners.forEach(el => {
|
||||
const id0=el.id.slice(0,-6);
|
||||
const OwnerEdit = document.getElementById(id0+"-owner" );
|
||||
const OfferedEdit = document.getElementById(id0+"-offered");
|
||||
const ActionButton = document.getElementById(id0+"-action" );
|
||||
OwnerEdit .addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
||||
OfferedEdit.addEventListener('input', () => { ActionButton.textContent = "Update"; ooEdits[id0]=true; console.log("Changed OfferedEdit:",OfferedEdit.value); });
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user