This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix p

+163 -29
+3
app/public/client-metadata.json
··· 7 7 "tos_uri": "https://flushes.app/terms", 8 8 "policy_uri": "https://flushes.app/privacy", 9 9 "dpop_bound_access_tokens": true, 10 + "authorization_endpoint": "https://bsky.social/oauth/authorize", 11 + "token_endpoint": "https://bsky.social/oauth/token", 12 + "issuer": "https://bsky.social", 10 13 "grant_types": [ 11 14 "authorization_code", 12 15 "refresh_token"
+73 -19
app/src/app/api/auth/token/route.ts
··· 31 31 const body = await request.json(); 32 32 const { code, codeVerifier, dpopToken, pdsEndpoint, originalPdsEndpoint } = body; 33 33 34 + // Enhanced logging 35 + console.log('[TOKEN ROUTE] Request parameters:', { 36 + code: code ? code.substring(0, 6) + '...' : 'none', // Only log first few chars of sensitive data 37 + codeVerifier: codeVerifier ? codeVerifier.substring(0, 6) + '...' : 'none', 38 + pdsEndpoint, 39 + originalPdsEndpoint, 40 + dpopTokenProvided: !!dpopToken 41 + }); 42 + 34 43 // Use the provided PDS endpoint or default to Bluesky's 35 44 // CRITICAL FIX: For third-party PDS, always use bsky.social for token requests 36 45 let authServer = pdsEndpoint || DEFAULT_AUTH_SERVER; 37 46 if (pdsEndpoint && !pdsEndpoint.includes('bsky.social')) { 38 - console.log(`Redirecting token request to bsky.social for third-party PDS: ${pdsEndpoint}`); 47 + console.log(`[TOKEN ROUTE] Redirecting token request to bsky.social for third-party PDS: ${pdsEndpoint}`); 39 48 authServer = DEFAULT_AUTH_SERVER; 40 49 } 41 50 42 51 if (!code || !codeVerifier || !dpopToken) { 52 + const missingParams = []; 53 + if (!code) missingParams.push('code'); 54 + if (!codeVerifier) missingParams.push('codeVerifier'); 55 + if (!dpopToken) missingParams.push('dpopToken'); 56 + 57 + console.error(`[TOKEN ROUTE] Missing required parameters: ${missingParams.join(', ')}`); 43 58 return NextResponse.json( 44 - { error: 'Missing required parameters' }, 59 + { error: 'Missing required parameters', missing: missingParams }, 45 60 { status: 400 } 46 61 ); 47 62 } 48 63 49 64 // Get a nonce from the specified PDS 50 65 const nonce = await getNonce(authServer); 51 - console.log(`Got nonce from server-side (${authServer}):`, nonce); 66 + console.log(`[TOKEN ROUTE] Got nonce from server-side (${authServer}):`, nonce); 52 67 53 68 // Forward the token request to the specified PDS 54 69 const tokenEndpoint = `${authServer}/oauth/token`; 55 - console.log(`Making token request to: ${tokenEndpoint}`); 70 + console.log(`[TOKEN ROUTE] Making token request to: ${tokenEndpoint}`); 56 71 57 72 // Prepare the form data 58 73 const formData = new URLSearchParams({ ··· 63 78 code_verifier: codeVerifier 64 79 }); 65 80 66 - // For third-party PDS, add the 'resource' parameter with the original PDS endpoint 67 - // This is CRITICAL for the token exchange to work with third-party PDS servers 81 + // For third-party PDS, add the 'resource' AND 'issuer' parameters 82 + // These are CRITICAL for the token exchange to work with third-party PDS servers 68 83 if (originalPdsEndpoint && originalPdsEndpoint !== authServer) { 69 - console.log(`Adding resource parameter for third-party PDS: ${originalPdsEndpoint}`); 84 + console.log(`[TOKEN ROUTE] Adding resource parameter for third-party PDS: ${originalPdsEndpoint}`); 70 85 formData.append('resource', originalPdsEndpoint); 86 + 87 + // Add the issuer parameter which is required for cross-domain OAuth 88 + console.log(`[TOKEN ROUTE] Adding issuer parameter for third-party PDS: ${originalPdsEndpoint}`); 89 + formData.append('issuer', originalPdsEndpoint); 71 90 } 72 91 92 + // Log the complete request for debugging 93 + console.log('[TOKEN ROUTE] Complete token request:', { 94 + url: tokenEndpoint, 95 + method: 'POST', 96 + headers: { 97 + 'Content-Type': 'application/x-www-form-urlencoded', 98 + 'DPoP': dpopToken ? '[TOKEN PRESENT]' : '[MISSING]' 99 + }, 100 + formData: Object.fromEntries(formData) 101 + }); 102 + 73 103 const response = await fetch(tokenEndpoint, { 74 104 method: 'POST', 75 105 headers: { ··· 80 110 body: formData 81 111 }); 82 112 113 + // Log response status and headers 114 + console.log(`[TOKEN ROUTE] Response status: ${response.status}`); 115 + console.log('[TOKEN ROUTE] Response headers:', Object.fromEntries([...response.headers.entries()])); 116 + 83 117 // Get the response data 84 118 const responseData = await response.json(); 85 119 120 + // Log complete error response for debugging 121 + if (!response.ok) { 122 + console.error('[TOKEN ROUTE] Token request failed with status:', response.status); 123 + console.error('[TOKEN ROUTE] Error response:', responseData); 124 + 125 + // For invalid_grant errors, provide more context 126 + if (responseData.error === 'invalid_grant') { 127 + console.error(`[TOKEN ROUTE] Invalid grant error details: 128 + - The authorization code might have expired 129 + - The code_verifier might not match what was used for code_challenge 130 + - For third-party PDS: resource parameter might be incorrect 131 + - Client ID might not match what was used in authorization request 132 + - Redirect URI might not match what was used in authorization request 133 + `); 134 + } 135 + } 136 + 86 137 // If there's an error about missing nonce, return the nonce 87 138 if (responseData.error === 'use_dpop_nonce') { 88 139 const dpopNonce = response.headers.get('DPoP-Nonce'); 140 + console.log(`[TOKEN ROUTE] Got DPoP-Nonce from error response: ${dpopNonce}`); 89 141 return NextResponse.json( 90 142 { 91 143 error: 'use_dpop_nonce', ··· 96 148 ); 97 149 } 98 150 99 - // Log the token response for debugging 100 - console.log('Token response from Bluesky:', JSON.stringify({ 101 - ...responseData, 102 - access_token: responseData.access_token ? '[REDACTED]' : null, 103 - refresh_token: responseData.refresh_token ? '[REDACTED]' : null, 104 - })); 105 - 106 - // Check if we have an audience in the token 107 - if (responseData.aud) { 108 - console.log('Token audience:', responseData.aud); 109 - } else { 110 - console.warn('No audience in token response'); 151 + // Log the token response for debugging (with sensitive data redacted) 152 + if (response.ok) { 153 + console.log('[TOKEN ROUTE] Token response from Bluesky:', JSON.stringify({ 154 + ...responseData, 155 + access_token: responseData.access_token ? '[REDACTED]' : null, 156 + refresh_token: responseData.refresh_token ? '[REDACTED]' : null, 157 + })); 158 + 159 + // Check if we have an audience in the token 160 + if (responseData.aud) { 161 + console.log('[TOKEN ROUTE] Token audience:', responseData.aud); 162 + } else { 163 + console.warn('[TOKEN ROUTE] No audience in token response'); 164 + } 111 165 } 112 166 113 167 // Return the response
+68 -9
app/src/app/auth/callback/page.tsx
··· 48 48 const code = searchParams.get('code'); 49 49 const state = searchParams.get('state'); 50 50 const iss = searchParams.get('iss'); 51 + const error = searchParams.get('error'); 52 + const errorDescription = searchParams.get('error_description'); 53 + 54 + // Log all URL parameters for debugging 55 + console.log('Callback URL parameters:', { 56 + code: code ? code.substring(0, 6) + '...' : null, 57 + state: state ? state.substring(0, 5) + '...' : null, 58 + iss, 59 + error, 60 + errorDescription, 61 + // Log any additional parameters 62 + allParams: Object.fromEntries(searchParams) 63 + }); 64 + 65 + // Check for error parameters in the callback 66 + if (error) { 67 + console.error(`OAuth error in callback: ${error} - ${errorDescription}`); 68 + setError(`Authentication error: ${errorDescription || error}`); 69 + return; 70 + } 51 71 52 72 if (!code || !state || !iss) { 53 - setError('Invalid callback parameters'); 73 + const missing = []; 74 + if (!code) missing.push('code'); 75 + if (!state) missing.push('state'); 76 + if (!iss) missing.push('iss'); 77 + 78 + console.error(`Missing required callback parameters: ${missing.join(', ')}`); 79 + setError(`Invalid callback parameters. Missing: ${missing.join(', ')}`); 54 80 return; 55 81 } 56 82 57 83 // Get stored values from our robust storage utility 58 84 if (typeof window === 'undefined') { 85 + console.error('Browser environment not available'); 59 86 setError('Browser environment not available'); 60 87 return; 61 88 } ··· 63 90 const storedState = retrieveAuthData('oauth_state'); 64 91 const codeVerifier = retrieveAuthData('code_verifier'); 65 92 const serializedKeyPair = retrieveAuthData('key_pair'); 93 + 94 + // Log stored auth data for debugging 95 + console.log('Stored auth data:', { 96 + storedStateExists: !!storedState, 97 + storedStatePrefix: storedState ? storedState.substring(0, 5) + '...' : null, 98 + codeVerifierExists: !!codeVerifier, 99 + codeVerifierLength: codeVerifier ? codeVerifier.length : 0, 100 + serializedKeyPairExists: !!serializedKeyPair, 101 + storageFunctioning: typeof localStorage !== 'undefined' && typeof sessionStorage !== 'undefined' 102 + }); 66 103 67 104 // Check if we have the stored values 68 105 if (!storedState) { 69 - setError('Session data lost. Please try logging in again.'); 106 + console.error('Session state data lost. Storage might be disabled or corrupted.'); 107 + setError('Session data lost. Please try logging in again, ensuring cookies and local storage are enabled.'); 70 108 return; 71 109 } 72 110 73 111 // Validate state 74 112 if (state !== storedState) { 75 - console.error('State mismatch. Received:', state, 'Stored:', storedState); 113 + console.error('State mismatch:', { 114 + received: state ? state.substring(0, 10) + '...' : null, 115 + stored: storedState ? storedState.substring(0, 10) + '...' : null, 116 + match: state === storedState 117 + }); 76 118 setError('Invalid state parameter. This may be due to an expired session or a security issue.'); 77 119 return; 78 120 } 79 121 122 + // Validate the rest of the auth data 80 123 if (!codeVerifier || !serializedKeyPair) { 81 - setError('Missing authorization data'); 124 + const missing = []; 125 + if (!codeVerifier) missing.push('code_verifier'); 126 + if (!serializedKeyPair) missing.push('key_pair'); 127 + 128 + console.error(`Missing authorization data: ${missing.join(', ')}`); 129 + setError(`Missing authorization data: ${missing.join(', ')}. Please try logging in again.`); 82 130 return; 83 131 } 84 132 ··· 117 165 console.log('Exchanging code for token...'); 118 166 let tokenResponse; 119 167 try { 120 - // CRITICAL FIX: For third-party PDS, we need to use bsky.social for token exchange 121 - // But we must pass the original PDS endpoint (iss param) as well 168 + // CRITICAL FIX: For third-party PDS, we need special handling for token exchange 122 169 let authServer = storedAuthServer || 'https://bsky.social'; 123 170 let tokenPdsEndpoint = storedPdsEndpoint; 124 171 ··· 129 176 tokenPdsEndpoint = iss; 130 177 // Store this for later use 131 178 storeAuthData('pds_endpoint', iss); 179 + 180 + // For third-party PDS, we need to ensure we're using the right auth server 181 + if (!iss.includes('bsky.social')) { 182 + // Always use bsky.social for token exchange with third-party PDS 183 + authServer = 'https://bsky.social'; 184 + console.log('Third-party PDS detected, using bsky.social as auth server'); 185 + 186 + // Also store the auth server 187 + storeAuthData('auth_server', authServer); 188 + } 132 189 } 133 190 134 - // Always use bsky.social for token exchange (even for custom PDS endpoints) 135 - console.log('Using auth server for token exchange:', authServer); 136 - console.log('Original PDS endpoint (iss):', tokenPdsEndpoint); 191 + console.log('Authentication servers:', { 192 + authServer, 193 + originalPdsEndpoint: tokenPdsEndpoint, 194 + isThirdPartyPds: tokenPdsEndpoint !== authServer 195 + }); 137 196 138 197 // Convert null to undefined for type compatibility 139 198 const originalPdsEndpoint = tokenPdsEndpoint === null ? undefined : tokenPdsEndpoint;
+19 -1
app/src/app/auth/login/page.tsx
··· 55 55 56 56 // For bsky.network endpoints, use the default AUTH_SERVER (bsky.social) 57 57 // For other PDS servers, use their actual endpoint 58 - let authUrl, state, codeVerifier, keyPair; 58 + let authUrl, state, codeVerifier, keyPair, authorizationEndpoint; 59 59 60 60 if (isBskyNetwork) { 61 61 console.log('Using standard Bluesky OAuth flow for bsky.network PDS'); ··· 65 65 state = authData.state; 66 66 codeVerifier = authData.codeVerifier; 67 67 keyPair = authData.keyPair; 68 + authorizationEndpoint = authData.pdsEndpoint; 69 + 70 + console.log('Standard OAuth flow details:', { 71 + pdsType: 'bsky.network', 72 + authEndpoint: authorizationEndpoint, 73 + statePrefix: state.substring(0, 5) + '...', 74 + codeVerifierLength: codeVerifier.length 75 + }); 68 76 } else { 69 77 console.log('Using custom PDS OAuth flow for:', pdsEndpoint); 70 78 // Use the custom PDS endpoint for OAuth ··· 73 81 state = authData.state; 74 82 codeVerifier = authData.codeVerifier; 75 83 keyPair = authData.keyPair; 84 + authorizationEndpoint = authData.pdsEndpoint; 85 + 86 + console.log('Custom PDS OAuth flow details:', { 87 + pdsType: 'third-party', 88 + pdsEndpoint, 89 + authEndpoint: authorizationEndpoint, 90 + statePrefix: state.substring(0, 5) + '...', 91 + codeVerifierLength: codeVerifier.length, 92 + redirectUri: 'https://flushes.app/auth/callback' // Expected to be this 93 + }); 76 94 } 77 95 78 96 // Store auth state