dubs-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// PM2 Ecosystem Configuration
|
|
2
|
+
// This ensures both API server and keeper bot run reliably
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
apps: [
|
|
6
|
+
{
|
|
7
|
+
name: 'dubs-api',
|
|
8
|
+
script: './server.js',
|
|
9
|
+
instances: 1,
|
|
10
|
+
autorestart: true,
|
|
11
|
+
watch: false,
|
|
12
|
+
max_memory_restart: '500M',
|
|
13
|
+
env: {
|
|
14
|
+
NODE_ENV: 'development',
|
|
15
|
+
SOLANA_NETWORK: 'https://api.devnet.solana.com',
|
|
16
|
+
PORT: 3001,
|
|
17
|
+
},
|
|
18
|
+
env_production: {
|
|
19
|
+
NODE_ENV: 'production',
|
|
20
|
+
SOLANA_NETWORK: 'https://api.mainnet-beta.solana.com',
|
|
21
|
+
PORT: 3001,
|
|
22
|
+
},
|
|
23
|
+
error_file: './logs/api-error.log',
|
|
24
|
+
out_file: './logs/api-out.log',
|
|
25
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'jackpot-keeper',
|
|
29
|
+
script: './scripts/jackpot/keeper.js',
|
|
30
|
+
instances: 1,
|
|
31
|
+
autorestart: true,
|
|
32
|
+
watch: false,
|
|
33
|
+
max_memory_restart: '300M',
|
|
34
|
+
env: {
|
|
35
|
+
NODE_ENV: 'development',
|
|
36
|
+
},
|
|
37
|
+
restart_delay: 5000, // Wait 5 seconds between restarts
|
|
38
|
+
max_restarts: 10, // Max 10 restarts in...
|
|
39
|
+
min_uptime: '10s', // ...10 seconds (prevents crash loop)
|
|
40
|
+
error_file: './logs/keeper-error.log',
|
|
41
|
+
out_file: './logs/keeper-out.log',
|
|
42
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'sports-oracle',
|
|
46
|
+
script: './cron/oracleMonitor.js',
|
|
47
|
+
instances: 1,
|
|
48
|
+
autorestart: true,
|
|
49
|
+
watch: false,
|
|
50
|
+
max_memory_restart: '300M',
|
|
51
|
+
env: {
|
|
52
|
+
NODE_ENV: 'development',
|
|
53
|
+
ORACLE_CHECK_INTERVAL: '60000', // Check every 60 seconds
|
|
54
|
+
NOTIFY_BEFORE_MINUTES: '10', // Notify 10 minutes before game starts
|
|
55
|
+
},
|
|
56
|
+
restart_delay: 5000,
|
|
57
|
+
max_restarts: 10,
|
|
58
|
+
min_uptime: '10s',
|
|
59
|
+
error_file: './logs/oracle-error.log',
|
|
60
|
+
out_file: './logs/oracle-out.log',
|
|
61
|
+
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
|
package/env.template
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Dubs Server Environment Variables
|
|
2
|
+
# Copy this to .env and update values
|
|
3
|
+
|
|
4
|
+
# Solana Configuration
|
|
5
|
+
SOLANA_NETWORK=http://127.0.0.1:8899
|
|
6
|
+
# For devnet: https://api.devnet.solana.com
|
|
7
|
+
# For mainnet: https://api.mainnet-beta.solana.com
|
|
8
|
+
|
|
9
|
+
PROGRAM_ID=8DJTkgk6MDr6tPtw4v2VzYAz9WWvmCg6786vZrEK3o5q
|
|
10
|
+
|
|
11
|
+
# Oracle Configuration (for Automatic Sports Mode)
|
|
12
|
+
ORACLE_WALLET_PATH=./wallets/oracle.json
|
|
13
|
+
ORACLE_CHECK_INTERVAL=60000
|
|
14
|
+
# Check interval in milliseconds (60000 = 1 minute, 300000 = 5 minutes)
|
|
15
|
+
|
|
16
|
+
NOTIFY_BEFORE_MINUTES=10
|
|
17
|
+
# Send "game starting soon" notification X minutes before lock time (default: 10)
|
|
18
|
+
|
|
19
|
+
# Telegram Bot Configuration (for notifications)
|
|
20
|
+
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
|
|
21
|
+
# Get this from @BotFather on Telegram
|
|
22
|
+
|
|
23
|
+
TELEGRAM_BOT_URL=https://your-bot-url.herokuapp.com
|
|
24
|
+
# URL to your deployed Telegram bot (for sending private chat notifications)
|
|
25
|
+
# Example: https://dubs-telegram-bot-xxxxx.herokuapp.com
|
|
26
|
+
|
|
27
|
+
# API URLs
|
|
28
|
+
LIVE_SCORES_API_URL=http://localhost:3000
|
|
29
|
+
# Your dubs-api endpoint with /api/livescores/:league
|
|
30
|
+
|
|
31
|
+
DUBS_GAMES_API_URL=http://localhost:3001
|
|
32
|
+
# Your dubs-games-api endpoint (Firebase database)
|
|
33
|
+
|
|
34
|
+
DUBS_SERVER_URL=http://localhost:3001
|
|
35
|
+
# Your dubs-server endpoint (PostgreSQL database)
|
|
36
|
+
# For production (Heroku): https://dubs-server-production.herokuapp.com
|
|
37
|
+
# For local dev: http://localhost:3001 (should match PORT above)
|
|
38
|
+
|
|
39
|
+
# Server Configuration
|
|
40
|
+
PORT=3001
|
|
41
|
+
NODE_ENV=development
|
|
42
|
+
|
|
43
|
+
# Database Configuration (PostgreSQL)
|
|
44
|
+
DATABASE_URL=postgresql://username:password@localhost:5432/dubs_db
|
|
45
|
+
# For production (Heroku adds this automatically)
|
|
46
|
+
# For local dev: postgresql://postgres:password@localhost:5432/dubs_db
|
|
47
|
+
|
|
48
|
+
# Redis Configuration (for high-performance caching)
|
|
49
|
+
REDIS_URL=redis://localhost:6379
|
|
50
|
+
# For Heroku: Add Heroku Redis addon (automatically sets REDIS_URL)
|
|
51
|
+
# For local dev: redis://localhost:6379
|
|
52
|
+
# Optional: If not set, server runs in PostgreSQL-only mode (slightly slower)
|
|
53
|
+
|
|
54
|
+
# Authentication & Security
|
|
55
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
|
56
|
+
# CRITICAL: Generate a secure random string for production!
|
|
57
|
+
# Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
|
58
|
+
|
|
59
|
+
JWT_EXPIRES_IN=7d
|
|
60
|
+
# Token expiration time (e.g., 7d, 24h, 30m)
|
|
61
|
+
|
|
62
|
+
# AWS S3 Configuration (for avatar uploads)
|
|
63
|
+
AWS_REGION=us-east-1
|
|
64
|
+
AWS_ACCESS_KEY_ID=your_aws_access_key
|
|
65
|
+
AWS_SECRET_ACCESS_KEY=your_aws_secret_key
|
|
66
|
+
AWS_S3_BUCKET_NAME=dubs-avatars
|
|
67
|
+
|
|
68
|
+
# Exchange Rate API Configuration
|
|
69
|
+
EXCHANGE_API_KEY=757419e9be20039acaf308a9
|
|
70
|
+
# API key for exchangerate-api.com (v6)
|
|
71
|
+
# Sign up at: https://www.exchangerate-api.com
|
|
72
|
+
|
|
73
|
+
EXCHANGE_API_BASE_URL=https://v6.exchangerate-api.com/v6
|
|
74
|
+
# Base URL for exchange rate API
|
|
75
|
+
|
|
76
|
+
EXCHANGE_CACHE_TTL=300
|
|
77
|
+
# Cache time-to-live in seconds (300 = 5 minutes)
|
|
78
|
+
|
|
79
|
+
BASE_CURRENCY=USD
|
|
80
|
+
# Default base currency for exchange rates
|
|
81
|
+
|
|
82
|
+
SUPPORTED_CURRENCIES=USD,EUR,CAD,GBP,JPY,AUD,CHF,CNY,SEK,NZD
|
|
83
|
+
# Comma-separated list of supported currencies
|
|
84
|
+
|
|
85
|
+
# PandaScore API Configuration (Esports)
|
|
86
|
+
PANDASCORE_API_KEY=your_pandascore_api_key_here
|
|
87
|
+
# Get a free API key at: https://pandascore.co
|
|
88
|
+
|
|
89
|
+
# Crypto Price API Configuration (CoinGecko)
|
|
90
|
+
CRYPTO_PRICE_CACHE_TTL=300
|
|
91
|
+
# Cache time-to-live in seconds (300 = 5 minutes, matches exchange rates)
|
|
92
|
+
|
|
93
|
+
# ============================================
|
|
94
|
+
# 🎰 JACKPOT CONFIGURATION
|
|
95
|
+
# ============================================
|
|
96
|
+
|
|
97
|
+
# Jackpot Program ID (Solana on-chain program)
|
|
98
|
+
JACKPOT_PROGRAM_ID=BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz
|
|
99
|
+
# Devnet: BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz
|
|
100
|
+
# Mainnet: <DEPLOY AND UPDATE>
|
|
101
|
+
|
|
102
|
+
# Jackpot Oracle Wallet (public key)
|
|
103
|
+
JACKPOT_ORACLE_WALLET=FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa
|
|
104
|
+
# This wallet submits randomness for provably fair rounds
|
|
105
|
+
|
|
106
|
+
# Keeper Private Key (JSON array format)
|
|
107
|
+
# CRITICAL: Get from oracle.json wallet file
|
|
108
|
+
# Format: [1,2,3,...,64] (64 byte array as JSON)
|
|
109
|
+
KEEPER_PRIVATE_KEY=
|
|
110
|
+
# To export: cat wallets/oracle.json
|
|
111
|
+
|
|
112
|
+
# Keeper API Base URL
|
|
113
|
+
API_BASE_URL=http://localhost:3001
|
|
114
|
+
# For Heroku devnet: https://dubs-server-dev-xxxxx.herokuapp.com
|
|
115
|
+
# For Heroku mainnet: https://dubs-server-prod-xxxxx.herokuapp.com
|
|
116
|
+
|
|
117
|
+
# ============================================
|
|
118
|
+
# 🎨 MATCHUP IMAGE GENERATION
|
|
119
|
+
# ============================================
|
|
120
|
+
|
|
121
|
+
# Base URL for fetching team logos (for matchup image generation)
|
|
122
|
+
MATCHUP_LOGO_BASE_URL=http://localhost:3000
|
|
123
|
+
# For local dev: http://localhost:3000 (your SPA's public folder)
|
|
124
|
+
# For production: https://dubs.app (or wherever your SPA is hosted)
|
|
125
|
+
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔑 API Key Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Validates developer API keys from the X-API-Key header.
|
|
5
|
+
* Hashes the key and looks up in developer_api_keys table.
|
|
6
|
+
* Attaches developer app info to req.developerApp on success.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
const { pool } = require('../services/db');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hash an API key for lookup (same SHA-256 used at key creation)
|
|
14
|
+
*/
|
|
15
|
+
function hashApiKey(key) {
|
|
16
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Middleware: Require valid API key in X-API-Key header
|
|
21
|
+
* Attaches req.developerApp with { appId, developerId, commissionWallet, environment, appName }
|
|
22
|
+
*/
|
|
23
|
+
async function apiKeyAuth(req, res, next) {
|
|
24
|
+
const apiKey = req.headers['x-api-key'];
|
|
25
|
+
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
return res.status(401).json({
|
|
28
|
+
success: false,
|
|
29
|
+
error: 'Missing API key — set X-API-Key header',
|
|
30
|
+
code: 'NO_API_KEY'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate prefix format
|
|
35
|
+
if (!apiKey.startsWith('dubs_test_') && !apiKey.startsWith('dubs_live_')) {
|
|
36
|
+
return res.status(401).json({
|
|
37
|
+
success: false,
|
|
38
|
+
error: 'Invalid API key format',
|
|
39
|
+
code: 'INVALID_KEY_FORMAT'
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const keyHash = hashApiKey(apiKey);
|
|
45
|
+
|
|
46
|
+
const result = await pool.query(`
|
|
47
|
+
SELECT
|
|
48
|
+
k.id as key_id,
|
|
49
|
+
k.app_id,
|
|
50
|
+
k.environment,
|
|
51
|
+
k.is_active,
|
|
52
|
+
a.developer_id,
|
|
53
|
+
a.app_name,
|
|
54
|
+
a.status as app_status,
|
|
55
|
+
a.network_mode,
|
|
56
|
+
d.commission_wallet,
|
|
57
|
+
d.wallet_address as developer_wallet
|
|
58
|
+
FROM developer_api_keys k
|
|
59
|
+
JOIN developer_apps a ON k.app_id = a.id
|
|
60
|
+
JOIN developer_accounts d ON a.developer_id = d.id
|
|
61
|
+
WHERE k.key_hash = $1
|
|
62
|
+
`, [keyHash]);
|
|
63
|
+
|
|
64
|
+
if (result.rows.length === 0) {
|
|
65
|
+
return res.status(401).json({
|
|
66
|
+
success: false,
|
|
67
|
+
error: 'Invalid API key',
|
|
68
|
+
code: 'INVALID_API_KEY'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const row = result.rows[0];
|
|
73
|
+
|
|
74
|
+
if (!row.is_active) {
|
|
75
|
+
return res.status(401).json({
|
|
76
|
+
success: false,
|
|
77
|
+
error: 'API key has been revoked',
|
|
78
|
+
code: 'KEY_REVOKED'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (row.app_status !== 'active') {
|
|
83
|
+
return res.status(403).json({
|
|
84
|
+
success: false,
|
|
85
|
+
error: `App is ${row.app_status}`,
|
|
86
|
+
code: 'APP_INACTIVE'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Attach developer context to request
|
|
91
|
+
req.developerApp = {
|
|
92
|
+
keyId: row.key_id,
|
|
93
|
+
appId: row.app_id,
|
|
94
|
+
developerId: row.developer_id,
|
|
95
|
+
commissionWallet: row.commission_wallet,
|
|
96
|
+
developerWallet: row.developer_wallet,
|
|
97
|
+
environment: row.environment,
|
|
98
|
+
appName: row.app_name,
|
|
99
|
+
networkMode: row.network_mode,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Update last_used_at (fire-and-forget, don't block the request)
|
|
103
|
+
pool.query(
|
|
104
|
+
'UPDATE developer_api_keys SET last_used_at = NOW() WHERE id = $1',
|
|
105
|
+
[row.key_id]
|
|
106
|
+
).catch(() => {});
|
|
107
|
+
|
|
108
|
+
next();
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('[ApiKeyAuth] Error:', error.message);
|
|
111
|
+
return res.status(500).json({
|
|
112
|
+
success: false,
|
|
113
|
+
error: 'Authentication error',
|
|
114
|
+
code: 'AUTH_ERROR'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Log an API call for analytics (fire-and-forget)
|
|
121
|
+
*/
|
|
122
|
+
function logApiCall(req, statusCode, responseTimeMs) {
|
|
123
|
+
if (!req.developerApp) return;
|
|
124
|
+
|
|
125
|
+
pool.query(
|
|
126
|
+
`INSERT INTO developer_api_logs (app_id, endpoint, method, status_code, response_time_ms)
|
|
127
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
128
|
+
[req.developerApp.appId, req.originalUrl, req.method, statusCode, responseTimeMs]
|
|
129
|
+
).catch(() => {});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
apiKeyAuth,
|
|
134
|
+
hashApiKey,
|
|
135
|
+
logApiCall,
|
|
136
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔐 JWT Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Validates JWT tokens from cookies and attaches user info to requests
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const jwt = require('jsonwebtoken');
|
|
8
|
+
const { pool } = require('../services/db'); // Shared database pool
|
|
9
|
+
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
|
|
12
|
+
// JWT Secret - MUST be set in environment variables
|
|
13
|
+
const JWT_SECRET = process.env.JWT_SECRET || (() => {
|
|
14
|
+
console.warn('⚠️ WARNING: JWT_SECRET not set! Using default (INSECURE for production)');
|
|
15
|
+
return 'dev-secret-change-in-production';
|
|
16
|
+
})();
|
|
17
|
+
|
|
18
|
+
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate JWT token for a user
|
|
22
|
+
*/
|
|
23
|
+
function generateToken(walletAddress, userId) {
|
|
24
|
+
return jwt.sign(
|
|
25
|
+
{
|
|
26
|
+
walletAddress,
|
|
27
|
+
userId,
|
|
28
|
+
iat: Math.floor(Date.now() / 1000)
|
|
29
|
+
},
|
|
30
|
+
JWT_SECRET,
|
|
31
|
+
{ expiresIn: JWT_EXPIRES_IN }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Middleware: Authenticate user via JWT token (Authorization header)
|
|
37
|
+
* Attaches user info to req.user if valid
|
|
38
|
+
*/
|
|
39
|
+
async function authenticate(req, res, next) {
|
|
40
|
+
try {
|
|
41
|
+
// Get token from Authorization header
|
|
42
|
+
const authHeader = req.headers.authorization;
|
|
43
|
+
|
|
44
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
45
|
+
return res.status(401).json({
|
|
46
|
+
success: false,
|
|
47
|
+
error: 'Authentication required - Missing or invalid Authorization header',
|
|
48
|
+
code: 'NO_TOKEN'
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
53
|
+
|
|
54
|
+
// Verify token
|
|
55
|
+
let decoded;
|
|
56
|
+
try {
|
|
57
|
+
decoded = jwt.verify(token, JWT_SECRET);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err.name === 'TokenExpiredError') {
|
|
60
|
+
return res.status(401).json({
|
|
61
|
+
success: false,
|
|
62
|
+
error: 'Session expired',
|
|
63
|
+
code: 'TOKEN_EXPIRED'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return res.status(401).json({
|
|
67
|
+
success: false,
|
|
68
|
+
error: 'Invalid token',
|
|
69
|
+
code: 'INVALID_TOKEN'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check if session exists in database (optional but recommended)
|
|
74
|
+
const sessionCheck = await pool.query(
|
|
75
|
+
'SELECT * FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
|
|
76
|
+
[decoded.walletAddress, hashToken(token)]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (sessionCheck.rows.length === 0) {
|
|
80
|
+
return res.status(401).json({
|
|
81
|
+
success: false,
|
|
82
|
+
error: 'Session not found or expired',
|
|
83
|
+
code: 'SESSION_INVALID'
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Optional: Verify wallet address from header matches token
|
|
88
|
+
const walletHeader = req.headers['x-wallet-address'];
|
|
89
|
+
if (walletHeader && walletHeader !== decoded.walletAddress) {
|
|
90
|
+
return res.status(403).json({
|
|
91
|
+
success: false,
|
|
92
|
+
error: 'Wallet address mismatch',
|
|
93
|
+
code: 'WALLET_MISMATCH'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Attach user info to request
|
|
98
|
+
req.user = {
|
|
99
|
+
walletAddress: decoded.walletAddress,
|
|
100
|
+
userId: decoded.userId,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Update last activity
|
|
104
|
+
await pool.query(
|
|
105
|
+
'UPDATE user_sessions SET last_activity = NOW() WHERE wallet_address = $1 AND token_hash = $2',
|
|
106
|
+
[decoded.walletAddress, hashToken(token)]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
next();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error('[Auth] Middleware error:', error);
|
|
112
|
+
return res.status(500).json({
|
|
113
|
+
success: false,
|
|
114
|
+
error: 'Authentication error',
|
|
115
|
+
code: 'AUTH_ERROR'
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Optional middleware: Only check if authenticated but don't require it
|
|
122
|
+
* Useful for endpoints that behave differently for logged-in users
|
|
123
|
+
*/
|
|
124
|
+
async function optionalAuth(req, res, next) {
|
|
125
|
+
try {
|
|
126
|
+
const authHeader = req.headers.authorization;
|
|
127
|
+
|
|
128
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
129
|
+
req.user = null;
|
|
130
|
+
return next();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const token = authHeader.substring(7);
|
|
134
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
135
|
+
req.user = {
|
|
136
|
+
walletAddress: decoded.walletAddress,
|
|
137
|
+
userId: decoded.userId,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
next();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// If token is invalid, just continue without user
|
|
143
|
+
req.user = null;
|
|
144
|
+
next();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Hash token for storage (don't store raw tokens in database)
|
|
150
|
+
*/
|
|
151
|
+
function hashToken(token) {
|
|
152
|
+
const crypto = require('crypto');
|
|
153
|
+
return crypto.createHash('sha256').update(token).digest('hex');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Store session in database
|
|
158
|
+
*/
|
|
159
|
+
async function createSession(walletAddress, userId, token, expiresAt) {
|
|
160
|
+
const tokenHash = hashToken(token);
|
|
161
|
+
|
|
162
|
+
await pool.query(
|
|
163
|
+
`INSERT INTO user_sessions (wallet_address, user_id, token_hash, expires_at, created_at, last_activity)
|
|
164
|
+
VALUES ($1, $2, $3, $4, NOW(), NOW())`,
|
|
165
|
+
[walletAddress, userId, tokenHash, expiresAt]
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Delete session from database
|
|
171
|
+
*/
|
|
172
|
+
async function deleteSession(walletAddress, token) {
|
|
173
|
+
const tokenHash = hashToken(token);
|
|
174
|
+
|
|
175
|
+
await pool.query(
|
|
176
|
+
'DELETE FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2',
|
|
177
|
+
[walletAddress, tokenHash]
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Delete all sessions for a wallet (logout from all devices)
|
|
183
|
+
*/
|
|
184
|
+
async function deleteAllSessions(walletAddress) {
|
|
185
|
+
await pool.query(
|
|
186
|
+
'DELETE FROM user_sessions WHERE wallet_address = $1',
|
|
187
|
+
[walletAddress]
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Clean up expired sessions (call this periodically)
|
|
193
|
+
*/
|
|
194
|
+
async function cleanupExpiredSessions() {
|
|
195
|
+
const result = await pool.query(
|
|
196
|
+
'DELETE FROM user_sessions WHERE expires_at < NOW()'
|
|
197
|
+
);
|
|
198
|
+
return result.rowCount;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
authenticate,
|
|
203
|
+
optionalAuth,
|
|
204
|
+
generateToken,
|
|
205
|
+
createSession,
|
|
206
|
+
deleteSession,
|
|
207
|
+
deleteAllSessions,
|
|
208
|
+
cleanupExpiredSessions,
|
|
209
|
+
hashToken,
|
|
210
|
+
JWT_SECRET,
|
|
211
|
+
JWT_EXPIRES_IN
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Developer User Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Validates Authorization: Bearer <JWT> after apiKeyAuth has already run.
|
|
5
|
+
* Reuses the same JWT_SECRET, hashToken, and user_sessions table as the main auth system.
|
|
6
|
+
* Attaches req.developerUser = { walletAddress, userId } (NOT req.user to avoid collision).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const jwt = require('jsonwebtoken');
|
|
10
|
+
const { pool } = require('../services/db');
|
|
11
|
+
const { JWT_SECRET, hashToken } = require('./authenticate');
|
|
12
|
+
|
|
13
|
+
async function developerUserAuth(req, res, next) {
|
|
14
|
+
try {
|
|
15
|
+
const authHeader = req.headers.authorization;
|
|
16
|
+
|
|
17
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
18
|
+
return res.status(401).json({
|
|
19
|
+
success: false,
|
|
20
|
+
error: { code: 'no_user_token', message: 'Authorization header with Bearer token is required' },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const token = authHeader.substring(7);
|
|
25
|
+
|
|
26
|
+
let decoded;
|
|
27
|
+
try {
|
|
28
|
+
decoded = jwt.verify(token, JWT_SECRET);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.name === 'TokenExpiredError') {
|
|
31
|
+
return res.status(401).json({
|
|
32
|
+
success: false,
|
|
33
|
+
error: { code: 'token_expired', message: 'User token has expired' },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return res.status(401).json({
|
|
37
|
+
success: false,
|
|
38
|
+
error: { code: 'invalid_token', message: 'Invalid user token' },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check session exists in user_sessions (same table as main auth)
|
|
43
|
+
const sessionCheck = await pool.query(
|
|
44
|
+
'SELECT 1 FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
|
|
45
|
+
[decoded.walletAddress, hashToken(token)]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (sessionCheck.rows.length === 0) {
|
|
49
|
+
return res.status(401).json({
|
|
50
|
+
success: false,
|
|
51
|
+
error: { code: 'session_invalid', message: 'Session not found or expired' },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
req.developerUser = {
|
|
56
|
+
walletAddress: decoded.walletAddress,
|
|
57
|
+
userId: decoded.userId,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Update last activity (fire-and-forget)
|
|
61
|
+
pool.query(
|
|
62
|
+
'UPDATE user_sessions SET last_activity = NOW() WHERE wallet_address = $1 AND token_hash = $2',
|
|
63
|
+
[decoded.walletAddress, hashToken(token)]
|
|
64
|
+
).catch(() => {});
|
|
65
|
+
|
|
66
|
+
next();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error('[DevUserAuth] Middleware error:', error);
|
|
69
|
+
return res.status(500).json({
|
|
70
|
+
success: false,
|
|
71
|
+
error: { code: 'auth_error', message: 'Authentication error' },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { developerUserAuth };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🔐 WebSocket JWT Authentication Middleware
|
|
3
|
+
*
|
|
4
|
+
* Authenticates socket.io connections using JWT tokens from auth parameter
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const jwt = require('jsonwebtoken');
|
|
8
|
+
const { pool } = require('../services/db'); // Shared database pool
|
|
9
|
+
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
|
|
12
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Socket.io middleware to authenticate connections
|
|
16
|
+
*/
|
|
17
|
+
function socketAuthMiddleware(socket, next) {
|
|
18
|
+
try {
|
|
19
|
+
// Get token from auth parameter (sent by client during connection)
|
|
20
|
+
const token = socket.handshake.auth?.token;
|
|
21
|
+
|
|
22
|
+
if (!token) {
|
|
23
|
+
console.log('[Socket] Connection rejected: No token in auth parameter');
|
|
24
|
+
return next(new Error('Authentication required - Missing token'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Verify JWT
|
|
28
|
+
let decoded;
|
|
29
|
+
try {
|
|
30
|
+
decoded = jwt.verify(token, JWT_SECRET);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.log('[Socket] Connection rejected: Invalid token');
|
|
33
|
+
return next(new Error('Invalid token'));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Verify session exists in database
|
|
37
|
+
const crypto = require('crypto');
|
|
38
|
+
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
39
|
+
|
|
40
|
+
pool.query(
|
|
41
|
+
'SELECT * FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
|
|
42
|
+
[decoded.walletAddress, tokenHash]
|
|
43
|
+
).then(result => {
|
|
44
|
+
if (result.rows.length === 0) {
|
|
45
|
+
console.log('[Socket] Connection rejected: Session not found');
|
|
46
|
+
return next(new Error('Session expired'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Attach user info to socket
|
|
50
|
+
socket.user = {
|
|
51
|
+
userId: decoded.userId,
|
|
52
|
+
walletAddress: decoded.walletAddress,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
console.log(`[Socket] ✅ Authenticated: ${decoded.walletAddress.slice(0, 8)}...`);
|
|
56
|
+
next();
|
|
57
|
+
}).catch(err => {
|
|
58
|
+
console.error('[Socket] Database error:', err);
|
|
59
|
+
next(new Error('Authentication error'));
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('[Socket] Auth middleware error:', error);
|
|
63
|
+
next(new Error('Authentication error'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { socketAuthMiddleware };
|
|
68
|
+
|
|
69
|
+
|