From 1cbff4ed56a11ab961eab7183dbdba127f83ca8c Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sat, 6 Dec 2025 00:26:27 +0000 Subject: [PATCH] Improve security by securing endpoints and fixing data leaks Implement JWT authentication for achievements activation endpoint and secure Nexus payouts and payment-history endpoints by filtering data at the database level. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 947d796e-4b26-4a17-afb1-6d7696b3c297 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/OjpZ7xP Replit-Helium-Checkpoint-Created: true --- client/lib/aethex-database-adapter.ts | 13 +- client/pages/Roadmap.tsx | 24 +-- replit.md | 289 +++----------------------- server/index.ts | 102 +++++++-- 4 files changed, 126 insertions(+), 302 deletions(-) diff --git a/client/lib/aethex-database-adapter.ts b/client/lib/aethex-database-adapter.ts index 8241092f..2ff617a4 100644 --- a/client/lib/aethex-database-adapter.ts +++ b/client/lib/aethex-database-adapter.ts @@ -1079,6 +1079,8 @@ export const aethexAchievementService = { username?: string; }): Promise { try { + ensureSupabase(); + const payload = { targetEmail: target?.email, targetUsername: target?.username, @@ -1094,12 +1096,21 @@ export const aethexAchievementService = { return null; } + // Get auth token for secure endpoint + const { data: { session } } = await supabase.auth.getSession(); + const token = session?.access_token; + const url = `${baseUrl}/api/achievements/activate`; console.log("[Rewards] Activating at:", url); + const headers: Record = { "Content-Type": "application/json" }; + if (token) { + headers["Authorization"] = `Bearer ${token}`; + } + const response = await fetch(url, { method: "POST", - headers: { "Content-Type": "application/json" }, + headers, body: JSON.stringify(payload), }); diff --git a/client/pages/Roadmap.tsx b/client/pages/Roadmap.tsx index 831d0a2d..2b8394d5 100644 --- a/client/pages/Roadmap.tsx +++ b/client/pages/Roadmap.tsx @@ -23,7 +23,6 @@ import { CheckCircle2, TimerReset, } from "lucide-react"; -import Timeline from "@/components/roadmap/Timeline"; import GalaxyMap from "@/components/roadmap/GalaxyMap"; import Achievements from "@/components/roadmap/Achievements"; import VoteWidget from "@/components/roadmap/VoteWidget"; @@ -294,8 +293,8 @@ export default function Roadmap() { -
-
+
+
({ id: id as Quest["phase"], @@ -315,23 +314,12 @@ export default function Roadmap() { }))} onSelect={(id) => setFocusedPhase(id)} /> - ({ - id: q.id, - title: q.title, - phase: q.phase, - xp: q.xp, - claimed: !!claimed[q.id], - }))} - onSelectPhase={(p) => setFocusedPhase(p)} - onToggleClaim={(id) => toggleClaim(id)} - />
{/* Phases */} -
-
+
+
{(focusedPhase ? [focusedPhase] : ["now", "month1", "month2", "month3"] @@ -398,14 +386,14 @@ export default function Roadmap() {
-
+
{/* Sneak peeks */}
Redirects) -``` -https://aethex.dev/api/discord/oauth/callback -https://aethex.foundation/api/discord/oauth/callback -https://supabase.aethex.tech/auth/v1/callback -``` - -### Supabase Dashboard (Authentication > URL Configuration) -- **Site URL**: `https://aethex.foundation` -- **Redirect URLs**: - - `https://aethex.dev/**` - - `https://aethex.foundation/**` - - `https://supabase.aethex.tech/auth/v1/callback` - -## Recent Changes (December 5, 2025) -- ✅ **Isometric 2.5D Realm Selector**: Replaced WebGL 3D scene with CSS-based isometric cards - - `client/components/IsometricRealmCard.tsx` - Individual card with CSS 3D transforms and parallax layers - - `client/components/IsometricRealmSelector.tsx` - Container with responsive grid, ambient particles, and header/footer - - Mouse-tracking tilt effect with translateZ parallax on hover - - Realm-specific colors, icons, and glow effects - - Memoized particle generation to prevent jitter on mouse movement - - Throttled mouse position updates (50ms) for better performance -- ✅ **Electron Desktop App Support**: Added desktop application framework - - `electron/main.js` - Main Electron process with window management - - `electron/preload.js` - Secure IPC bridge for frontend communication - - `electron-builder.yml` - Build configuration for Windows/Mac/Linux packaging - - `client/desktop/components/` - TitleBar, DesktopShell, Overlay - - `client/desktop/types/preload.d.ts` - TypeScript declarations for IPC bridge -- ✅ **Desktop App Distribution**: Complete release pipeline - - `.github/workflows/desktop-build.yml` - Automated builds on Windows, macOS, Linux runners - - Triggers on version tags (v*) or manual dispatch - - Uploads artifacts (.exe, .dmg, .AppImage, .deb) to GitHub Releases - - `docs/DESKTOP-RELEASE.md` - Comprehensive release guide with code signing instructions - - `build/icons/` - App icon assets with conversion instructions - - `build/entitlements.mac.plist` - macOS security entitlements for notarization - - Auto-updater configured via GitHub Releases -- ✅ **Utility Services**: New backend services - - `services/pii-scrub.js` - PII scrubbing utility for privacy - - `services/watcher.js` - File watcher for development workflow - -## Recent Changes (December 4, 2025) -- ✅ **RealmSwitcher Alignment Fix**: Fixed realm IDs to match ARMS taxonomy - - Old IDs (`game_developer`, `client`, `community_member`, `customer`) replaced with ARMS IDs (`labs`, `gameforge`, `corp`, `foundation`, `devlink`, `nexus`, `staff`) - - Realm selection now persists correctly and pre-selects on page load - - Routes aligned with actual dashboard routes (`/dashboard/labs`, `/gameforge`, `/hub/client`, etc.) -- ✅ **Profile Update Security**: Added JWT authentication to `/api/profile/update` endpoint - - Bearer token authentication required for all profile updates - - User can only update their own profile (user_id validation against auth session) - - Dashboard now sends auth token with all profile API requests -- ✅ **Bot Panel** (`/bot-panel`): Comprehensive Discord bot management dashboard - - Overview tab: Bot info, feed bridge stats, uptime - - Servers tab: All connected Discord servers with member counts - - Commands tab: All slash commands with "Register Commands" button - - Linked Users tab: Discord-linked AeThex users (sanitized PII) - - Feed tab: Recent feed activity from Discord and website - - Protected with admin token authentication -- ✅ **New Discord Slash Commands**: Added 4 new commands - - `/help` - Shows all bot commands with descriptions - - `/stats` - View your AeThex statistics (posts, likes, comments) - - `/leaderboard` - Top contributors with category filter (posts, likes, creators) - - `/post` - Create a post directly from Discord with category and image support -- ✅ **Bot API Security**: Added authentication and CORS to management endpoints - - All management endpoints require admin token - - PII sanitized in linked users endpoint - - CORS headers added for browser access - - Server-side proxy endpoints (`/api/discord/bot-*`) to keep admin token secure - - Client uses proxied endpoints - no tokens exposed in frontend bundle - -## Recent Changes (December 3, 2025) -- ✅ **Discord Feed Bridge Bug Fix**: Fixed critical 14x duplicate post issue with three-layer protection - - Added polling lock to prevent overlapping poll cycles - - Immediate timestamp updates before sending to Discord - - Processed ID tracking to skip already-sent posts -- ✅ **Creator Directory "Become a Creator" Flow**: Registration modal for new creators - - Form validation for username, bio, and primary arm affiliation - - Pre-fills data from user profile where available - - Integrates with `aethex_creators` table in Supabase -- ✅ **Dashboard Realm & Settings**: Functional realm switching with persistence - - RealmSwitcher component wired with proper state management - - Saves realm preference to Supabase database - - Profile update functionality in Settings tab -- ✅ **Toast Notification Fixes**: Migrated all calls to use `description` property -- ✅ **Bidirectional Discord-Feed Bridge**: Full two-way sync between Discord and AeThex feed - - **Discord → AeThex**: Messages from Discord FEED channel sync to community feed - - **AeThex → Discord**: Bot polls Supabase every 5 seconds for new posts (works from production!) - - Bot listens to configured channel (DISCORD_MAIN_CHAT_CHANNELS env var) - - Posts display with purple Discord badge and channel name - - Supports images/videos from both platforms - - Loop prevention: Discord-sourced posts don't re-post back to Discord - - **Architecture**: Bot polls `community_posts` table directly - no HTTP dependency on server -- ✅ **Moved /feed to /community/feed**: Feed is now a tab within the Community page - - Old /feed URL redirects to /community/feed - - Added redirect in vercel.json for production -- ✅ Fixed passport subdomain API to call aethex.foundation (identity authority) -- ✅ Fixed API paths: `subdomain-data` → `subdomain`, `project-data` → `project` -- ✅ Restored wildcard rewrites in vercel.json for `*.aethex.me` and `*.aethex.space` -- ✅ Added missing routes to vercel.json: /community/*, /developers/*, /discord-verify/*, /ethos/* -- ✅ Added catch-all route for future paths -- ✅ Fixed Discord verification code input to accept alphanumeric codes (was filtering out letters) -- ✅ Added step-by-step error tracking to verify-code API for debugging -- ✅ Configured Discord OAuth redirect URLs for multi-domain setup -- ✅ Discord bot running on Replit (moved from Railway) - -## Recent Changes (December 2, 2025) -- ✅ Configured Vite to run on port 5000 for Replit compatibility -- ✅ Set up proper host configuration (0.0.0.0) for Replit proxy -- ✅ Added `allowedHosts: true` to allow Replit's dynamic proxy hostnames -- ✅ Updated .gitignore to properly exclude environment files -- ✅ Installed all npm dependencies -- ✅ Configured deployment settings for Replit autoscale -- ✅ Fixed server build to output `dist/server/production.mjs` for deployment -- ✅ Verified application runs without errors in Replit environment -- ✅ Discord bot (AeThex#9389) running and connected to 7 servers -- ✅ Created discord_verifications and discord_links database tables -- ✅ Registered all 5 slash commands with Discord API -- ✅ Extended auth loading timeout to 30s for slow networks -- ✅ Fixed /community route with wildcard for nested tabs -- ✅ Fixed /developers route showing real user data - -## Notes -- Supabase credentials must be configured in Replit Secrets for the app to fully function -- The application integrates an Express backend directly into the Vite dev server for seamless API development -- Discord bot runs as a separate workflow on port 8044 (health check only) -- Main application runs on port 5000 +## External Dependencies +- **Supabase**: Used for database (PostgreSQL), authentication, and real-time features. +- **Discord API**: Integrated for Discord bot functionality, OAuth for user verification and linking, and feed bridging. +- **Vite**: Frontend build tool. +- **Express.js**: Backend web framework. +- **React**: Frontend library. +- **Tailwind CSS**: Utility-first CSS framework. +- **Radix UI**: Unstyled UI component library. +- **TanStack Query**: Data fetching and state management. +- **React Router DOM**: Client-side routing. +- **Electron**: Framework for building desktop applications. \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index d438af1e..acc43283 100644 --- a/server/index.ts +++ b/server/index.ts @@ -2991,6 +2991,37 @@ export function createServer() { try { const { targetEmail, targetUsername } = req.body || {}; + // Verify auth - get the requesting user from their token + const authHeader = req.headers.authorization; + let requestingUser: any = null; + if (authHeader) { + const token = authHeader.replace("Bearer ", ""); + const { data: { user } } = await adminSupabase.auth.getUser(token); + requestingUser = user; + } + + // Get requester's profile to check if they're an admin + let requesterProfile: any = null; + if (requestingUser?.id) { + const { data: profile } = await adminSupabase + .from("user_profiles") + .select("id, email, username, role") + .eq("id", requestingUser.id) + .single(); + requesterProfile = profile; + } + + // Security: If targeting someone other than yourself, you must be an admin + const isAdmin = requesterProfile?.role === "admin" || + requesterProfile?.role === "owner" || + requesterProfile?.email === "mrpiglr@gmail.com"; + + const isSelfTarget = (targetEmail && requesterProfile?.email === targetEmail) || + (targetUsername && requesterProfile?.username === targetUsername); + + // If not self-targeting and not admin, only allow seeding (no awards) + const canAwardToTarget = isSelfTarget || isAdmin; + const CORE_ACHIEVEMENTS = [ { id: "welcome-to-aethex", @@ -3072,12 +3103,12 @@ export function createServer() { console.log("[Achievements] Seeded", Object.keys(seededAchievements).length, "achievements"); - // Step 2: Try to find target user and award achievements + // Step 2: Try to find target user and award achievements (only if authorized) let targetUserId: string | null = null; let godModeAwarded = false; const awardedAchievementIds: string[] = []; - if (targetEmail || targetUsername) { + if (canAwardToTarget && (targetEmail || targetUsername)) { let query = adminSupabase.from("user_profiles").select("id, email, username"); if (targetEmail) { query = query.eq("email", targetEmail); @@ -3090,10 +3121,10 @@ export function createServer() { if (userProfile?.id) { targetUserId = userProfile.id; - // Check if admin user (mrpiglr) - const isAdmin = targetEmail === "mrpiglr@gmail.com" || targetUsername === "mrpiglr"; + // Check if target user is an admin (for GOD Mode) + const isTargetAdmin = userProfile.email === "mrpiglr@gmail.com" || userProfile.username === "mrpiglr"; - // Award Welcome achievement to everyone + // Award Welcome achievement to the user const welcomeId = seededAchievements["Welcome to AeThex"]; if (welcomeId) { const { error: welcomeError } = await adminSupabase @@ -3107,8 +3138,8 @@ export function createServer() { } } - // Award GOD Mode to admins - if (isAdmin) { + // Award GOD Mode only to admin users + if (isTargetAdmin) { const godModeId = seededAchievements["GOD Mode"]; if (godModeId) { const { error: godError } = await adminSupabase @@ -6761,6 +6792,27 @@ export function createServer() { const limit = parseInt((req.query.limit as string) || "50", 10); const offset = parseInt((req.query.offset as string) || "0", 10); + // First get user's contracts to ensure we only return their payments + const { data: userContracts } = await adminSupabase + .from("nexus_contracts") + .select("id, total_amount, creator_payout_amount, status") + .eq("creator_id", user.id); + + if (!userContracts || userContracts.length === 0) { + return res.status(200).json({ + payments: [], + summary: { + total_earnings: 0, + pending_payouts: 0, + completed_contracts: 0, + }, + limit, + offset, + }); + } + + const contractIds = userContracts.map((c: any) => c.id); + let query = adminSupabase .from("nexus_payments") .select(` @@ -6768,6 +6820,7 @@ export function createServer() { contract:nexus_contracts(id, title, total_amount, status, client_id, created_at), milestone:nexus_milestones(id, description, amount, status) `) + .in("contract_id", contractIds) .order("created_at", { ascending: false }); if (status) { @@ -6780,14 +6833,9 @@ export function createServer() { return res.status(500).json({ error: paymentsError.message }); } - // Calculate summary stats - const { data: contracts } = await adminSupabase - .from("nexus_contracts") - .select("total_amount, creator_payout_amount, status") - .eq("creator_id", user.id); - - const totalEarnings = (contracts || []).reduce((sum: number, c: any) => sum + (c.creator_payout_amount || 0), 0); - const completedContracts = (contracts || []).filter((c: any) => c.status === "completed").length; + // Calculate summary stats from already-fetched user contracts + const totalEarnings = userContracts.reduce((sum: number, c: any) => sum + (c.creator_payout_amount || 0), 0); + const completedContracts = userContracts.filter((c: any) => c.status === "completed").length; const pendingPayouts = (payments || []) .filter((p: any) => p.payment_status === "pending") .reduce((sum: number, p: any) => sum + (p.creator_payout || 0), 0); @@ -7009,6 +7057,22 @@ export function createServer() { const limit = parseInt((req.query.limit as string) || "50", 10); const offset = parseInt((req.query.offset as string) || "0", 10); + // First get user's contracts where they are the client + const { data: clientContracts } = await adminSupabase + .from("nexus_contracts") + .select("id") + .eq("client_id", user.id); + + if (!clientContracts || clientContracts.length === 0) { + return res.status(200).json({ + payments: [], + limit, + offset, + }); + } + + const contractIds = clientContracts.map((c: any) => c.id); + const { data: payments, error: paymentsError } = await adminSupabase .from("nexus_payments") .select(` @@ -7016,6 +7080,7 @@ export function createServer() { contract:nexus_contracts(id, title, creator_id, total_amount, status), milestone:nexus_milestones(id, description, amount) `) + .in("contract_id", contractIds) .order("created_at", { ascending: false }) .range(offset, offset + limit - 1); @@ -7023,13 +7088,8 @@ export function createServer() { return res.status(500).json({ error: paymentsError.message }); } - // Filter to only payments for contracts where user is the client - const userPayments = (payments || []).filter((p: any) => { - return p.contract?.creator_id !== user.id; - }); - return res.status(200).json({ - payments: userPayments, + payments: payments || [], limit, offset, });