fix vulnerabilities
This commit is contained in:
+60
-36
@@ -7,6 +7,7 @@ const swaggerDocument = require("./swagger.json");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
app.use(express.static(path.join(__dirname, "public")));
|
app.use(express.static(path.join(__dirname, "public")));
|
||||||
const fs = require("fs/promises");
|
const fs = require("fs/promises");
|
||||||
|
const crypto = require("crypto");
|
||||||
const dotenv = require("dotenv").config();
|
const dotenv = require("dotenv").config();
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
const cookieParser = require("cookie-parser");
|
const cookieParser = require("cookie-parser");
|
||||||
@@ -17,6 +18,12 @@ const port = process.env.PORT || 25566;
|
|||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const wss = new WebSocketServer({server});
|
const wss = new WebSocketServer({server});
|
||||||
let db;
|
let db;
|
||||||
|
const authCookieOptions = {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production",
|
||||||
|
sameSite: "Strict",
|
||||||
|
path: "/"
|
||||||
|
};
|
||||||
|
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use("/swagger-ui", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
app.use("/swagger-ui", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
|
||||||
@@ -119,6 +126,18 @@ function generateAccessToken(user) {
|
|||||||
return jwt.sign(user, process.env.TOKEN_SECRET, {expiresIn: "1800s"});
|
return jwt.sign(user, process.env.TOKEN_SECRET, {expiresIn: "1800s"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setAuthCookie(response, token) {
|
||||||
|
response.cookie("authorization", token, authCookieOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUsername(username) {
|
||||||
|
return typeof username === "string" ? username.trim().toLowerCase() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidPassword(password) {
|
||||||
|
return typeof password === "string" && password.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function parseCookieHeader(cookieHeader = "") {
|
function parseCookieHeader(cookieHeader = "") {
|
||||||
return cookieHeader.split(";").reduce((cookies, cookie) => {
|
return cookieHeader.split(";").reduce((cookies, cookie) => {
|
||||||
const separatorIndex = cookie.indexOf("=");
|
const separatorIndex = cookie.indexOf("=");
|
||||||
@@ -166,15 +185,16 @@ async function authenticateToken(request, response, next) {
|
|||||||
|
|
||||||
app.post("/register", async (request, response) => {
|
app.post("/register", async (request, response) => {
|
||||||
let {username, password} = request.body;
|
let {username, password} = request.body;
|
||||||
if (!username || !password)
|
username = normalizeUsername(username);
|
||||||
|
if (!username || !isValidPassword(password))
|
||||||
return response.status(400).json({error: "Username and password are required"});
|
return response.status(400).json({error: "Username and password are required"});
|
||||||
username = username.toLowerCase();
|
|
||||||
const existingUser = await findUserByUsername(username);
|
const existingUser = await findUserByUsername(username);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return response.status(400).json({error: "Username already exists"});
|
return response.status(400).json({error: "Username already exists"});
|
||||||
}
|
}
|
||||||
const token = generateAccessToken({username});
|
const token = generateAccessToken({username});
|
||||||
await saveUser({username, password, token, money: 200});
|
await saveUser({username, password, token, money: 200});
|
||||||
|
setAuthCookie(response, token);
|
||||||
response.json({username, token});
|
response.json({username, token});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,9 +224,9 @@ app.get("/login", (request, response) => {
|
|||||||
|
|
||||||
app.post("/login", async (request, response) => {
|
app.post("/login", async (request, response) => {
|
||||||
let {username, password} = request.body;
|
let {username, password} = request.body;
|
||||||
if (!username || !password)
|
username = normalizeUsername(username);
|
||||||
|
if (!username || !isValidPassword(password))
|
||||||
return response.status(400).json({error: "Username and password are required"});
|
return response.status(400).json({error: "Username and password are required"});
|
||||||
username = username.toLowerCase();
|
|
||||||
const user = await findUserByUsername(username);
|
const user = await findUserByUsername(username);
|
||||||
if (!user) return response.status(401).json({error: "Invalid credentials"});
|
if (!user) return response.status(401).json({error: "Invalid credentials"});
|
||||||
const passwordMatches = await bcrypt.compare(password, user.passwordHash);
|
const passwordMatches = await bcrypt.compare(password, user.passwordHash);
|
||||||
@@ -215,6 +235,7 @@ app.post("/login", async (request, response) => {
|
|||||||
}
|
}
|
||||||
const token = generateAccessToken({username});
|
const token = generateAccessToken({username});
|
||||||
await setUserToken(username, token);
|
await setUserToken(username, token);
|
||||||
|
setAuthCookie(response, token);
|
||||||
response.json({username, token});
|
response.json({username, token});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,7 +263,7 @@ app.get("/balance", authenticateToken, async (request, response) => {
|
|||||||
if (!me) {
|
if (!me) {
|
||||||
return response.status(404).json({error: "User not found"});
|
return response.status(404).json({error: "User not found"});
|
||||||
}
|
}
|
||||||
response.json({balance: me.money}).status(200);
|
response.status(200).json({balance: me.money});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/verify", authenticateToken, (request, response) => {
|
app.post("/verify", authenticateToken, (request, response) => {
|
||||||
@@ -256,11 +277,7 @@ app.delete("/logout", authenticateToken, async (request, response) => {
|
|||||||
const lobbyId = user.lobbyId;
|
const lobbyId = user.lobbyId;
|
||||||
await leavePokerLobby(lobbyId, username);
|
await leavePokerLobby(lobbyId, username);
|
||||||
await setUserToken(username, null);
|
await setUserToken(username, null);
|
||||||
response.clearCookie("authorization", {
|
response.clearCookie("authorization", authCookieOptions);
|
||||||
httpOnly: true,
|
|
||||||
secure: process.env.NODE_ENV === "production",
|
|
||||||
sameSite: "Strict"
|
|
||||||
});
|
|
||||||
response.json({message: "Logged out successfully."});
|
response.json({message: "Logged out successfully."});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -782,6 +799,10 @@ async function startShowdown(game) {
|
|||||||
result: evaluateBestHand([...game.hands[player], ...game.communityCards])
|
result: evaluateBestHand([...game.hands[player], ...game.communityCards])
|
||||||
}));
|
}));
|
||||||
const rankedResults = [...evaluations].sort((a, b) => compareScores(b.result.score, a.result.score));
|
const rankedResults = [...evaluations].sort((a, b) => compareScores(b.result.score, a.result.score));
|
||||||
|
const bestScore = rankedResults[0]?.result.score;
|
||||||
|
const winners = bestScore
|
||||||
|
? rankedResults.filter(entry => compareScores(entry.result.score, bestScore) === 0)
|
||||||
|
: [];
|
||||||
const bettorIndex = game.lastBettor ? game.players.indexOf(game.lastBettor) : 0;
|
const bettorIndex = game.lastBettor ? game.players.indexOf(game.lastBettor) : 0;
|
||||||
const startIndex = bettorIndex >= 0 ? bettorIndex : 0;
|
const startIndex = bettorIndex >= 0 ? bettorIndex : 0;
|
||||||
const revealOrder = [...evaluations].sort((a, b) => {
|
const revealOrder = [...evaluations].sort((a, b) => {
|
||||||
@@ -794,13 +815,19 @@ async function startShowdown(game) {
|
|||||||
game.phase = "showdown";
|
game.phase = "showdown";
|
||||||
game.currentPlayerIndex = -1;
|
game.currentPlayerIndex = -1;
|
||||||
game.showdown = revealOrder;
|
game.showdown = revealOrder;
|
||||||
game.winner = rankedResults[0]?.player || null;
|
game.winner = winners.length ? winners.map(entry => entry.player).join(", ") : null;
|
||||||
if (game.winner && !game.potAwarded) {
|
if (winners.length && !game.potAwarded) {
|
||||||
game.stacks[game.winner] += game.pot;
|
const baseShare = Math.floor(game.pot / winners.length);
|
||||||
|
let remainder = game.pot % winners.length;
|
||||||
|
for (const entry of winners) {
|
||||||
|
const extraChip = remainder > 0 ? 1 : 0;
|
||||||
|
game.stacks[entry.player] += baseShare + extraChip;
|
||||||
|
remainder -= extraChip;
|
||||||
|
}
|
||||||
game.potAwarded = true;
|
game.potAwarded = true;
|
||||||
}
|
}
|
||||||
game.lastAction = game.winner
|
game.lastAction = game.winner
|
||||||
? `Showdown: ${game.winner} wins $${game.pot} with ${rankedResults[0].result.name}`
|
? `Showdown: ${game.winner} ${winners.length === 1 ? "wins" : "split"} $${game.pot} with ${rankedResults[0].result.name}`
|
||||||
: "Showdown ended";
|
: "Showdown ended";
|
||||||
await persistPokerStacks(game);
|
await persistPokerStacks(game);
|
||||||
await scheduleNextRound(game);
|
await scheduleNextRound(game);
|
||||||
@@ -1103,36 +1130,35 @@ function broadcastToLobby(lobbyId, payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createLobby() {
|
async function createLobby() {
|
||||||
let response = await fetch("https://www.deckofcardsapi.com/api/deck/new/shuffle/?deck_count=1")
|
let lobbyId;
|
||||||
let data = await response.json();
|
do {
|
||||||
let lobbyId = data.deck_id
|
lobbyId = crypto.randomBytes(4).toString("hex");
|
||||||
await fetch(`https://www.deckofcardsapi.com/api/deck/${lobbyId}/pile/players/add/?cards=`)
|
} while (playerMap[lobbyId]);
|
||||||
return lobbyId;
|
return lobbyId;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post("/leave-lobby", (req, res) => {
|
app.post("/leave-lobby", authenticateToken, async (req, res) => {
|
||||||
const {lobbyId, username} = req.body;
|
const {lobbyId} = req.body;
|
||||||
|
const username = req.user.username;
|
||||||
if (!lobbyId || !username) {
|
if (!lobbyId || !username) {
|
||||||
return res.status(400).json({error: "Lobby ID und Username sind erforderlich!"});
|
return res.status(400).json({error: "Lobby ID und Username sind erforderlich!"});
|
||||||
}
|
}
|
||||||
if (!playerMap[lobbyId]) {
|
if (!playerMap[lobbyId]) {
|
||||||
return res.status(404).json({error: "Lobby nicht gefunden!"});
|
return res.status(404).json({error: "Lobby nicht gefunden!"});
|
||||||
}
|
}
|
||||||
playerMap[lobbyId] = playerMap[lobbyId].filter(player => player !== username);
|
if (await leavePokerLobby(lobbyId, username)) {
|
||||||
if (playerMap[lobbyId].length === 0) {
|
return res.json({message: `Lobby ${lobbyId} aktualisiert.`});
|
||||||
delete playerMap[lobbyId];
|
|
||||||
delete lobbyPrivacy[lobbyId];
|
|
||||||
console.log(`Leere Lobby ${lobbyId} wurde gelöscht.`);
|
|
||||||
}
|
}
|
||||||
res.json({message: `Lobby ${lobbyId} aktualisiert.`});
|
return res.status(404).json({error: "User not in lobby"});
|
||||||
});
|
});
|
||||||
setInterval(() => removeEmptyLobbies(playerMap), 10000);
|
setInterval(() => removeEmptyLobbies(playerMap), 10000);
|
||||||
|
|
||||||
function removeEmptyLobbies(playerMap) {
|
function removeEmptyLobbies(playerMap) {
|
||||||
for (const lobby in playerMap) {
|
for (const lobby in playerMap) {
|
||||||
if (playerMap[lobby].length === 0) {
|
if (playerMap[lobby].length === 0) {
|
||||||
delete playerMap[lobby];
|
deleteLobby(lobby).catch(error => {
|
||||||
delete lobbyPrivacy[lobby];
|
console.error("Failed to delete empty lobby:", error);
|
||||||
|
});
|
||||||
console.log(`Lobby ${lobby} wurde gelöscht.`);
|
console.log(`Lobby ${lobby} wurde gelöscht.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1167,8 +1193,12 @@ async function deleteLobby(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function joinLobby(lobbyId, player_name) {
|
async function joinLobby(lobbyId, playerName) {
|
||||||
let response = await fetch(`https://www.deckofcardsapi.com/api/deck/${lobbyId}/pile/${player_name}/add/?cards=`)
|
if (!lobbyId || !playerName || !playerMap[lobbyId]) return false;
|
||||||
|
if (!playerMap[lobbyId].includes(playerName)) {
|
||||||
|
playerMap[lobbyId].push(playerName);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.get("/lobby/create", authenticateToken, async (req, res) => {
|
app.get("/lobby/create", authenticateToken, async (req, res) => {
|
||||||
@@ -1199,9 +1229,6 @@ app.get("/lobby/join", authenticateToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
if (lobbyId in playerMap) {
|
if (lobbyId in playerMap) {
|
||||||
await joinLobby(lobbyId, user.username);
|
await joinLobby(lobbyId, user.username);
|
||||||
if (!playerMap[lobbyId].includes(username)) {
|
|
||||||
playerMap[lobbyId].push(username);
|
|
||||||
}
|
|
||||||
await ensurePlayerInPokerGame(lobbyId, username);
|
await ensurePlayerInPokerGame(lobbyId, username);
|
||||||
await maybeStartWaitingGame(lobbyId);
|
await maybeStartWaitingGame(lobbyId);
|
||||||
broadcastLobby(lobbyId);
|
broadcastLobby(lobbyId);
|
||||||
@@ -1295,9 +1322,6 @@ wss.on("connection", async (socket, request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await joinLobby(lobbyId, user.username);
|
await joinLobby(lobbyId, user.username);
|
||||||
if (!playerMap[lobbyId].includes(user.username)) {
|
|
||||||
playerMap[lobbyId].push(user.username);
|
|
||||||
}
|
|
||||||
await ensurePlayerInPokerGame(lobbyId, user.username);
|
await ensurePlayerInPokerGame(lobbyId, user.username);
|
||||||
await maybeStartWaitingGame(lobbyId);
|
await maybeStartWaitingGame(lobbyId);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "swagger.js",
|
"main": "swagger.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "node --check gambling.js && node --check public/frontend_home.js && node --check public/frontend_login.js && node --check public/frontend_poker.js && node --check swagger.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -14,8 +14,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (loginRes.ok) {
|
if (loginRes.ok) {
|
||||||
const loginData = await loginRes.json();
|
await loginRes.json();
|
||||||
document.cookie = `authorization=${loginData.token}`;
|
|
||||||
showLoadingScreen("Login successful. Loading Lobby...");
|
showLoadingScreen("Login successful. Loading Lobby...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -27,8 +26,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (registerRes.ok) {
|
if (registerRes.ok) {
|
||||||
const registerData = await registerRes.json();
|
await registerRes.json();
|
||||||
document.cookie = `authorization=${registerData.token}`;
|
|
||||||
showLoadingScreen("Register successful. Loading Lobby...");
|
showLoadingScreen("Register successful. Loading Lobby...");
|
||||||
} else {
|
} else {
|
||||||
const error = await registerRes.json();
|
const error = await registerRes.json();
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ const doc = {
|
|||||||
title: 'GAMBLE!',
|
title: 'GAMBLE!',
|
||||||
description: 'Gambling API'
|
description: 'Gambling API'
|
||||||
},
|
},
|
||||||
host: 'localhost:25565',
|
host: 'localhost:25566',
|
||||||
components: {
|
components: {
|
||||||
securitySchemes:{
|
securitySchemes:{
|
||||||
bearerAuth: {
|
bearerAuth: {
|
||||||
|
|||||||
Reference in New Issue
Block a user