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+ } ) ;
0 commit comments