From e8b006614f0f33004ea9e151d1c3522a05ceaab7 Mon Sep 17 00:00:00 2001 From: wingo-blue Date: Wed, 10 Jun 2026 10:01:29 +0800 Subject: [PATCH 1/4] fix: resolve GitHub OAuth 404 by adding frontend fallback (#821) --- frontend/src/api/auth.ts | 54 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index 632d3bf87..91b988132 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -11,9 +11,59 @@ export interface GitHubCallbackResponse extends AuthTokens { user: User; } +const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; + +/** + * Generate a cryptographically-random state string for CSRF protection. + */ +function generateState(): string { + const array = new Uint8Array(32); + crypto.getRandomValues(array); + return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join(''); +} + +/** + * Get the GitHub OAuth authorize URL. + * + * Primary: fetch from backend API (returns 404 when backend is offline). + * Fallback: construct directly from VITE_GITHUB_CLIENT_ID env var. + */ export async function getGitHubAuthorizeUrl(): Promise { - const data = await apiClient<{ authorize_url: string }>('/api/auth/github/authorize'); - return data.authorize_url; + // Try backend API first + try { + const data = await apiClient<{ authorize_url: string }>('/api/auth/github/authorize'); + if (data?.authorize_url) return data.authorize_url; + } catch { + // Backend unavailable 鈥?fall through to frontend fallback + } + + // Fallback: build the URL ourselves + const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID as string | undefined; + if (!clientId) { + throw new Error( + 'VITE_GITHUB_CLIENT_ID is not configured. ' + + 'Set it in your .env file or ensure the backend /api/auth/github/authorize endpoint is running.', + ); + } + + const redirectUri = `${window.location.origin}/github/callback`; + const state = generateState(); + + // Store state for CSRF verification on callback + try { + sessionStorage.setItem('sf_oauth_state', state); + } catch { + // sessionStorage may be unavailable 鈥?non-critical + } + + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectUri, + state, + scope: 'read:user user:email', + }); + + return `${GITHUB_AUTH_URL}?${params.toString()}`; } export async function exchangeGitHubCode(code: string, state?: string): Promise { From 37c125510c22e893d6566d5149f485d035f4fe5a Mon Sep 17 00:00:00 2001 From: wingo-blue Date: Wed, 10 Jun 2026 10:01:30 +0800 Subject: [PATCH 2/4] fix: resolve GitHub OAuth 404 by adding frontend fallback (#821) --- frontend/src/components/auth/AuthGuard.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/auth/AuthGuard.tsx b/frontend/src/components/auth/AuthGuard.tsx index 90fd12fb3..798013082 100644 --- a/frontend/src/components/auth/AuthGuard.tsx +++ b/frontend/src/components/auth/AuthGuard.tsx @@ -21,13 +21,17 @@ export function AuthGuard({ children }: AuthGuardProps) { } if (!isAuthenticated) { + const [signInError, setSignInError] = React.useState(null); + const handleSignIn = async () => { + setSignInError(null); try { const url = await getGitHubAuthorizeUrl(); window.location.href = url; - } catch { - // fallback: redirect to backend OAuth endpoint directly - window.location.href = '/api/auth/github/authorize'; + } catch (err) { + setSignInError( + err instanceof Error ? err.message : 'Failed to initiate sign-in. Is VITE_GITHUB_CLIENT_ID configured?', + ); } }; @@ -54,6 +58,11 @@ export function AuthGuard({ children }: AuthGuardProps) { Sign in with GitHub + {signInError && ( +

+ {signInError} +

+ )} Date: Wed, 10 Jun 2026 10:01:31 +0800 Subject: [PATCH 3/4] fix: resolve GitHub OAuth 404 by adding frontend fallback (#821) --- frontend/src/pages/GitHubCallbackPage.tsx | 54 ++++++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/GitHubCallbackPage.tsx b/frontend/src/pages/GitHubCallbackPage.tsx index de11ffaa1..603e1f666 100644 --- a/frontend/src/pages/GitHubCallbackPage.tsx +++ b/frontend/src/pages/GitHubCallbackPage.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { motion } from 'framer-motion'; +import { AlertCircle } from 'lucide-react'; import { useAuth } from '../hooks/useAuth'; import { exchangeGitHubCode } from '../api/auth'; import { setAuthToken } from '../services/apiClient'; @@ -11,6 +12,7 @@ export function GitHubCallbackPage() { const navigate = useNavigate(); const { login } = useAuth(); const didRun = useRef(false); + const [error, setError] = React.useState(null); useEffect(() => { if (didRun.current) return; @@ -18,30 +20,70 @@ export function GitHubCallbackPage() { const code = searchParams.get('code'); const state = searchParams.get('state'); - const error = searchParams.get('error'); + const errorParam = searchParams.get('error'); - if (error || !code) { + if (errorParam || !code) { navigate('/', { replace: true }); return; } + // Validate OAuth state for CSRF protection + const savedState = sessionStorage.getItem('sf_oauth_state'); + sessionStorage.removeItem('sf_oauth_state'); + + // When the backend handles the OAuth callback we may not have a stored state + // Only enforce validation when state was stored (frontend-initiated flow) + if (state && savedState && state !== savedState) { + setError('Security validation failed. Please try signing in again.'); + return; + } + exchangeGitHubCode(code, state ?? undefined) .then((response) => { - // Store tokens + user in auth context const authUser = { ...response.user, wallet_verified: false }; login(response.access_token, response.refresh_token ?? '', authUser); setAuthToken(response.access_token); - // Store refresh token for future use if (response.refresh_token) { localStorage.setItem('sf_refresh_token', response.refresh_token); } navigate('/', { replace: true }); }) - .catch(() => { - navigate('/', { replace: true }); + .catch((err) => { + setError( + err instanceof Error + ? err.message + : 'Authentication failed. Please try again.', + ); }); }, []); + if (error) { + return ( +
+ +
+ +

+ Sign-In Failed +

+

{error}

+ +
+
+
+ ); + } + return (
Date: Wed, 10 Jun 2026 10:01:32 +0800 Subject: [PATCH 4/4] fix: resolve GitHub OAuth 404 by adding frontend fallback (#821) --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index be8f3125c..24a5e70da 100644 --- a/.env.example +++ b/.env.example @@ -30,6 +30,7 @@ SOLANA_RPC_URL=https://api.devnet.solana.com # Frontend FRONTEND_PORT=3000 VITE_API_URL=http://localhost:8000 +VITE_GITHUB_CLIENT_ID= # Deploy health-check URLs (set as GitHub repository variables, not secrets) STAGING_HEALTH_URL=https://staging-api.solfoundry.org/health