diff --git a/docs/DISCORD-COMPLETE-FLOWS.md b/docs/DISCORD-COMPLETE-FLOWS.md new file mode 100644 index 00000000..4a9f734f --- /dev/null +++ b/docs/DISCORD-COMPLETE-FLOWS.md @@ -0,0 +1,543 @@ +# AeThex Discord System - Complete Flow Documentation + +## Overview +There are 5 completely separate Discord flows in AeThex. Each one is independent but some share infrastructure. + +--- + +## FLOW 1: Discord OAuth Login +**When**: User clicks "Continue with Discord" on `/login` page +**Goal**: Create new account OR link Discord to existing email +**Files Involved**: Login.tsx → oauth/start.ts → oauth/callback.ts + +### Step-by-Step + +``` +┌─ User on /login page +│ +├─ Clicks "Continue with Discord" button +│ └─ code/client/pages/Login.tsx line ~180-185 +│ +├─ Browser → /api/discord/oauth/start (GET) +│ └─ code/api/discord/oauth/start.ts +│ • Builds Discord OAuth URL +│ • No state needed for login flow +│ • Redirects to: https://discord.com/api/oauth2/authorize?client_id=... +│ +├─ User authorizes on Discord +│ └─ Discord redirects to: /api/discord/oauth/callback?code=XXX&state=... +│ +├─ Backend processes callback (GET) +│ └─ code/api/discord/oauth/callback.ts +│ +│ A) Parse state (action will be undefined or "login") +│ B) Exchange code for Discord access token +│ C) Fetch Discord user profile (id, username, email, avatar) +│ D) Check if email already in Supabase auth +│ └─ If YES: Just link to existing user (don't create) +│ └─ If NO: Create new auth user with email +│ E) Create/update user_profiles record (upsert) +│ F) Create discord_links record (links discord_id → user_id) +│ G) Create session (login user automatically) +�� H) Redirect to /dashboard or /onboarding based on profile_complete +│ +└─ ✅ User logged in with Discord account linked +``` + +### Database Operations (Login Flow) +``` +discord_links table: + INSERT: { discord_id: "123", user_id: "uuid", linked_at: now() } + +user_profiles table: + UPSERT: { id: user_id, full_name: discord_username, email, avatar_url } + +auth.users table: + INSERT: { email, user_metadata: { full_name, avatar_url } } + (only if email doesn't already exist) +``` + +### Success Path +✅ User sees dashboard or onboarding screen +✅ User is fully logged in +✅ Discord account is linked + +### Failure Paths +❌ Email already exists → Redirect to `/login?error=account_exists&message=...` + - User must sign in with email first, then link from Dashboard +❌ Auth user creation fails → Redirect to `/login?error=auth_create` +❌ Profile creation fails → Redirect to `/login?error=profile_create` +❌ Discord link creation fails → Redirect to `/login?error=link_create` + +--- + +## FLOW 2: Discord Account Linking (from Dashboard) +**When**: User clicks "Link Discord" button in `/dashboard?tab=connections` +**Goal**: Add Discord account to existing AeThex account (user already logged in) +**Files Involved**: Dashboard.tsx → AuthContext.linkProvider() → oauth/start.ts → oauth/callback.ts + +### Step-by-Step + +``` +┌─ User on /dashboard?tab=connections (ALREADY LOGGED IN) +│ +├─ Clicks "Link Discord" button +│ └─ code/client/pages/Dashboard.tsx line ~283-290 +│ Calls: AuthContext.linkProvider("discord") +│ +├─ AuthContext.linkProvider("discord") +│ └─ code/client/contexts/AuthContext.tsx line ~700-770 +│ +│ A) Get current user's auth token +│ └─ const session = await supabase.auth.getSession() +│ +│ B) POST to /api/discord/create-linking-session +│ └─ code/server/index.ts line ~871-911 +│ • Validates auth token +│ • Creates temporary session in discord_linking_sessions table +│ • Returns: { token: sessionToken } (5 min expiry) +│ +│ C) Build Discord OAuth URL with state containing: +│ { +│ action: "link", +│ sessionToken: sessionToken, +│ redirectTo: "/dashboard?tab=connections" +│ } +│ +│ D) Browser → https://discord.com/api/oauth2/authorize?... +│ +├─ User authorizes on Discord +│ └─ Discord redirects to: /api/discord/oauth/callback?code=XXX&state=... +│ +├─ Backend processes callback (GET) +│ └─ code/api/discord/oauth/callback.ts line ~38-100 +│ +│ A) Parse state +│ └─ stateData.action === "link" ��� (this is linking flow) +│ └─ stateData.sessionToken: "hex string" +│ +│ B) Look up session in discord_linking_sessions table +│ └─ if expired or not found → Redirect to login with error +│ +│ C) Extract authenticatedUserId from session +│ +│ D) Exchange code for Discord access token +│ +│ E) Fetch Discord user profile (id, username, email) +│ +│ F) Check if Discord ID already linked to different user +│ └─ if YES: Show error (Discord can only be linked once) +│ └─ if NO: Continue +│ +│ G) Create discord_links record with authenticatedUserId +│ +│ H) Delete the temporary session (cleanup) +│ +│ I) Redirect back to /dashboard?tab=connections +│ └─ User sees page refresh, Discord is now linked +│ +└─ ✅ Discord account linked to existing AeThex account +``` + +### Database Operations (Linking Flow) +``` +discord_linking_sessions table: + INSERT: { user_id: uuid, session_token: "hex", expires_at: now+5min } + DELETE: WHERE session_token = sessionToken (cleanup) + +discord_links table: + UPSERT: { discord_id: "123", user_id: uuid, linked_at: now() } +``` + +### Success Path +✅ Browser redirects to `/dashboard?tab=connections` +✅ Discord appears as "linked" in connections section +✅ User can now use /verify command in Discord to link roles + +### Failure Paths +❌ Auth token missing/invalid → User sees "Auth failed" toast +❌ Session creation fails → User sees "Link failed" toast +❌ Session expired during OAuth redirect → Redirect to login +❌ Discord ID already linked to different account → Redirect to login with error + +### Critical: Session Persistence +⚠️ **KEY ISSUE THAT WAS FIXED**: +- During Discord OAuth redirect (step 2D above), browser leaves aethex.dev +- Session cookies might not be sent to Discord redirect back +- **SOLUTION**: We store user_id in temporary database session (discord_linking_sessions) +- On callback, we extract user_id from database, not from cookies +- This guarantees linking works even if cookies get lost + +--- + +## FLOW 3: Discord Verification Code (Bot /verify Command) +**When**: User types `/verify` in Discord, bot sends code, user clicks link +**Goal**: Link Discord account without OAuth flow (alternative to Flow 2) +**Files Involved**: bot.js /verify command → DiscordVerify.tsx → api/discord/verify-code.ts + +### Step-by-Step + +``` +┌─ User in Discord +│ +├─ Types /verify command +│ └─ code/discord-bot/commands/verify.js +│ +│ A) Bot generates 6-digit code +│ B) Stores in discord_verifications table: +│ { discord_id, verification_code, expires_at: now+15min } +│ C) Sends message with link: +│ "https://aethex.dev/discord-verify?code=123456" +│ +├─ User clicks link +│ └─ Browser goes to /discord-verify?code=123456 +│ code/client/pages/DiscordVerify.tsx +│ +│ A) Check if user is logged in +│ └─ if NO: Redirect to /login?next=/discord-verify?code=123456 +│ (code is preserved in URL) +│ +│ B) If NOT logged in: +│ • User logs in with email/password or OAuth +│ • Browser returns to /discord-verify?code=123456 +│ +│ C) Show form with verification code (pre-filled if from URL) +│ +│ D) User clicks "Verify" button +│ +│ E) Frontend calls POST /api/discord/verify-code +│ └─ code/api/discord/verify-code.ts +│ +│ A) Get current user ID from session +│ B) Query discord_verifications table for matching code +│ C) Check if code is not expired +│ D) Check if Discord ID already linked to different user +│ E) Create discord_links record +│ F) Delete the used verification code +│ G) Return success +│ +│ F) Frontend shows success message +│ +│ G) Browser redirects to /dashboard?tab=connections +│ +└─ ✅ Discord linked via verification code +``` + +### Database Operations (Verification Flow) +``` +discord_verifications table: + INSERT: { discord_id: "123", verification_code: "456789", expires_at: now+15min } + (done by bot.js) + DELETE: WHERE verification_code = code (cleanup after verification) + +discord_links table: + INSERT: { discord_id: "123", user_id: uuid, linked_at: now() } +``` + +### Success Path +✅ Page shows "Discord linked successfully!" +✅ Browser redirects to connections tab +✅ Discord appears as linked + +### Failure Paths +❌ Code expired → Show error "Code expired, ask bot to run /verify again" +❌ Code not found → Show error "Invalid code" +❌ Discord ID already linked to different user → Show error "This Discord is already linked to another account" +❌ User not logged in when clicking link → Redirect to login (code preserved) + +### Why This Flow Exists +- Simpler than OAuth (no redirect to discord.com) +- Works if user's Discord is not verified with email +- Manual process but more user-friendly for some + +--- + +## FLOW 4: Discord Activity (Standalone SPA in Discord) +**When**: User opens Activity in Discord desktop app +**Goal**: Show AeThex dashboard inside Discord as an Activity +**Files Involved**: Activity.tsx → DiscordActivityContext.tsx → api/discord/activity-auth.ts + +### Step-by-Step + +``` +┌─ User opens Discord Activity from context menu in Discord app +│ +├─ Discord loads iframe with Activity URL +│ └─ https://aethex.dev/activity (or deep link) +│ code/client/pages/Activity.tsx +│ +├─ Activity page initializes +│ └─ code/client/contexts/DiscordActivityContext.tsx +│ +│ A) Detect if running inside Discord iframe +│ B) Initialize Discord SDK +│ C) Get Discord access token from SDK +│ D) POST to /api/discord/activity-auth with access token +│ └─ code/api/discord/activity-auth.ts +│ +├─ Backend validates and creates/updates user +│ └─ code/api/discord/activity-auth.ts +│ +│ A) Validate access token (from Discord SDK, not OAuth) +│ B) Get current user ID from token (or create if new) +│ C) Query user_profiles for that user +│ D) If user doesn't exist: +│ • Create with defaults: primary_arm="labs", user_type="community_member" +│ E) Return user profile data +│ +├─ Activity page displays +│ └─ Shows user profile (name, avatar, discord_id) +│ └─ Shows current realm/arm +│ └─ Shows quick action buttons (browse, opportunities, settings) +│ +├─ User can click buttons +│ └─ Each button opens new tab: window.open(url, "_blank") +│ └─ Keeps Activity in Discord while main app opens in browser tab +│ +└─ ✅ Activity displayed successfully +``` + +### Database Operations (Activity Flow) +``` +user_profiles table: + SELECT: WHERE id = user_id (from token) + INSERT: IF NOT EXISTS with defaults +``` + +### Success Path +✅ Activity loads in Discord +✅ Shows user profile +✅ Buttons open links in new tabs +✅ Activity stays in Discord + +### Failure Paths +❌ Not in Discord iframe → Show message "Open this in Discord Activity" +❌ Token invalid → Show error "Authentication failed" +❌ SDK failed to load → Show error "Discord SDK unavailable" + +### Key Difference from Other Flows +- This uses Discord SDK (embedded in iframe) +- NOT OAuth (no redirect to discord.com) +- NOT a regular login (Activity is ephemeral) +- User is verified by Discord SDK internally + +--- + +## FLOW 5: Discord Bot Commands +**When**: User types slash commands in Discord +**Goal**: Manage Discord account, set realm, view profile, etc. +**Files Involved**: bot.js → /api/discord/interactions.ts + +### Available Commands + +#### /verify +``` +User: /verify +Bot: "Click to link: https://aethex.dev/discord-verify?code=123456" +User: (clicks link, links Discord account) +Bot: Role assignment happens (if set in discord_role_mappings) +``` +Uses: Flow 3 (Verification Code) + +#### /set-realm [arm] +``` +User: /set-realm +Bot: Shows dropdown with 5 arms (labs, gameforge, corp, foundation, devlink) +User: Clicks one +Bot: Updates discord_links.primary_arm +Bot: Assigns Discord role based on arm + user_type mapping +``` + +#### /profile +``` +User: /profile +Bot: Embeds card with user's AeThex profile + (name, bio, avatar, primary_arm, avatar) +``` + +#### /unlink +``` +User: /unlink +Bot: Removes discord_links record +Bot: Removes all Discord roles assigned by AeThex +User: Discord account no longer linked +``` + +#### /verify-role +``` +User: /verify-role +Bot: Shows current assigned roles +Bot: Shows expected roles from discord_role_mappings +Bot: Option to auto-assign missing roles +``` + +--- + +## Database Schema + +### discord_links +```sql +id UUID PRIMARY KEY +discord_id TEXT UNIQUE NOT NULL -- Discord user ID +user_id UUID NOT NULL REFERENCES user_profiles(id) +primary_arm TEXT -- 'labs', 'gameforge', etc +linked_at TIMESTAMP DEFAULT now() +``` +**Used by**: All flows +**Query patterns**: +- Find user by discord_id (Flow 2, 3, 4, 5) +- Find discord_id by user_id (Dashboard connections check) +- Update primary_arm (Flow 5 /set-realm) + +### discord_linking_sessions +```sql +id UUID PRIMARY KEY +user_id UUID NOT NULL REFERENCES user_profiles(id) +session_token TEXT UNIQUE NOT NULL -- Random hex string +expires_at TIMESTAMP NOT NULL -- 5 minute expiry +``` +**Used by**: Flow 2 (OAuth Linking) +**Query patterns**: +- Insert when user clicks "Link Discord" (create-linking-session endpoint) +- Select when OAuth callback received (lookup user_id from token) +- Delete after lookup (cleanup) + +### discord_verifications +```sql +id UUID PRIMARY KEY +discord_id TEXT NOT NULL -- Discord user ID +verification_code TEXT UNIQUE NOT NULL +expires_at TIMESTAMP NOT NULL -- 15 minute expiry +``` +**Used by**: Flow 3 (Verification Code) +**Query patterns**: +- Insert when bot /verify command runs (generate code) +- Select when user submits code (verify-code endpoint) +- Delete after verification (cleanup) + +### discord_role_mappings +```sql +id UUID PRIMARY KEY +arm TEXT NOT NULL -- 'labs', 'gameforge', etc +user_type TEXT NOT NULL -- 'game_developer', 'community_member', etc +discord_role_name TEXT NOT NULL -- Role name in Discord +discord_role_id TEXT -- Role ID (optional) +server_id TEXT -- Optional (specific server) +``` +**Used by**: Flow 5 (Bot role assignment) +**Query patterns**: +- Select by (arm, user_type) to find which role to assign +- Managed in Admin panel + +### discord_user_roles +```sql +id UUID PRIMARY KEY +discord_id TEXT NOT NULL -- Discord user ID +server_id TEXT NOT NULL -- Discord server ID +role_id TEXT NOT NULL -- Discord role ID +role_name TEXT NOT NULL -- Role name +assigned_at TIMESTAMP DEFAULT now() +last_verified TIMESTAMP -- When role was last verified +UNIQUE(discord_id, server_id, role_id) +``` +**Used by**: Flow 5 (Bot tracking) +**Query patterns**: +- Insert when role is assigned +- Update when verified +- Select to show /verify-role command + +--- + +## Environment Variables + +```bash +# Required +DISCORD_CLIENT_ID="578971245454950421" +DISCORD_CLIENT_SECRET="JKlilGzcTWgfmt2wEqiHO8wpCel5VEji" +DISCORD_PUBLIC_KEY="d9771dd29e3a6f030cb313e33bb4b51384c7c36829bd551df714681dcf1e1eb0" +DISCORD_BOT_TOKEN="NTc4OTcxMjQ1NDU0OTUwNDIx.GS3vNk.QbRXV0vC6OoUFxhChI6Tp-YEDVtT-pDqrfczvk" + +# Optional +DISCORD_ADMIN_REGISTER_TOKEN="aethex-link" +DISCORD_BOT_HEALTH_URL="https://aethex.railway.internal:8044/health" +``` + +--- + +## Quick Comparison + +| Flow | Entry | Goal | Auth Type | Endpoints | Status | +|------|-------|------|-----------|-----------|--------| +| **1: OAuth Login** | /login button | Create account or link to existing email | OAuth + email | /api/discord/oauth/start, /callback | ✅ Working | +| **2: OAuth Linking** | Dashboard button | Link to logged-in account | OAuth + session token | /api/discord/create-linking-session, /oauth/start, /callback | ✅ Working | +| **3: Verification Code** | Discord bot /verify | Link to logged-in account | Manual code | /api/discord/verify-code | ✅ Working | +| **4: Activity** | Discord Activity | Show dashboard in Discord | Discord SDK | /api/discord/activity-auth | ✅ Working | +| **5: Bot Commands** | Discord slash commands | Manage account & roles | None (bot to backend) | /api/discord/interactions | ✅ Working | + +--- + +## Common Issues & Solutions + +### Session Lost During OAuth Linking +**Problem**: User logs in with email, clicks "Link Discord", gets redirected to login page +**Cause**: Session cookies not sent during Discord redirect +**Solution**: We use `discord_linking_sessions` table to store user_id before redirect ✅ + +### Forced Onboarding After Email Login +**Problem**: User logs in with email, is sent to onboarding even though they completed it before +**Cause**: Profile not marked as `onboarded: true` after completion +**Solution**: Onboarding now sets `onboarded: true` flag ✅ + +### Discord Already Linked Error +**Problem**: User tries to link Discord account that's already linked to different AeThex account +**Cause**: discord_links.discord_id is UNIQUE +**Solution**: Check if discord_id exists and belongs to different user, show error ✅ + +### Verification Code Expired +**Problem**: User takes too long to click Discord link or verify +**Cause**: discord_verifications has 15-min expiry +**Solution**: Tell user to ask bot to run /verify again ✅ + +--- + +## Testing Checklist + +- [ ] **Flow 1**: Login with Discord (new account) +- [ ] **Flow 1**: Login with Discord (existing email account) +- [ ] **Flow 2**: Login with email, then link Discord from Dashboard +- [ ] **Flow 3**: Login with email, use bot /verify code to link +- [ ] **Flow 4**: Open Activity in Discord desktop app +- [ ] **Flow 5**: Try /verify, /set-realm, /profile commands +- [ ] **Flow 5**: Check roles are assigned correctly after /set-realm +- [ ] Unlink Discord and re-link via different flow +- [ ] Multiple users linking same Discord fails gracefully +- [ ] Expired codes show proper error message + +--- + +## Architecture Summary + +``` +┌─────────────────────────────────────────────────────────────┐ +│ DISCORD FLOWS │ +├────────────────────────────────────────────────────────────���┤ +│ │ +│ Flow 1: OAuth Login ──→ /oauth/start → /callback │ +│ Flow 2: OAuth Linking ──→ Session + /oauth/start │ +│ Flow 3: Verify Code ──→ bot /verify + manual code │ +│ Flow 4: Activity SPA ──→ Activity.tsx + SDK auth │ +│ Flow 5: Bot Commands ──→ /interactions endpoint │ +│ │ +│ All flows converge on: discord_links table │ +│ All require: DISCORD_CLIENT_ID + CLIENT_ID │ +│ │ +│ Authentication: │ +│ • Flow 1: OAuth (Discord) │ +│ • Flow 2: Session token (database stored) │ +│ • Flow 3: User session (already logged in) │ +│ • Flow 4: Discord SDK token │ +│ • Flow 5: Bot token (server-side only) │ +│ │ +└────────────────────────────────────────��────────────────────┘ +``` +