diff --git a/api/auth/callback.ts b/api/auth/callback.ts index aed21490..e3b9e68a 100644 --- a/api/auth/callback.ts +++ b/api/auth/callback.ts @@ -11,7 +11,8 @@ import { VercelRequest, VercelResponse } from "@vercel/node"; import { getAdminClient } from "../_supabase"; -const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; +const FOUNDATION_URL = + process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; const CLIENT_ID = process.env.FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp"; const CLIENT_SECRET = process.env.FOUNDATION_OAUTH_CLIENT_SECRET; const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev"; @@ -47,14 +48,18 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) { // Handle Foundation errors if (error) { - const message = error_description ? decodeURIComponent(String(error_description)) : String(error); + const message = error_description + ? decodeURIComponent(String(error_description)) + : String(error); return res.redirect( `/login?error=${error}&message=${encodeURIComponent(message)}`, ); } if (!code) { - return res.redirect(`/login?error=no_code&message=${encodeURIComponent("No authorization code received")}`); + return res.redirect( + `/login?error=no_code&message=${encodeURIComponent("No authorization code received")}`, + ); } try { @@ -64,7 +69,9 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) { console.warn("[Foundation OAuth] Missing state parameter"); } - console.log("[Foundation OAuth] Received authorization code, initiating token exchange"); + console.log( + "[Foundation OAuth] Received authorization code, initiating token exchange", + ); // Store code in a temporary location for the exchange endpoint // In a real implementation, you'd use a temporary token or session @@ -75,7 +82,9 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) { } // Fetch user information from Foundation - const userInfo = await fetchUserInfoFromFoundation(exchangeResult.accessToken); + const userInfo = await fetchUserInfoFromFoundation( + exchangeResult.accessToken, + ); // Sync user to local database await syncUserToLocalDatabase(userInfo); @@ -89,11 +98,12 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) { console.log("[Foundation OAuth] User authenticated:", userInfo.id); // Redirect to dashboard (or stored destination) - const redirectTo = req.query.redirect_to as string || "/dashboard"; + const redirectTo = (req.query.redirect_to as string) || "/dashboard"; return res.redirect(redirectTo); } catch (error) { console.error("[Foundation OAuth] Callback error:", error); - const message = error instanceof Error ? error.message : "Authentication failed"; + const message = + error instanceof Error ? error.message : "Authentication failed"; return res.redirect( `/login?error=auth_failed&message=${encodeURIComponent(message)}`, ); @@ -105,7 +115,10 @@ export async function handleCallback(req: VercelRequest, res: VercelResponse) { * Exchange authorization code for access token * Called from frontend */ -export async function handleTokenExchange(req: VercelRequest, res: VercelResponse) { +export async function handleTokenExchange( + req: VercelRequest, + res: VercelResponse, +) { if (req.method !== "POST") { return res.status(405).json({ error: "Method not allowed" }); } @@ -120,7 +133,9 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons const exchangeResult = await performTokenExchange(code); // Fetch user information from Foundation - const userInfo = await fetchUserInfoFromFoundation(exchangeResult.accessToken); + const userInfo = await fetchUserInfoFromFoundation( + exchangeResult.accessToken, + ); // Sync user to local database await syncUserToLocalDatabase(userInfo); @@ -131,7 +146,10 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons `auth_user_id=${userInfo.id}; Path=/; Secure; SameSite=Strict; Max-Age=2592000`, ]); - console.log("[Foundation OAuth] Token exchange successful for user:", userInfo.id); + console.log( + "[Foundation OAuth] Token exchange successful for user:", + userInfo.id, + ); return res.status(200).json({ accessToken: exchangeResult.accessToken, @@ -139,7 +157,8 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons }); } catch (error) { console.error("[Foundation OAuth] Token exchange error:", error); - const message = error instanceof Error ? error.message : "Token exchange failed"; + const message = + error instanceof Error ? error.message : "Token exchange failed"; return res.status(400).json({ error: message }); } } @@ -147,9 +166,7 @@ export async function handleTokenExchange(req: VercelRequest, res: VercelRespons /** * Exchange authorization code for access token with Foundation */ -async function performTokenExchange( - code: string, -): Promise<{ +async function performTokenExchange(code: string): Promise<{ accessToken: string; tokenType: string; expiresIn: number; @@ -205,7 +222,9 @@ async function performTokenExchange( /** * Fetch user information from Foundation using access token */ -async function fetchUserInfoFromFoundation(accessToken: string): Promise { +async function fetchUserInfoFromFoundation( + accessToken: string, +): Promise { const userInfoEndpoint = `${FOUNDATION_URL}/api/oauth/userinfo`; console.log("[Foundation OAuth] Fetching user info from:", userInfoEndpoint); @@ -234,30 +253,36 @@ async function fetchUserInfoFromFoundation(accessToken: string): Promise { +async function syncUserToLocalDatabase( + foundationUser: FoundationUserInfo, +): Promise { const supabase = getAdminClient(); - console.log("[Foundation OAuth] Syncing user to local database:", foundationUser.id); + console.log( + "[Foundation OAuth] Syncing user to local database:", + foundationUser.id, + ); // Upsert user profile - const { error } = await supabase - .from("user_profiles") - .upsert({ - id: foundationUser.id, - email: foundationUser.email, - username: foundationUser.username || null, - full_name: foundationUser.full_name || null, - avatar_url: foundationUser.avatar_url || null, - profile_completed: foundationUser.profile_complete || false, - updated_at: new Date().toISOString(), - }); + const { error } = await supabase.from("user_profiles").upsert({ + id: foundationUser.id, + email: foundationUser.email, + username: foundationUser.username || null, + full_name: foundationUser.full_name || null, + avatar_url: foundationUser.avatar_url || null, + profile_completed: foundationUser.profile_complete || false, + updated_at: new Date().toISOString(), + }); if (error) { console.error("[Foundation OAuth] Failed to sync user profile:", error); throw new Error("Failed to create local user profile"); } - console.log("[Foundation OAuth] User synced successfully:", foundationUser.id); + console.log( + "[Foundation OAuth] User synced successfully:", + foundationUser.id, + ); } /** diff --git a/api/auth/exchange-token.ts b/api/auth/exchange-token.ts index c29e622d..142169dc 100644 --- a/api/auth/exchange-token.ts +++ b/api/auth/exchange-token.ts @@ -1,19 +1,17 @@ /** * Token Exchange Endpoint - * + * * Frontend calls this endpoint after receiving the authorization code from Foundation. * It stores the Foundation's access token and returns user information. */ import { VercelRequest, VercelResponse } from "@vercel/node"; -const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; +const FOUNDATION_URL = + process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev"; -export default async function handler( - req: VercelRequest, - res: VercelResponse, -) { +export default async function handler(req: VercelRequest, res: VercelResponse) { if (req.method !== "POST") { return res.status(405).json({ error: "Method not allowed" }); } diff --git a/api/auth/foundation-callback.ts b/api/auth/foundation-callback.ts index 5616f4e3..68aa47e1 100644 --- a/api/auth/foundation-callback.ts +++ b/api/auth/foundation-callback.ts @@ -1,6 +1,6 @@ /** * Foundation OAuth Callback Handler - * + * * This endpoint receives the authorization code from aethex.foundation after user authentication. * It exchanges the code for an access token and establishes a session on aethex.dev. */ @@ -8,7 +8,8 @@ import { getAdminClient } from "../_supabase"; import { VercelRequest, VercelResponse } from "@vercel/node"; -const FOUNDATION_URL = process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; +const FOUNDATION_URL = + process.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; const API_BASE = process.env.VITE_API_BASE || "https://aethex.dev"; interface FoundationTokenResponse { @@ -23,10 +24,7 @@ interface FoundationTokenResponse { }; } -export default async function handler( - req: VercelRequest, - res: VercelResponse, -) { +export default async function handler(req: VercelRequest, res: VercelResponse) { if (req.method !== "GET") { return res.status(405).json({ error: "Method not allowed" }); } @@ -36,11 +34,15 @@ export default async function handler( // Handle Foundation errors if (error) { const errorDesc = req.query.error_description || error; - return res.redirect(`/login?error=${error}&message=${encodeURIComponent(String(errorDesc))}`); + return res.redirect( + `/login?error=${error}&message=${encodeURIComponent(String(errorDesc))}`, + ); } if (!code) { - return res.redirect("/login?error=no_code&message=Authorization code not received"); + return res.redirect( + "/login?error=no_code&message=Authorization code not received", + ); } try { @@ -74,7 +76,7 @@ export default async function handler( const errorData = await tokenResponse.json().catch(() => ({})); console.error("[Foundation OAuth] Token exchange failed:", errorData); return res.redirect( - `/login?error=token_exchange&message=${encodeURIComponent("Failed to exchange authorization code")}` + `/login?error=token_exchange&message=${encodeURIComponent("Failed to exchange authorization code")}`, ); } @@ -82,7 +84,9 @@ export default async function handler( if (!tokenData.access_token || !tokenData.user) { console.error("[Foundation OAuth] Invalid token response"); - return res.redirect("/login?error=invalid_token&message=Invalid token response"); + return res.redirect( + "/login?error=invalid_token&message=Invalid token response", + ); } // Extract user information from Foundation response @@ -101,25 +105,33 @@ export default async function handler( if (fetchError && fetchError.code !== "PGRST116") { // PGRST116 = no rows found (expected for new users) - console.error("[Foundation OAuth] Error fetching user profile:", fetchError); + console.error( + "[Foundation OAuth] Error fetching user profile:", + fetchError, + ); } if (!existingProfile) { // Create user profile from Foundation data - const { error: createError } = await supabase.from("user_profiles").insert({ - id: user.id, - email: user.email, - username: user.username || null, - full_name: user.full_name || null, - profile_completed: user.profile_complete || false, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), - }); + const { error: createError } = await supabase + .from("user_profiles") + .insert({ + id: user.id, + email: user.email, + username: user.username || null, + full_name: user.full_name || null, + profile_completed: user.profile_complete || false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + }); if (createError) { - console.error("[Foundation OAuth] Failed to create user profile:", createError); + console.error( + "[Foundation OAuth] Failed to create user profile:", + createError, + ); return res.redirect( - `/login?error=profile_create&message=${encodeURIComponent("Failed to create local user profile")}` + `/login?error=profile_create&message=${encodeURIComponent("Failed to create local user profile")}`, ); } } @@ -138,7 +150,7 @@ export default async function handler( } catch (error) { console.error("[Foundation OAuth] Callback error:", error); return res.redirect( - `/login?error=unknown&message=${encodeURIComponent("An unexpected error occurred")}` + `/login?error=unknown&message=${encodeURIComponent("An unexpected error occurred")}`, ); } } diff --git a/client/hooks/use-foundation-auth.ts b/client/hooks/use-foundation-auth.ts index d97b9a15..e59ae5d5 100644 --- a/client/hooks/use-foundation-auth.ts +++ b/client/hooks/use-foundation-auth.ts @@ -72,7 +72,10 @@ export function useFoundationAuth(): UseFoundationAuthReturn { throw new Error("Invalid response from token exchange"); } - console.log("[Foundation Auth] Token exchange successful for user:", tokenData.user.id); + console.log( + "[Foundation Auth] Token exchange successful for user:", + tokenData.user.id, + ); // Clear auth parameters from URL and storage const url = new URL(window.location.href); @@ -95,16 +98,21 @@ export function useFoundationAuth(): UseFoundationAuthReturn { // Redirect to dashboard or stored destination navigate(redirectTo, { replace: true }); } catch (exchangeError) { - const message = exchangeError instanceof Error - ? exchangeError.message - : "Failed to exchange authorization code"; + const message = + exchangeError instanceof Error + ? exchangeError.message + : "Failed to exchange authorization code"; - console.error("[Foundation Auth] Token exchange failed:", exchangeError); + console.error( + "[Foundation Auth] Token exchange failed:", + exchangeError, + ); throw new Error(message); } } catch (err) { - const message = err instanceof Error ? err.message : "Authentication failed"; + const message = + err instanceof Error ? err.message : "Authentication failed"; setError(message); console.error("[Foundation Auth] Callback processing error:", err); @@ -150,7 +158,9 @@ export function useFoundationAuthStatus(): { useEffect(() => { // Check for foundation_access_token cookie const cookies = document.cookie.split(";").map((c) => c.trim()); - const tokenCookie = cookies.find((c) => c.startsWith("foundation_access_token=")); + const tokenCookie = cookies.find((c) => + c.startsWith("foundation_access_token="), + ); const userCookie = cookies.find((c) => c.startsWith("auth_user_id=")); if (tokenCookie && userCookie) { diff --git a/client/lib/foundation-auth.ts b/client/lib/foundation-auth.ts index 426c2622..4429dd2e 100644 --- a/client/lib/foundation-auth.ts +++ b/client/lib/foundation-auth.ts @@ -50,8 +50,10 @@ export function clearFoundationAuth(): void { if (typeof window === "undefined") return; // Clear cookies by setting expiration to past - document.cookie = "foundation_access_token=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; - document.cookie = "auth_user_id=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; + document.cookie = + "foundation_access_token=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; + document.cookie = + "auth_user_id=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; // Clear session storage sessionStorage.removeItem("oauth_code_verifier"); @@ -89,7 +91,8 @@ export async function makeAuthenticatedRequest( * Clears local auth state and optionally notifies Foundation */ export async function logoutFromFoundation(): Promise { - const FOUNDATION_URL = import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; + const FOUNDATION_URL = + import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; const token = getFoundationAccessToken(); // Clear local auth diff --git a/client/lib/foundation-oauth.ts b/client/lib/foundation-oauth.ts index 516498b3..f52b76bc 100644 --- a/client/lib/foundation-oauth.ts +++ b/client/lib/foundation-oauth.ts @@ -10,8 +10,10 @@ * - GET /api/oauth/userinfo - User info endpoint */ -const FOUNDATION_URL = import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; -const CLIENT_ID = import.meta.env.VITE_FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp"; +const FOUNDATION_URL = + import.meta.env.VITE_FOUNDATION_URL || "https://aethex.foundation"; +const CLIENT_ID = + import.meta.env.VITE_FOUNDATION_OAUTH_CLIENT_ID || "aethex_corp"; const API_BASE = import.meta.env.VITE_API_BASE || "https://aethex.dev"; /** @@ -19,7 +21,8 @@ const API_BASE = import.meta.env.VITE_API_BASE || "https://aethex.dev"; * Must be 43-128 characters, URL-safe (A-Z, a-z, 0-9, -, ., _, ~) */ function generateCodeVerifier(): string { - const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; + const charset = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; const length = 64; let verifier = ""; const randomValues = new Uint8Array(length); @@ -104,7 +107,9 @@ export async function getFoundationAuthorizationUrl(options?: { /** * Initiate the Foundation OAuth login flow */ -export async function initiateFoundationLogin(redirectTo?: string): Promise { +export async function initiateFoundationLogin( + redirectTo?: string, +): Promise { try { const authUrl = await getFoundationAuthorizationUrl({ redirectTo }); window.location.href = authUrl; diff --git a/docs/AXIOM-MODEL-PHASE1-SCOPE.md b/docs/AXIOM-MODEL-PHASE1-SCOPE.md index afe98149..d8d71559 100644 --- a/docs/AXIOM-MODEL-PHASE1-SCOPE.md +++ b/docs/AXIOM-MODEL-PHASE1-SCOPE.md @@ -1,4 +1,5 @@ # Axiom Model: Phase 1 Code Migration Scope + ## Moving Identity from aethex.dev (Vercel) to aethex.foundation (Replit) **Status:** CRITICAL P0 (Blocks NEXUS & FOUNDATION work) @@ -10,56 +11,62 @@ ## 1. PAGES TO COPY ### Authentication & Onboarding Pages -| File | Purpose | Notes | -|------|---------|-------| -| `code/client/pages/Login.tsx` | Login UI + Discord OAuth button | Copy as-is; validate Discord button routing | -| `code/client/pages/Signup.tsx` | (if exists) User registration | Copy if present | -| `code/client/pages/Onboarding.tsx` | Realm/arm selection, profile setup | Copy all onboarding flow | -| `code/client/pages/DiscordVerify.tsx` | Verification code entry for linking | Copy verification flow | + +| File | Purpose | Notes | +| ------------------------------------- | ----------------------------------- | ------------------------------------------- | +| `code/client/pages/Login.tsx` | Login UI + Discord OAuth button | Copy as-is; validate Discord button routing | +| `code/client/pages/Signup.tsx` | (if exists) User registration | Copy if present | +| `code/client/pages/Onboarding.tsx` | Realm/arm selection, profile setup | Copy all onboarding flow | +| `code/client/pages/DiscordVerify.tsx` | Verification code entry for linking | Copy verification flow | ### Profile & Settings Pages -| File | Purpose | Notes | -|------|---------|-------| -| `code/client/pages/Profile.tsx` | (or Dashboard) User profile view | Copy public profile viewing | -| `code/client/pages/Dashboard.tsx` | User dashboard + OAuthConnections | Copy OAuth linking UI | -| `code/client/pages/settings/*` | Profile settings, password reset, etc. | Copy all settings pages | + +| File | Purpose | Notes | +| --------------------------------- | -------------------------------------- | --------------------------- | +| `code/client/pages/Profile.tsx` | (or Dashboard) User profile view | Copy public profile viewing | +| `code/client/pages/Dashboard.tsx` | User dashboard + OAuthConnections | Copy OAuth linking UI | +| `code/client/pages/settings/*` | Profile settings, password reset, etc. | Copy all settings pages | ### Passport Pages -| File | Purpose | Notes | -|------|---------|-------| -| `code/client/pages/SubdomainPassport.tsx` | Creator passport for *.aethex.me | Copy; will fetch from Foundation API | + +| File | Purpose | Notes | +| ----------------------------------------- | --------------------------------- | ------------------------------------ | +| `code/client/pages/SubdomainPassport.tsx` | Creator passport for \*.aethex.me | Copy; will fetch from Foundation API | --- ## 2. CONTEXTS & STATE MANAGEMENT -| File | Purpose | Dependencies | -|------|---------|--------------| -| `code/client/contexts/AuthContext.tsx` | Central auth state, loginProvider, linkProvider | Depends on Supabase client | -| `code/client/contexts/DiscordActivityContext.tsx` | Discord Activity SDK state | Optional; copy if Activity is needed | -| `code/client/contexts/ThemeContext.tsx` | Theme switching | Dependency; copy | +| File | Purpose | Dependencies | +| ------------------------------------------------- | ----------------------------------------------- | ------------------------------------ | +| `code/client/contexts/AuthContext.tsx` | Central auth state, loginProvider, linkProvider | Depends on Supabase client | +| `code/client/contexts/DiscordActivityContext.tsx` | Discord Activity SDK state | Optional; copy if Activity is needed | +| `code/client/contexts/ThemeContext.tsx` | Theme switching | Dependency; copy | --- ## 3. COMPONENTS TO COPY ### Auth & OAuth Components -| File | Purpose | -|------|---------| -| `code/client/components/settings/OAuthConnections.tsx` | OAuth provider cards (Discord, etc.) | + +| File | Purpose | +| --------------------------------------------------------- | ------------------------------------------------- | +| `code/client/components/settings/OAuthConnections.tsx` | OAuth provider cards (Discord, etc.) | | `code/client/components/admin/AdminDiscordManagement.tsx` | Admin UI for role mappings (optional for Phase 1) | ### Profile & Passport Components -| File | Purpose | -|------|---------| + +| File | Purpose | +| ----------------------------------------------------- | ------------------------ | | `code/client/components/passport/PassportSummary.tsx` | Renders creator passport | -| `code/client/components/ErrorBoundary.tsx` | Error handling | -| `code/client/components/LoadingScreen.tsx` | Loading UI | -| `code/client/components/Layout.tsx` | App layout & header | +| `code/client/components/ErrorBoundary.tsx` | Error handling | +| `code/client/components/LoadingScreen.tsx` | Loading UI | +| `code/client/components/Layout.tsx` | App layout & header | ### Shared UI Components -| Directory | Purpose | -|-----------|---------| + +| Directory | Purpose | +| ----------------------------- | --------------------------------------- | | `code/client/components/ui/*` | All Radix UI & design system components | --- @@ -67,36 +74,40 @@ ## 4. API ENDPOINTS & SERVERLESS FUNCTIONS TO COPY ### Discord OAuth Endpoints -| File | Endpoint | Purpose | -|------|----------|---------| -| `code/api/discord/oauth/start.ts` | `GET /api/discord/oauth/start` | Redirect to Discord authorization | + +| File | Endpoint | Purpose | +| ------------------------------------ | --------------------------------- | ---------------------------------- | +| `code/api/discord/oauth/start.ts` | `GET /api/discord/oauth/start` | Redirect to Discord authorization | | `code/api/discord/oauth/callback.ts` | `GET /api/discord/oauth/callback` | Handle Discord callback, link user | -| `code/api/discord/verify-code.ts` | `POST /api/discord/verify-code` | Verify 6-digit code for linking | -| `code/api/discord/link.ts` | `POST /api/discord/link` | Link Discord account by code | -| `code/api/discord/sync-roles.ts` | `POST /api/discord/sync-roles` | Assign Discord roles after linking | +| `code/api/discord/verify-code.ts` | `POST /api/discord/verify-code` | Verify 6-digit code for linking | +| `code/api/discord/link.ts` | `POST /api/discord/link` | Link Discord account by code | +| `code/api/discord/sync-roles.ts` | `POST /api/discord/sync-roles` | Assign Discord roles after linking | ### Profile & Auth Endpoints -| File | Endpoint | Purpose | -|------|----------|---------| -| `code/api/profile/ensure.ts` | `POST /api/profile/ensure` | Create or ensure user profile exists | -| `code/api/user/*` | Various | User data endpoints (review for auth deps) | + +| File | Endpoint | Purpose | +| ---------------------------- | -------------------------- | ------------------------------------------ | +| `code/api/profile/ensure.ts` | `POST /api/profile/ensure` | Create or ensure user profile exists | +| `code/api/user/*` | Various | User data endpoints (review for auth deps) | ### Passport Endpoints -| File | Endpoint | Purpose | -|------|----------|---------| + +| File | Endpoint | Purpose | +| ------------------------------------------- | --------------------------------------- | ------------------------- | | `code/api/passport/subdomain/[username].ts` | `GET /api/passport/subdomain/:username` | Creator passport JSON API | -| `code/api/passport/project/[slug].ts` | `GET /api/passport/project/:slug` | Project passport JSON API | +| `code/api/passport/project/[slug].ts` | `GET /api/passport/project/:slug` | Project passport JSON API | --- ## 5. DATABASE MIGRATIONS TO COPY -| File | Purpose | -|------|---------| +| File | Purpose | +| --------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `code/supabase/migrations/20250107_add_discord_integration.sql` | Discord tables (discord_links, discord_verifications, discord_role_mappings) | -| All other user/auth-related migrations | Copy all identity-related schema | +| All other user/auth-related migrations | Copy all identity-related schema | **Supabase Tables Required:** + - `user_profiles` - `user_auth_identities` - `discord_links` @@ -108,6 +119,7 @@ ## 6. LIBRARIES & DEPENDENCIES ### Required npm packages (verify in aethex.dev package.json) + ```json { "@supabase/supabase-js": "^2.x", @@ -124,6 +136,7 @@ ``` ### Environment Variables Needed + ``` VITE_SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co VITE_SUPABASE_ANON_KEY=sb_publishable_... @@ -138,14 +151,15 @@ VITE_API_BASE=https://aethex.foundation (after switchover) ## 7. CRITICAL ADAPTATIONS FOR REPLIT TARGET -| Current (aethex.dev) | Needed for aethex.foundation | -|----------------------|------------------------------| +| Current (aethex.dev) | Needed for aethex.foundation | +| ------------------------------------------ | ------------------------------------------- | | Vercel serverless functions (`code/api/*`) | Express or Remix server endpoints on Replit | -| `VITE_API_BASE=https://aethex.dev` | `VITE_API_BASE=https://aethex.foundation` | -| Vite + React on Vercel | Vite + React on Replit (same) | -| Uses Vercel environment variables | Use Replit Secrets or .env | +| `VITE_API_BASE=https://aethex.dev` | `VITE_API_BASE=https://aethex.foundation` | +| Vite + React on Vercel | Vite + React on Replit (same) | +| Uses Vercel environment variables | Use Replit Secrets or .env | ### Key Refactoring Points + 1. **API Endpoints:** Vercel's `/api/*` files may need conversion to Express routes in `code/server/index.ts` or equivalent Replit server. 2. **Base URLs:** Update all `VITE_API_BASE` references to point to `aethex.foundation` instead of `aethex.dev`. 3. **OAuth Redirect URIs:** Update Discord OAuth app to use `aethex.foundation` callback URL. @@ -158,6 +172,7 @@ VITE_API_BASE=https://aethex.foundation (after switchover) After copying existing code, build 3 new OAuth 2.0 endpoints on aethex.foundation: ### 1. `/authorize` (Foundation SSO Authorization) + **Purpose:** Initiate login flow for external apps (aethex.dev) ``` @@ -167,6 +182,7 @@ GET /authorize?client_id=AETHEX_DEV&redirect_uri=https://aethex.dev/auth/callbac **Response:** Redirect user to `/login` with state preserved ### 2. `/token` (Foundation SSO Token Exchange) + **Purpose:** Exchange auth code for JWT token ``` @@ -188,6 +204,7 @@ Returns: ``` ### 3. `/userinfo` (Foundation SSO User Info) + **Purpose:** Fetch current logged-in user info (used by aethex.dev after login) ``` @@ -211,6 +228,7 @@ Returns: ## 9. MIGRATION CHECKLIST ### Before Starting Phase 1 + - [ ] Verify all auth code is in `code/client/pages/` and `code/api/discord/*` - [ ] List all custom hooks used in auth flow (use-toast, etc.) - [ ] Document all Supabase queries used for auth @@ -218,10 +236,11 @@ Returns: - [ ] Create a "mirror" directory structure on aethex.foundation (Replit) ### During Phase 1 + - [ ] Copy all page files (Login, Signup, Onboarding, Dashboard, etc.) - [ ] Copy all context files (AuthContext, DiscordActivityContext, ThemeContext) - [ ] Copy all component files (OAuthConnections, PassportSummary, etc.) -- [ ] Copy all API endpoint files (discord/oauth/*, profile/ensure.ts, passport/*) +- [ ] Copy all API endpoint files (discord/oauth/_, profile/ensure.ts, passport/_) - [ ] Copy all Supabase migrations - [ ] Copy tailwind.config.js and global.css for styling - [ ] Adapt all import paths for new directory structure @@ -230,6 +249,7 @@ Returns: - [ ] Set up environment variables on Replit ### Testing Phase 1 + - [ ] Can users log in via Discord on aethex.foundation? - [ ] Can users view their profile? - [ ] Can users link additional OAuth providers? @@ -253,15 +273,15 @@ Returns: ## 11. ESTIMATED EFFORT -| Task | Estimate | -|------|----------| -| Audit & document auth code | 2-3 hours | -| Copy & adapt page files | 4-6 hours | -| Copy & adapt API endpoints | 3-4 hours | -| Fix imports & dependencies | 2-3 hours | -| Test login flow | 2-3 hours | -| Build SSO endpoints | 4-6 hours | -| **Total Phase 1** | **17-25 hours** | +| Task | Estimate | +| -------------------------- | --------------- | +| Audit & document auth code | 2-3 hours | +| Copy & adapt page files | 4-6 hours | +| Copy & adapt API endpoints | 3-4 hours | +| Fix imports & dependencies | 2-3 hours | +| Test login flow | 2-3 hours | +| Build SSO endpoints | 4-6 hours | +| **Total Phase 1** | **17-25 hours** | --- diff --git a/docs/DEPLOYMENT-CHECKLIST.md b/docs/DEPLOYMENT-CHECKLIST.md index e521695c..8bc8ef01 100644 --- a/docs/DEPLOYMENT-CHECKLIST.md +++ b/docs/DEPLOYMENT-CHECKLIST.md @@ -20,6 +20,7 @@ FOUNDATION_OAUTH_CLIENT_SECRET=bcoEtyQVGr6Z4557658eUXpDF5FDni2TGNahH3HT-FtylNrLC ``` **Important:** + - ✅ Keep `FOUNDATION_OAUTH_CLIENT_SECRET` **secure** (never commit to git) - ✅ Use deployment platform's secret management (Vercel > Settings > Environment Variables) - ✅ Mark secret variables as "Encrypted" @@ -30,11 +31,12 @@ Before deploying, confirm: - [ ] aethex.foundation is running and accessible - [ ] `/api/oauth/authorize` endpoint responding -- [ ] `/api/oauth/token` endpoint responding +- [ ] `/api/oauth/token` endpoint responding - [ ] `/api/oauth/userinfo` endpoint responding - [ ] OAuth credentials valid (client_id, client_secret) **Quick Test:** + ```bash # Test token endpoint curl -X POST https://aethex.foundation/api/oauth/token \ @@ -62,6 +64,7 @@ Ask Foundation admin to verify these are registered. ### Step 1: Set Environment Variables **Vercel:** + 1. Go to Project Settings > Environment Variables 2. Add three variables: - `VITE_FOUNDATION_URL` = `https://aethex.foundation` @@ -71,6 +74,7 @@ Ask Foundation admin to verify these are registered. 4. Save **Railway/Other:** + - Add to `.env` file in deployment - Or configure in platform's settings - Restart deployment for changes to take effect @@ -98,6 +102,7 @@ code/ ``` **Deploy command:** + ```bash # For Vercel vercel deploy --prod @@ -114,22 +119,26 @@ docker push /aethex-dev ### Step 3: Verify Deployment 1. **Check environment variables:** + ```bash # On deployed app, check logs for env var loading # Should see Foundation URL in console (not secret though!) ``` 2. **Visit login page:** + - Go to https://aethex.dev/login - Should see "Login with Foundation" button - No console errors 3. **Test OAuth flow:** + - Click "Login with Foundation" - Should redirect to https://aethex.foundation/api/oauth/authorize - Page should show Foundation login (or auth screen) 4. **Check callback endpoint:** + - Network tab should show POST to `/auth/callback/exchange` - Should return 200 with access_token @@ -162,6 +171,7 @@ ECONNREFUSED ⚠️ Foundation unreachabl ### Test 1: Happy Path (Successful Login) **Steps:** + 1. Visit https://aethex.dev/login 2. Click "Login with Foundation" 3. Enter test credentials on Foundation @@ -172,6 +182,7 @@ ECONNREFUSED ⚠️ Foundation unreachabl **Expected Result:** ✅ Logged in, cookies set, profile synced **Check:** + ```bash # In browser console: document.cookie # Should show foundation_access_token @@ -184,6 +195,7 @@ SELECT * FROM user_profiles WHERE email = ''; ### Test 2: Error: Invalid Code **Steps:** + 1. Manually modify callback URL: `?code=invalid_code_123` 2. Press Enter @@ -192,6 +204,7 @@ SELECT * FROM user_profiles WHERE email = ''; ### Test 3: Network Error **Steps:** + 1. Stop/pause Foundation service 2. Attempt login 3. Foundation redirects back with code @@ -202,6 +215,7 @@ SELECT * FROM user_profiles WHERE email = ''; ### Test 4: Logout and Re-login **Steps:** + 1. Logout from dashboard (if logout button exists) 2. Check cookies are cleared 3. Login again with Foundation @@ -212,6 +226,7 @@ SELECT * FROM user_profiles WHERE email = ''; ### Test 5: Multiple Browsers Test on: + - [ ] Chrome/Chromium - [ ] Firefox - [ ] Safari @@ -255,6 +270,7 @@ After 1-2 weeks of successful deployment: 1. Remove old Discord OAuth code (optional) 2. Delete deprecated files: + - `code/api/discord/oauth/start.ts` - `code/api/discord/oauth/callback.ts` - `code/api/discord/link.ts` @@ -273,15 +289,17 @@ If critical issues occur: ### Immediate Rollback (< 1 hour) 1. **Revert deployment:** + ```bash # Vercel vercel rollback - + # Railway railway rollback ``` 2. **Remove environment variables:** + - Remove VITE_FOUNDATION_URL - Remove FOUNDATION_OAUTH_CLIENT_ID - Remove FOUNDATION_OAUTH_CLIENT_SECRET @@ -293,6 +311,7 @@ If critical issues occur: ### If Rollback Fails Contact Foundation admin for assistance with: + - OAuth endpoint status - User session validation - Database consistency @@ -304,16 +323,19 @@ Contact Foundation admin for assistance with: ### Key Metrics to Monitor 1. **Auth Success Rate** + - Target: >99% - Alert threshold: <95% - What to check: Logs for "Token exchange" errors 2. **Token Exchange Time** + - Target: <500ms - Alert threshold: >2000ms - What to check: Network latency to Foundation 3. **Foundation Connectivity** + - Monitor: Foundation endpoint availability - Alert on: Connection failures to /api/oauth/token - Fallback: Maintenance page if Foundation down @@ -330,11 +352,11 @@ Contact Foundation admin for assistance with: // In application logs/dashboard: const metrics = { total_login_attempts: 1000, - successful_logins: 990, // 99% + successful_logins: 990, // 99% failed_token_exchange: 5, failed_user_sync: 2, failed_state_validation: 3, - avg_token_exchange_time_ms: 234 + avg_token_exchange_time_ms: 234, }; ``` @@ -344,13 +366,13 @@ const metrics = { ### Common Issues During Deployment -| Issue | Cause | Solution | -|-------|-------|----------| -| "Client secret not found" | Missing env var | Add FOUNDATION_OAUTH_CLIENT_SECRET | -| "Redirect URI mismatch" | URI not registered on Foundation | Ask Foundation admin to register | -| "Token exchange failed 401" | Invalid credentials | Verify client_id and client_secret | -| "User sync failed" | Supabase error | Check user_profiles table schema | -| "Cookies not set" | SameSite policy blocking | Check cookie headers on response | +| Issue | Cause | Solution | +| --------------------------- | -------------------------------- | ---------------------------------- | +| "Client secret not found" | Missing env var | Add FOUNDATION_OAUTH_CLIENT_SECRET | +| "Redirect URI mismatch" | URI not registered on Foundation | Ask Foundation admin to register | +| "Token exchange failed 401" | Invalid credentials | Verify client_id and client_secret | +| "User sync failed" | Supabase error | Check user_profiles table schema | +| "Cookies not set" | SameSite policy blocking | Check cookie headers on response | ### Debug Commands @@ -371,12 +393,14 @@ psql -c "SELECT COUNT(*) FROM user_profiles;" ### Getting Help 1. **Check logs:** + - Deployment platform logs (Vercel Dashboard, Railway Dashboard) - Application logs (if available) - Browser console (F12) - Network tab (check requests/responses) 2. **Verify configuration:** + - Environment variables set correctly - Foundation endpoints accessible - Redirect URI registered diff --git a/docs/FOUNDATION-OAUTH-IMPLEMENTATION.md b/docs/FOUNDATION-OAUTH-IMPLEMENTATION.md index d07e9967..7eaabea4 100644 --- a/docs/FOUNDATION-OAUTH-IMPLEMENTATION.md +++ b/docs/FOUNDATION-OAUTH-IMPLEMENTATION.md @@ -71,6 +71,7 @@ GET https://aethex.foundation/api/oauth/authorize ``` **Parameters:** + - `client_id` - aethex_corp (identifies this app) - `redirect_uri` - Where Foundation redirects after auth - `response_type` - Always "code" (OAuth 2.0 authorization code flow) @@ -98,6 +99,7 @@ grant_type=authorization_code ``` **Response:** + ```json { "access_token": "eyJ...", @@ -143,22 +145,26 @@ See `code/api/auth/callback.ts` → `fetchUserInfoFromFoundation()` ### How PKCE Works 1. **Client generates code verifier:** + ```javascript verifier = randomString(64 chars, URL-safe) // Example: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" ``` 2. **Client generates code challenge:** + ```javascript - challenge = base64url(SHA256(verifier)) + challenge = base64url(SHA256(verifier)); ``` 3. **Client sends challenge with authorization request:** + ``` GET /api/oauth/authorize?...&code_challenge=...&code_challenge_method=S256 ``` 4. **Server stores challenge, validates with verifier on token exchange:** + ``` POST /api/oauth/token?...&code_verifier=... ``` @@ -169,6 +175,7 @@ See `code/api/auth/callback.ts` → `fetchUserInfoFromFoundation()` ``` **Why PKCE?** + - Prevents authorization code interception attacks - Secure for mobile apps and single-page applications - Required by OAuth 2.1 best practices @@ -193,6 +200,7 @@ Updated to show Foundation OAuth button: ``` Features: + - Initiates Foundation OAuth flow - Generates PKCE parameters - Stores verifier and state in sessionStorage @@ -204,16 +212,16 @@ Core OAuth functionality: ```typescript // Generate PKCE parameters -async function generatePKCEParams(): Promise<{ verifier, challenge }> +async function generatePKCEParams(): Promise<{ verifier; challenge }>; // Build authorization URL -async function getFoundationAuthorizationUrl(options?): Promise +async function getFoundationAuthorizationUrl(options?): Promise; // Initiate login -async function initiateFoundationLogin(redirectTo?: string): Promise +async function initiateFoundationLogin(redirectTo?: string): Promise; // Exchange code for token (called from backend) -async function exchangeCodeForToken(code: string): Promise +async function exchangeCodeForToken(code: string): Promise; ``` #### 3. useFoundationAuth Hook (`code/client/hooks/use-foundation-auth.ts`) @@ -235,6 +243,7 @@ useFoundationAuthStatus(): { isAuthenticated, userId } **Route:** `GET /auth/callback?code=...&state=...` **Flow:** + 1. Receive authorization code from Foundation 2. Validate state token (CSRF protection) 3. Exchange code for access token @@ -244,29 +253,30 @@ useFoundationAuthStatus(): { isAuthenticated, userId } 7. Redirect to dashboard **Code:** + ```typescript async function handleCallback(req, res) { // 1. Get code from URL const { code, state } = req.query; - + // 2. Validate state validateState(state); - + // 3. Exchange for token const token = await performTokenExchange(code); - + // 4. Fetch user info const user = await fetchUserInfoFromFoundation(token); - + // 5. Sync to database await syncUserToLocalDatabase(user); - + // 6. Set cookies res.setHeader("Set-Cookie", [ `foundation_access_token=${token}; ...`, - `auth_user_id=${user.id}; ...` + `auth_user_id=${user.id}; ...`, ]); - + // 7. Redirect return res.redirect("/dashboard"); } @@ -279,7 +289,7 @@ async function handleCallback(req, res) { ```typescript async function handleTokenExchange(req, res) { const { code } = req.body; - + // Exchange code with Foundation // Fetch user info // Sync to database @@ -314,16 +324,16 @@ For authenticated API requests: ```typescript // Get token from cookie const token = document.cookie - .split(';') - .find(c => c.trim().startsWith('foundation_access_token=')) - ?.split('=')[1]; + .split(";") + .find((c) => c.trim().startsWith("foundation_access_token=")) + ?.split("=")[1]; // Use in requests -fetch('/api/user/profile', { +fetch("/api/user/profile", { headers: { - 'Authorization': `Bearer ${token}` + Authorization: `Bearer ${token}`, }, - credentials: 'include' + credentials: "include", }); ``` @@ -333,13 +343,14 @@ On logout: ```typescript // Clear cookies -document.cookie = 'foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; -document.cookie = 'auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; +document.cookie = + "foundation_access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; +document.cookie = "auth_user_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC;"; // Optional: Notify Foundation of logout await fetch(`${FOUNDATION_URL}/api/oauth/logout`, { - method: 'POST', - headers: { 'Authorization': `Bearer ${token}` } + method: "POST", + headers: { Authorization: `Bearer ${token}` }, }); ``` @@ -376,13 +387,13 @@ Corp Local Database (user_profiles table): ```typescript await supabase.from("user_profiles").upsert({ - id: foundationUser.id, // Match by ID + id: foundationUser.id, // Match by ID email: foundationUser.email, username: foundationUser.username, full_name: foundationUser.full_name, avatar_url: foundationUser.avatar_url, profile_completed: foundationUser.profile_complete, - updated_at: new Date().toISOString() + updated_at: new Date().toISOString(), }); ``` @@ -395,6 +406,7 @@ await supabase.from("user_profiles").upsert({ ### Local Testing 1. **Set up environment:** + ```bash export VITE_FOUNDATION_URL=http://localhost:3001 # Or staging URL export FOUNDATION_OAUTH_CLIENT_ID=aethex_corp @@ -402,6 +414,7 @@ await supabase.from("user_profiles").upsert({ ``` 2. **Test flow:** + - Visit `http://localhost:5173/login` - Click "Login with Foundation" - Should redirect to Foundation auth page @@ -417,6 +430,7 @@ await supabase.from("user_profiles").upsert({ ### Error Scenarios **Invalid code:** + ``` GET /auth/callback?code=invalid → 400 "Token exchange failed" @@ -424,6 +438,7 @@ GET /auth/callback?code=invalid ``` **Invalid state:** + ``` GET /auth/callback?code=...&state=wrong_state → Error "Invalid state token - possible CSRF attack" @@ -431,6 +446,7 @@ GET /auth/callback?code=...&state=wrong_state ``` **Foundation down:** + ``` POST /api/oauth/token → ECONNREFUSED → Error "Failed to exchange code" @@ -442,6 +458,7 @@ POST /api/oauth/token → ECONNREFUSED ## Files Modified/Created ### New Files + ``` code/ ├── client/ @@ -454,6 +471,7 @@ code/ ``` ### Modified Files + ``` code/ └── client/ @@ -461,7 +479,9 @@ code/ ``` ### Deprecated Files + These can be removed after testing completes: + ``` code/api/discord/oauth/start.ts code/api/discord/oauth/callback.ts @@ -492,14 +512,17 @@ code/api/discord/verify-code.ts ### Key Metrics 1. **Auth Success Rate** + - Target: >99% - Alert if: <95% 2. **Token Exchange Time** + - Target: <500ms - Alert if: >2s 3. **Error Categories** + - Track: invalid_code, state_mismatch, timeout, etc. 4. **Foundation Connectivity** @@ -528,6 +551,7 @@ Key points to log: **Cause:** Foundation didn't redirect back properly **Solutions:** + 1. Check `redirect_uri` matches exactly (case-sensitive) 2. Verify Foundation OAuth settings 3. Check browser console for JavaScript errors @@ -537,6 +561,7 @@ Key points to log: **Cause:** CSRF validation failed **Solutions:** + 1. Check that state is generated consistently 2. Verify sessionStorage isn't cleared between redirect 3. Check for multiple browser tabs (different state per tab) @@ -546,6 +571,7 @@ Key points to log: **Cause:** Foundation token endpoint unavailable or code invalid **Solutions:** + 1. Check Foundation is running and `/api/oauth/token` accessible 2. Verify `client_id` and `client_secret` are correct 3. Check code hasn't expired (usually 10 minutes) @@ -556,6 +582,7 @@ Key points to log: **Cause:** Database error during sync **Solutions:** + 1. Check `user_profiles` table exists and has proper schema 2. Verify Supabase connection and permissions 3. Check user_id isn't duplicated in database diff --git a/docs/PHASE1-FILES.json b/docs/PHASE1-FILES.json index 9da48382..91644e68 100644 --- a/docs/PHASE1-FILES.json +++ b/docs/PHASE1-FILES.json @@ -157,7 +157,11 @@ "source": "code/supabase/migrations/20250107_add_discord_integration.sql", "type": "migration", "priority": "high", - "tables": ["discord_links", "discord_verifications", "discord_role_mappings"] + "tables": [ + "discord_links", + "discord_verifications", + "discord_role_mappings" + ] } ], "config": [ diff --git a/docs/PHASE1-README.md b/docs/PHASE1-README.md index c35b5062..8d9f05a2 100644 --- a/docs/PHASE1-README.md +++ b/docs/PHASE1-README.md @@ -1,22 +1,29 @@ # Axiom Model - Phase 1 Migration + ## How to Use This Package You have 3 files to help with the Phase 1 code migration: ### 1. **PHASE1-CHECKLIST.txt** ← START HERE + Simple, easy-to-read checklist of all files to copy. + - ✅ Just check off items as you copy them - 📋 Best for quick reference while working - 👥 Share this with your team ### 2. **PHASE1-FILES.json** + Machine-readable list of all files with metadata. + - 🤖 Use this if you want to script/automate the copy - 📊 Includes priority levels, categories, and notes - 💻 Parse this in your favorite tool ### 3. **AXIOM-MODEL-PHASE1-SCOPE.md** + Complete detailed documentation. + - 📖 Full explanation of every file and why it matters - 🎯 Includes adaptation notes and potential blockers - 🔍 Reference this if you get stuck on something @@ -43,12 +50,15 @@ Complete detailed documentation. --- ## Estimated Time + **17-25 hours** for complete Phase 1 migration --- ## Questions? + Refer to AXIOM-MODEL-PHASE1-SCOPE.md sections: + - **Import issues?** → Section 7: Libraries & Dependencies - **What file goes where?** → Sections 1-5: Complete file listing - **How do I adapt?** → Section 7: Critical Adaptations @@ -57,11 +67,14 @@ Refer to AXIOM-MODEL-PHASE1-SCOPE.md sections: --- ## Next: Phase 2 + Once Phase 1 is complete, Phase 2 involves: + - Supabase permission migration (Foundation gets full access) - `aethex.dev` loses direct write access to user_profiles Then Phase 3: + - Reroute `aethex.dev` login → `aethex.foundation` (SSO) **Done!** The Axiom Model is live. diff --git a/docs/PHASE3-FINAL-IMPLEMENTATION-SUMMARY.md b/docs/PHASE3-FINAL-IMPLEMENTATION-SUMMARY.md index 6bc25f73..048e4acc 100644 --- a/docs/PHASE3-FINAL-IMPLEMENTATION-SUMMARY.md +++ b/docs/PHASE3-FINAL-IMPLEMENTATION-SUMMARY.md @@ -58,6 +58,7 @@ Foundation Endpoints: ### New Implementation Files #### Frontend OAuth Client (`code/client/lib/foundation-oauth.ts`) + ✅ **Implements PKCE (Proof Key for Code Exchange)** - Generates code verifier (64-char random, URL-safe) @@ -68,14 +69,16 @@ Foundation Endpoints: - Stores verifier/state in sessionStorage **Key Functions:** + ```typescript -getFoundationAuthorizationUrl() // Build auth URL -initiateFoundationLogin() // Redirect to Foundation -exchangeCodeForToken() // Exchange code (called from backend) -validateState() // CSRF validation +getFoundationAuthorizationUrl(); // Build auth URL +initiateFoundationLogin(); // Redirect to Foundation +exchangeCodeForToken(); // Exchange code (called from backend) +validateState(); // CSRF validation ``` #### Token & Cookie Management (`code/client/lib/foundation-auth.ts`) + ✅ **Handles session cookies and authentication state** - Get/check Foundation access token from cookies @@ -85,16 +88,18 @@ validateState() // CSRF validation - Logout notification to Foundation **Key Functions:** + ```typescript -getFoundationAccessToken() // Get JWT from cookie -getAuthUserId() // Get user UUID from cookie -isFoundationAuthenticated() // Check auth status -clearFoundationAuth() // Logout -makeAuthenticatedRequest() // API call with token -logoutFromFoundation() // Full logout flow +getFoundationAccessToken(); // Get JWT from cookie +getAuthUserId(); // Get user UUID from cookie +isFoundationAuthenticated(); // Check auth status +clearFoundationAuth(); // Logout +makeAuthenticatedRequest(); // API call with token +logoutFromFoundation(); // Full logout flow ``` #### OAuth Callback Hook (`code/client/hooks/use-foundation-auth.ts`) + ✅ **Detects OAuth callback and handles token exchange** - Detects authorization code in URL @@ -105,16 +110,20 @@ logoutFromFoundation() // Full logout flow - Error handling with user feedback **Key Functions:** + ```typescript -useFoundationAuth() // Process OAuth callback -useFoundationAuthStatus() // Check auth status +useFoundationAuth(); // Process OAuth callback +useFoundationAuthStatus(); // Check auth status ``` #### OAuth Callback Handler (`code/api/auth/callback.ts`) + ✅ **Backend endpoint for OAuth flow completion** **Two routes:** + 1. `GET /auth/callback?code=...&state=...` + - Receives authorization code from Foundation - Validates state (CSRF) - Exchanges code for token @@ -130,15 +139,17 @@ useFoundationAuthStatus() // Check auth status - Sets secure cookies **Key Functions:** + ```typescript -handleCallback() // GET /auth/callback -handleTokenExchange() // POST /auth/callback/exchange -performTokenExchange() // Code → token exchange -fetchUserInfoFromFoundation() // Fetch user profile -syncUserToLocalDatabase() // Upsert to local DB +handleCallback(); // GET /auth/callback +handleTokenExchange(); // POST /auth/callback/exchange +performTokenExchange(); // Code → token exchange +fetchUserInfoFromFoundation(); // Fetch user profile +syncUserToLocalDatabase(); // Upsert to local DB ``` #### Updated Login Page (`code/client/pages/Login.tsx`) + ✅ **New Foundation OAuth button** - Added "Login with Foundation" button (primary option) @@ -147,6 +158,7 @@ syncUserToLocalDatabase() // Upsert to local DB - Discord now managed by Foundation instead **Changes:** + ```typescript // NEW ``` **New button added:** + ```javascript