Skip to content

Commit a0f00bb

Browse files
committed
Add basic card game
1 parent 84b0c39 commit a0f00bb

File tree

4 files changed

+704
-2
lines changed

4 files changed

+704
-2
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
SESSION_SECRET=random_string
2-
DICEGAME_FLAG=your_flag
2+
DICEGAME_FLAG=your_flag
3+
CARDGAME_FLAG=your_flag

cardgame.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
require('dotenv').config();
2+
const express = require('express');
3+
const session = require('express-session');
4+
const WebSocket = require('ws');
5+
const http = require('http');
6+
const crypto = require('crypto');
7+
const app = express();
8+
const server = http.createServer(app);
9+
const wss = new WebSocket.Server({ server });
10+
11+
app.use(express.json());
12+
app.use(express.static('public'));
13+
14+
// Session configuration
15+
const sessionParser = session({
16+
secret: process.env.SESSION_SECRET,
17+
resave: false,
18+
saveUninitialized: true,
19+
cookie: { secure: false }
20+
});
21+
22+
app.use(sessionParser);
23+
24+
// In-memory storage (intentionally vulnerable)
25+
const users = new Map(); // username -> {password, cards}
26+
const activeConnections = new Map(); // username -> WebSocket
27+
28+
// Card generation utilities
29+
function generateUniqueCard(username) {
30+
const id = `unique_${username}_${crypto.randomBytes(8).toString('hex')}`;
31+
return {
32+
id,
33+
name: `${username}'s Identity Card`,
34+
type: 'IDENTITY',
35+
description: 'A unique card that proves your identity. There should only ever be ONE of these for your account.',
36+
rarity: 'UNIQUE',
37+
soulbound: true,
38+
created_at: Date.now()
39+
};
40+
}
41+
42+
function generateStarterCards() {
43+
return [
44+
{ id: crypto.randomBytes(8).toString('hex'), name: 'Basic Sword', type: 'WEAPON', rarity: 'COMMON' },
45+
{ id: crypto.randomBytes(8).toString('hex'), name: 'Basic Shield', type: 'ARMOR', rarity: 'COMMON' },
46+
{ id: crypto.randomBytes(8).toString('hex'), name: 'Health Potion', type: 'CONSUMABLE', rarity: 'COMMON' }
47+
];
48+
}
49+
50+
// Check for duplicated unique cards
51+
function checkForDuplication(username) {
52+
const playerCards = users.get(username).cards;
53+
const identityCards = playerCards.filter(card => card.type === 'IDENTITY');
54+
if (identityCards.length > 1) {
55+
const uniqueIds = new Set(identityCards.map(card => card.id));
56+
if (uniqueIds.size < identityCards.length) {
57+
return process.env.CARDGAME_FLAG;
58+
}
59+
}
60+
return null;
61+
}
62+
63+
// Broadcast active players to all connected clients
64+
function broadcastActivePlayers() {
65+
const activePlayers = Array.from(activeConnections.keys());
66+
const message = JSON.stringify({
67+
type: 'activePlayers',
68+
players: activePlayers
69+
});
70+
71+
activeConnections.forEach(ws => {
72+
if (ws.readyState === WebSocket.OPEN) {
73+
ws.send(message);
74+
}
75+
});
76+
}
77+
78+
// Authentication endpoints
79+
app.post('/register', (req, res) => {
80+
const { username, password } = req.body;
81+
if (!username || !password) {
82+
return res.status(400).json({ error: 'Username and password required' });
83+
}
84+
85+
if (users.has(username)) {
86+
return res.status(400).json({ error: 'Username already taken' });
87+
}
88+
89+
const cards = [
90+
generateUniqueCard(username),
91+
...generateStarterCards()
92+
];
93+
94+
users.set(username, { password, cards });
95+
req.session.username = username;
96+
97+
res.json({ success: true });
98+
});
99+
100+
app.post('/login', (req, res) => {
101+
const { username, password } = req.body;
102+
const user = users.get(username);
103+
104+
if (!user || user.password !== password) {
105+
return res.status(401).json({ error: 'Invalid credentials' });
106+
}
107+
108+
req.session.username = username;
109+
res.json({ success: true });
110+
});
111+
112+
app.post('/logout', (req, res) => {
113+
const ws = activeConnections.get(req.session.username);
114+
if (ws) {
115+
ws.close();
116+
activeConnections.delete(req.session.username);
117+
}
118+
119+
req.session.destroy();
120+
res.json({ success: true });
121+
});
122+
123+
// Game endpoints
124+
app.get('/inventory', (req, res) => {
125+
if (!req.session.username) {
126+
return res.status(401).json({ error: 'Not authenticated' });
127+
}
128+
129+
const user = users.get(req.session.username);
130+
res.json({ cards: user.cards });
131+
});
132+
133+
// WebSocket handling
134+
wss.on('connection', (ws, req) => {
135+
// Ensure the connection is authenticated
136+
sessionParser(req, {}, () => {
137+
const username = req.session.username;
138+
if (!username) {
139+
ws.close();
140+
return;
141+
}
142+
143+
activeConnections.set(username, ws);
144+
broadcastActivePlayers();
145+
146+
// Handle incoming WebSocket messages
147+
ws.on('message', (message) => {
148+
const data = JSON.parse(message);
149+
150+
if (data.type === 'giftCards') {
151+
handleGiftCards(username, data, ws);
152+
}
153+
});
154+
155+
ws.on('close', () => {
156+
activeConnections.delete(username);
157+
broadcastActivePlayers();
158+
});
159+
});
160+
});
161+
162+
function handleGiftCards(username, data, ws) {
163+
const { toUser, cardIds } = data;
164+
165+
// Process the gift (intentionally vulnerable)
166+
const fromUser = users.get(username);
167+
const toUserData = users.get(toUser);
168+
169+
// Get the full card details
170+
const giftedCards = cardIds
171+
.map(cardId => fromUser.cards.find(card => card.id === cardId))
172+
.filter(card => card);
173+
174+
// Remove cards from sender
175+
giftedCards.forEach(card => {
176+
fromUser.cards = fromUser.cards.filter(c => c.id !== card.id);
177+
});
178+
179+
// Add cards to receiver
180+
toUserData.cards.push(...giftedCards);
181+
182+
// Check for duplication
183+
const fromFlag = checkForDuplication(username);
184+
const toFlag = checkForDuplication(toUser);
185+
186+
// Notify both parties
187+
const fromWs = activeConnections.get(username);
188+
const toWs = activeConnections.get(toUser);
189+
190+
if (fromWs && fromWs.readyState === WebSocket.OPEN) {
191+
fromWs.send(JSON.stringify({
192+
type: 'giftComplete',
193+
inventory: fromUser.cards,
194+
flag: fromFlag
195+
}));
196+
}
197+
198+
if (toWs && toWs.readyState === WebSocket.OPEN) {
199+
toWs.send(JSON.stringify({
200+
type: 'giftReceived',
201+
from: username,
202+
cards: giftedCards,
203+
inventory: toUserData.cards,
204+
flag: toFlag
205+
}));
206+
}
207+
}
208+
209+
const PORT = 3001;
210+
server.listen(PORT, () => {
211+
console.log(`Card Game Server running on port ${PORT}`);
212+
});

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dependencies": {
1313
"dotenv": "^16.4.7",
1414
"express": "^4.21.2",
15-
"express-session": "^1.18.1"
15+
"express-session": "^1.18.1",
16+
"ws": "^8.18.1"
1617
}
1718
}

0 commit comments

Comments
 (0)