This repository has no description
0

Configure Feed

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

fix auth issues

+352 -109
.DS_Store

This is a binary file and will not be displayed.

+140 -56
app/src/app/api/auth/nonce/route.ts
··· 13 13 14 14 // Try to get a nonce from the specified PDS 15 15 const tokenEndpoint = `${pdsEndpoint}/oauth/token`; 16 - console.log(`Attempting to get nonce from: ${tokenEndpoint}`); 16 + console.log(`[NONCE API] Attempting to get nonce from: ${tokenEndpoint}`); 17 17 18 - const response = await fetch(tokenEndpoint, { 19 - method: 'HEAD', 20 - headers: { 21 - 'Accept': '*/*' 22 - } 23 - }); 18 + // Try multiple methods to get a nonce 19 + let nonce = null; 24 20 25 - const nonce = response.headers.get('DPoP-Nonce'); 26 - 27 - if (nonce) { 28 - return NextResponse.json({ nonce }); 29 - } else { 30 - // Try another method if HEAD doesn't work 31 - const probeResponse = await fetch(tokenEndpoint, { 32 - method: 'POST', 21 + // Method 1: HEAD request (most efficient) 22 + try { 23 + console.log(`[NONCE API] Trying HEAD request to ${tokenEndpoint}`); 24 + const headResponse = await fetch(tokenEndpoint, { 25 + method: 'HEAD', 33 26 headers: { 34 - 'Content-Type': 'application/x-www-form-urlencoded' 35 - }, 36 - // Empty body to trigger an error response that might contain a nonce 37 - body: new URLSearchParams({}) 27 + 'Accept': '*/*' 28 + } 38 29 }); 39 30 40 - const probeNonce = probeResponse.headers.get('DPoP-Nonce'); 41 - 42 - if (probeNonce) { 43 - return NextResponse.json({ nonce: probeNonce }); 44 - } else { 45 - return NextResponse.json( 46 - { error: 'Could not retrieve nonce', endpoint: tokenEndpoint }, 47 - { status: 404 } 48 - ); 31 + nonce = headResponse.headers.get('DPoP-Nonce'); 32 + if (nonce) { 33 + console.log(`[NONCE API] Got nonce via HEAD request: ${nonce}`); 49 34 } 35 + } catch (headError) { 36 + console.warn(`[NONCE API] HEAD request failed:`, headError); 50 37 } 38 + 39 + // Method 2: OPTIONS request if HEAD fails 40 + if (!nonce) { 41 + try { 42 + console.log(`[NONCE API] Trying OPTIONS request to ${tokenEndpoint}`); 43 + const optionsResponse = await fetch(tokenEndpoint, { 44 + method: 'OPTIONS', 45 + headers: { 46 + 'Accept': '*/*' 47 + } 48 + }); 49 + 50 + nonce = optionsResponse.headers.get('DPoP-Nonce'); 51 + if (nonce) { 52 + console.log(`[NONCE API] Got nonce via OPTIONS request: ${nonce}`); 53 + } 54 + } catch (optionsError) { 55 + console.warn(`[NONCE API] OPTIONS request failed:`, optionsError); 56 + } 57 + } 58 + 59 + // Method 3: POST probe (last resort) 60 + if (!nonce) { 61 + try { 62 + console.log(`[NONCE API] Trying POST probe to ${tokenEndpoint}`); 63 + const probeResponse = await fetch(tokenEndpoint, { 64 + method: 'POST', 65 + headers: { 66 + 'Content-Type': 'application/x-www-form-urlencoded' 67 + }, 68 + // Empty body to trigger an error response that might contain a nonce 69 + body: new URLSearchParams({}) 70 + }); 71 + 72 + nonce = probeResponse.headers.get('DPoP-Nonce'); 73 + if (nonce) { 74 + console.log(`[NONCE API] Got nonce via POST probe: ${nonce}`); 75 + } 76 + } catch (probeError) { 77 + console.warn(`[NONCE API] POST probe failed:`, probeError); 78 + } 79 + } 80 + 81 + // If we got a nonce through any method, return it 82 + if (nonce) { 83 + return NextResponse.json({ nonce }); 84 + } 85 + 86 + // If all methods failed, return an error 87 + console.log(`[NONCE API] All methods failed to get a nonce from ${tokenEndpoint}`); 88 + return NextResponse.json( 89 + { error: 'Could not retrieve nonce', endpoint: tokenEndpoint }, 90 + { status: 404 } 91 + ); 51 92 } catch (error: any) { 52 - console.error('Nonce retrieval error:', error); 93 + console.error('[NONCE API] Nonce retrieval error:', error); 53 94 return NextResponse.json( 54 95 { error: 'Nonce retrieval error', message: error.message }, 55 96 { status: 500 } ··· 62 103 try { 63 104 // Use the default Bluesky server 64 105 const tokenEndpoint = `${DEFAULT_AUTH_SERVER}/oauth/token`; 65 - const response = await fetch(tokenEndpoint, { 66 - method: 'HEAD', 67 - headers: { 68 - 'Accept': '*/*' 69 - } 70 - }); 106 + console.log(`[NONCE API] GET: Attempting to get nonce from: ${tokenEndpoint}`); 71 107 72 - const nonce = response.headers.get('DPoP-Nonce'); 108 + // Try multiple methods to get a nonce 109 + let nonce = null; 73 110 74 - if (nonce) { 75 - return NextResponse.json({ nonce }); 76 - } else { 77 - // Try another method if HEAD doesn't work 78 - const probeResponse = await fetch(tokenEndpoint, { 79 - method: 'POST', 111 + // Method 1: HEAD request (most efficient) 112 + try { 113 + console.log(`[NONCE API] GET: Trying HEAD request to ${tokenEndpoint}`); 114 + const headResponse = await fetch(tokenEndpoint, { 115 + method: 'HEAD', 80 116 headers: { 81 - 'Content-Type': 'application/x-www-form-urlencoded' 82 - }, 83 - // Empty body to trigger an error response that might contain a nonce 84 - body: new URLSearchParams({}) 117 + 'Accept': '*/*' 118 + } 85 119 }); 86 120 87 - const probeNonce = probeResponse.headers.get('DPoP-Nonce'); 88 - 89 - if (probeNonce) { 90 - return NextResponse.json({ nonce: probeNonce }); 91 - } else { 92 - return NextResponse.json( 93 - { error: 'Could not retrieve nonce' }, 94 - { status: 404 } 95 - ); 121 + nonce = headResponse.headers.get('DPoP-Nonce'); 122 + if (nonce) { 123 + console.log(`[NONCE API] GET: Got nonce via HEAD request: ${nonce}`); 96 124 } 125 + } catch (headError) { 126 + console.warn(`[NONCE API] GET: HEAD request failed:`, headError); 97 127 } 128 + 129 + // Method 2: OPTIONS request if HEAD fails 130 + if (!nonce) { 131 + try { 132 + console.log(`[NONCE API] GET: Trying OPTIONS request to ${tokenEndpoint}`); 133 + const optionsResponse = await fetch(tokenEndpoint, { 134 + method: 'OPTIONS', 135 + headers: { 136 + 'Accept': '*/*' 137 + } 138 + }); 139 + 140 + nonce = optionsResponse.headers.get('DPoP-Nonce'); 141 + if (nonce) { 142 + console.log(`[NONCE API] GET: Got nonce via OPTIONS request: ${nonce}`); 143 + } 144 + } catch (optionsError) { 145 + console.warn(`[NONCE API] GET: OPTIONS request failed:`, optionsError); 146 + } 147 + } 148 + 149 + // Method 3: POST probe (last resort) 150 + if (!nonce) { 151 + try { 152 + console.log(`[NONCE API] GET: Trying POST probe to ${tokenEndpoint}`); 153 + const probeResponse = await fetch(tokenEndpoint, { 154 + method: 'POST', 155 + headers: { 156 + 'Content-Type': 'application/x-www-form-urlencoded' 157 + }, 158 + // Empty body to trigger an error response that might contain a nonce 159 + body: new URLSearchParams({}) 160 + }); 161 + 162 + nonce = probeResponse.headers.get('DPoP-Nonce'); 163 + if (nonce) { 164 + console.log(`[NONCE API] GET: Got nonce via POST probe: ${nonce}`); 165 + } 166 + } catch (probeError) { 167 + console.warn(`[NONCE API] GET: POST probe failed:`, probeError); 168 + } 169 + } 170 + 171 + // If we got a nonce through any method, return it 172 + if (nonce) { 173 + return NextResponse.json({ nonce }); 174 + } 175 + 176 + // If all methods failed, return an error 177 + console.log(`[NONCE API] GET: All methods failed to get a nonce from ${tokenEndpoint}`); 178 + return NextResponse.json( 179 + { error: 'Could not retrieve nonce', endpoint: tokenEndpoint }, 180 + { status: 404 } 181 + ); 98 182 } catch (error: any) { 99 - console.error('Nonce retrieval error:', error); 183 + console.error('[NONCE API] GET: Nonce retrieval error:', error); 100 184 return NextResponse.json( 101 185 { error: 'Nonce retrieval error', message: error.message }, 102 186 { status: 500 }
+14 -1
app/src/app/auth/callback/page.tsx
··· 148 148 return; 149 149 } 150 150 151 - // Save the DPoP nonce from the response headers if present 151 + // Save the DPoP nonce from the response 152 152 let dpopNonce = null; 153 + 154 + // First check if it's in the response object 153 155 if (tokenResponse.dpop_nonce) { 154 156 dpopNonce = tokenResponse.dpop_nonce; 157 + console.log('Retrieved DPoP nonce from token response:', dpopNonce); 158 + } 159 + 160 + // If not found in the response object, check localStorage 161 + // This is useful for third-party PDS servers 162 + if (!dpopNonce && typeof localStorage !== 'undefined') { 163 + const storedNonce = localStorage.getItem('dpopNonce'); 164 + if (storedNonce) { 165 + console.log('Retrieved DPoP nonce from localStorage:', storedNonce); 166 + dpopNonce = storedNonce; 167 + } 155 168 } 156 169 157 170 setStatus('Getting user profile...');
+53 -15
app/src/lib/auth-context.tsx
··· 53 53 54 54 // Check if token is expired or expiring soon 55 55 if (isTokenExpired(accessToken)) { 56 - console.log('Access token is expired or expiring soon, refreshing...'); 56 + console.log('[AUTH CONTEXT] Access token is expired or expiring soon, refreshing...'); 57 57 58 58 // Deserialize keypair 59 59 const keyPairData = JSON.parse(serializedKeyPair); ··· 73 73 ); 74 74 const keyPair = { publicKey, privateKey }; 75 75 76 - // Refresh the token 77 - const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } = 78 - await refreshAccessToken(refreshToken, keyPair, pdsEndpoint); 79 - 80 - // Update state and localStorage 81 - setAccessToken(newAccessToken); 82 - setRefreshToken(newRefreshToken); 83 - if (newNonce) setDpopNonce(newNonce); 76 + // Get the current DPoP nonce from localStorage if available 77 + let currentNonce = dpopNonce; 78 + if (!currentNonce && typeof localStorage !== 'undefined') { 79 + currentNonce = localStorage.getItem('dpopNonce'); 80 + if (currentNonce) { 81 + console.log('[AUTH CONTEXT] Retrieved nonce from localStorage:', currentNonce); 82 + // Update state with the nonce from localStorage 83 + setDpopNonce(currentNonce); 84 + } 85 + } 84 86 85 - localStorage.setItem('accessToken', newAccessToken); 86 - localStorage.setItem('refreshToken', newRefreshToken); 87 - if (newNonce) localStorage.setItem('dpopNonce', newNonce); 87 + console.log('[AUTH CONTEXT] Refreshing token for PDS:', pdsEndpoint); 88 88 89 - setLastTokenRefresh(Date.now()); 90 - console.log('Successfully refreshed access token'); 89 + // Refresh the token with enhanced error handling 90 + try { 91 + const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } = 92 + await refreshAccessToken(refreshToken, keyPair, pdsEndpoint); 93 + 94 + // Update state 95 + setAccessToken(newAccessToken); 96 + setRefreshToken(newRefreshToken); 97 + if (newNonce) setDpopNonce(newNonce); 98 + 99 + // Update localStorage 100 + if (typeof localStorage !== 'undefined') { 101 + localStorage.setItem('accessToken', newAccessToken); 102 + localStorage.setItem('refreshToken', newRefreshToken); 103 + if (newNonce) localStorage.setItem('dpopNonce', newNonce); 104 + } 105 + 106 + setLastTokenRefresh(Date.now()); 107 + console.log('[AUTH CONTEXT] Successfully refreshed access token'); 108 + } catch (refreshError) { 109 + console.error('[AUTH CONTEXT] Token refresh failed:', refreshError); 110 + 111 + // If refresh fails, we'll still try to use any nonce we received 112 + if (typeof localStorage !== 'undefined') { 113 + const latestNonce = localStorage.getItem('dpopNonce'); 114 + if (latestNonce && latestNonce !== dpopNonce) { 115 + console.log('[AUTH CONTEXT] Using latest nonce from localStorage:', latestNonce); 116 + setDpopNonce(latestNonce); 117 + } 118 + } 119 + } 120 + } else { 121 + // Even if token is not expired, make sure we have the latest nonce 122 + if (typeof localStorage !== 'undefined') { 123 + const latestNonce = localStorage.getItem('dpopNonce'); 124 + if (latestNonce && latestNonce !== dpopNonce) { 125 + console.log('[AUTH CONTEXT] Updating nonce from localStorage:', latestNonce); 126 + setDpopNonce(latestNonce); 127 + } 128 + } 91 129 } 92 130 } catch (error) { 93 - console.error('Failed to refresh token:', error); 131 + console.error('[AUTH CONTEXT] Error in checkAndRefreshToken:', error); 94 132 } 95 133 }; 96 134
+145 -37
app/src/lib/bluesky-api.ts
··· 71 71 throw new Error('No PDS endpoint provided for token refresh'); 72 72 } 73 73 74 - console.log('Refreshing access token with PDS endpoint:', pdsEndpoint); 74 + console.log('[TOKEN REFRESH] Refreshing token for PDS:', pdsEndpoint); 75 75 76 76 // Endpoint for token refresh 77 77 const tokenEndpoint = `${pdsEndpoint}/oauth/token`; 78 78 79 - // Get a nonce for the DPoP token 79 + // First, ALWAYS get a fresh nonce before attempting token refresh 80 80 let dpopNonce = null; 81 81 try { 82 - // Try to get a nonce with a HEAD request 83 - const headResponse = await fetch(tokenEndpoint, { 84 - method: 'HEAD' 82 + // Try server-side nonce retrieval first 83 + console.log('[TOKEN REFRESH] Getting fresh nonce from server API'); 84 + const nonceResponse = await fetch('/api/auth/nonce', { 85 + method: 'POST', 86 + headers: { 'Content-Type': 'application/json' }, 87 + body: JSON.stringify({ pdsEndpoint }) 85 88 }); 86 89 87 - dpopNonce = headResponse.headers.get('DPoP-Nonce'); 88 - if (dpopNonce) { 89 - console.log('Got nonce for token refresh:', dpopNonce); 90 + if (nonceResponse.ok) { 91 + const nonceData = await nonceResponse.json(); 92 + if (nonceData.nonce) { 93 + dpopNonce = nonceData.nonce; 94 + console.log('[TOKEN REFRESH] Got fresh nonce from server API:', dpopNonce); 95 + } 96 + } 97 + 98 + // If server-side retrieval fails, try client-side 99 + if (!dpopNonce) { 100 + console.log('[TOKEN REFRESH] Trying HEAD request for nonce'); 101 + const headResponse = await fetch(tokenEndpoint, { method: 'HEAD' }); 102 + dpopNonce = headResponse.headers.get('DPoP-Nonce'); 103 + } 104 + 105 + // If still no nonce, try POST probe 106 + if (!dpopNonce) { 107 + console.log('[TOKEN REFRESH] Trying POST probe for nonce'); 108 + const probeResponse = await fetch(tokenEndpoint, { 109 + method: 'POST', 110 + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 111 + body: new URLSearchParams({}) // Empty body to trigger error response with nonce 112 + }); 113 + dpopNonce = probeResponse.headers.get('DPoP-Nonce'); 90 114 } 91 115 } catch (nonceError) { 92 - console.warn('Failed to get nonce for token refresh:', nonceError); 116 + console.warn('[TOKEN REFRESH] Failed to get initial nonce:', nonceError); 117 + } 118 + 119 + if (!dpopNonce) { 120 + console.log('[TOKEN REFRESH] No nonce obtained, proceeding without one'); 121 + } else { 122 + console.log('[TOKEN REFRESH] Obtained nonce:', dpopNonce); 93 123 } 94 124 95 125 // Generate DPoP token for the refresh request ··· 99 129 publicKey, 100 130 'POST', 101 131 tokenEndpoint, 102 - dpopNonce || undefined // Convert null to undefined to satisfy TypeScript 132 + dpopNonce || undefined 103 133 ); 134 + 135 + console.log('[TOKEN REFRESH] Making token refresh request'); 104 136 105 137 // Make the token refresh request 106 138 const response = await fetch(tokenEndpoint, { ··· 116 148 }) 117 149 }); 118 150 119 - // Check for nonce error 151 + // Handle nonce error explicitly - this is the critical part! 120 152 if (response.status === 401) { 153 + let responseBody; 154 + try { 155 + responseBody = await response.json(); 156 + } catch (e) { 157 + responseBody = {}; 158 + } 159 + 121 160 const newNonce = response.headers.get('DPoP-Nonce'); 122 - if (newNonce) { 123 - console.log('Got new nonce during token refresh:', newNonce); 161 + 162 + // Check for DPoP nonce error 163 + if ( 164 + (responseBody.error === 'use_dpop_nonce' || 165 + (responseBody.error_description && responseBody.error_description.includes('nonce'))) && 166 + newNonce 167 + ) { 168 + console.log('[TOKEN REFRESH] Received nonce error, retrying with new nonce:', newNonce); 124 169 125 - // Try again with the new nonce 170 + // Generate new DPoP token with the provided nonce 126 171 const retryDpopToken = await generateDPoPToken( 127 172 keyPair.privateKey, 128 173 publicKey, ··· 131 176 newNonce 132 177 ); 133 178 179 + console.log('[TOKEN REFRESH] Retrying token refresh with new nonce'); 180 + 181 + // Retry the request with the new nonce 134 182 const retryResponse = await fetch(tokenEndpoint, { 135 183 method: 'POST', 136 184 headers: { ··· 146 194 147 195 if (!retryResponse.ok) { 148 196 const errorText = await retryResponse.text(); 197 + console.error('[TOKEN REFRESH] Token refresh retry failed:', retryResponse.status, errorText); 149 198 throw new Error(`Token refresh retry failed: ${retryResponse.status}, ${errorText}`); 150 199 } 151 200 152 201 const tokenData = await retryResponse.json(); 202 + console.log('[TOKEN REFRESH] Successfully refreshed token on retry'); 203 + 204 + // Store the new nonce for future requests 205 + if (typeof localStorage !== 'undefined') { 206 + localStorage.setItem('dpopNonce', newNonce); 207 + } 153 208 154 209 // Return the new tokens and nonce 155 210 return { 156 211 accessToken: tokenData.access_token, 157 - refreshToken: tokenData.refresh_token || refreshToken, // Use the new refresh token if provided 212 + refreshToken: tokenData.refresh_token || refreshToken, 158 213 dpopNonce: newNonce 159 214 }; 160 215 } ··· 162 217 163 218 if (!response.ok) { 164 219 const errorText = await response.text(); 220 + console.error('[TOKEN REFRESH] Token refresh failed:', response.status, errorText); 165 221 throw new Error(`Token refresh failed: ${response.status}, ${errorText}`); 166 222 } 167 223 ··· 170 226 // Get any nonce from the response headers 171 227 const responseNonce = response.headers.get('DPoP-Nonce'); 172 228 229 + console.log('[TOKEN REFRESH] Successfully refreshed access token'); 230 + 231 + // Update the nonce in localStorage 232 + if (responseNonce && typeof localStorage !== 'undefined') { 233 + localStorage.setItem('dpopNonce', responseNonce); 234 + } 235 + 173 236 // Return the new tokens and nonce 174 237 return { 175 238 accessToken: tokenData.access_token, 176 - refreshToken: tokenData.refresh_token || refreshToken, // Use the new refresh token if provided 177 - dpopNonce: responseNonce || dpopNonce || undefined 239 + refreshToken: tokenData.refresh_token || refreshToken, 240 + dpopNonce: responseNonce || dpopNonce 178 241 }; 179 242 } catch (error) { 180 - console.error('Error refreshing access token:', error); 243 + console.error('[TOKEN REFRESH] Error refreshing access token:', error); 181 244 throw error; 182 245 } 183 246 } ··· 280 343 } 281 344 282 345 if (response.status === 401) { 346 + // Try to parse error response 347 + let responseBody; 348 + try { 349 + responseBody = await response.json(); 350 + } catch (e) { 351 + responseBody = {}; 352 + } 353 + 283 354 const nonce = response.headers.get('DPoP-Nonce'); 284 355 if (nonce) { 285 - console.log('Got nonce during auth check:', nonce); 356 + console.log('[AUTH CHECK] Got nonce during auth check:', nonce); 357 + 358 + // Store the nonce for future use 359 + if (typeof localStorage !== 'undefined') { 360 + localStorage.setItem('dpopNonce', nonce); 361 + } 362 + 363 + // Check if this is a nonce error 364 + if (responseBody.error === 'use_dpop_nonce' || 365 + (responseBody.error_description && responseBody.error_description.includes('nonce'))) { 366 + console.log('[AUTH CHECK] DPoP nonce error detected, retrying with new nonce'); 367 + } 368 + 286 369 // Try again with the nonce, but prevent infinite recursion 287 370 return checkAuth(accessToken, keyPair, did, nonce, pdsEndpoint, refreshToken); 288 371 } 289 372 290 373 // If we have a refresh token, try to refresh the access token 291 374 if (refreshToken && !tokenExpired) { // Only try this if we didn't already try above 292 - console.log('Auth failed with 401, attempting to refresh token...'); 375 + console.log('[AUTH CHECK] Auth failed with 401, attempting to refresh token...'); 293 376 294 377 try { 295 - // Try to refresh the token 378 + // Try to refresh the token with enhanced error handling 296 379 const { accessToken: newAccessToken, refreshToken: newRefreshToken, dpopNonce: newNonce } = 297 380 await refreshAccessToken(refreshToken, keyPair, pdsEndpoint); 298 381 299 382 // Update tokens in localStorage 300 - localStorage.setItem('accessToken', newAccessToken); 301 - localStorage.setItem('refreshToken', newRefreshToken); 302 - if (newNonce) localStorage.setItem('dpopNonce', newNonce); 383 + if (typeof localStorage !== 'undefined') { 384 + localStorage.setItem('accessToken', newAccessToken); 385 + localStorage.setItem('refreshToken', newRefreshToken); 386 + if (newNonce) localStorage.setItem('dpopNonce', newNonce); 387 + 388 + console.log('[AUTH CHECK] Tokens updated in localStorage during checkAuth'); 389 + } 303 390 304 - console.log('Tokens updated in localStorage during checkAuth'); 305 - 306 - console.log('Token refreshed successfully, retrying auth check with new token'); 391 + console.log('[AUTH CHECK] Token refreshed successfully, retrying auth check with new token'); 307 392 308 393 // Return the result of checkAuth with the new token 309 394 return checkAuth(newAccessToken, keyPair, did, newNonce || null, pdsEndpoint, newRefreshToken); 310 395 } catch (refreshError) { 311 - console.error('Token refresh failed:', refreshError); 396 + console.error('[AUTH CHECK] Token refresh failed:', refreshError); 397 + console.log('[AUTH CHECK] User needs to re-authenticate - session cannot be restored'); 312 398 } 313 399 } 314 400 } ··· 393 479 responseBody = {}; 394 480 } 395 481 482 + // Get the nonce from headers if available 483 + const newDpopNonce = response.headers.get('DPoP-Nonce'); 484 + 396 485 // Check if this is a nonce error 397 486 if ( 398 - responseBody.error === 'use_dpop_nonce' || 399 - (responseBody.error_description && responseBody.error_description.includes('nonce')) 487 + (responseBody.error === 'use_dpop_nonce' || 488 + (responseBody.error_description && responseBody.error_description.includes('nonce'))) && 489 + newDpopNonce 400 490 ) { 401 - // Get new nonce from header 402 - const newDpopNonce = response.headers.get('DPoP-Nonce'); 403 - if (newDpopNonce) { 404 - console.log('Retrying API request with nonce:', newDpopNonce); 405 - return makeAuthenticatedRequest(endpoint, method, accessToken, keyPair, newDpopNonce, body); 491 + // Store the nonce for future use 492 + if (typeof localStorage !== 'undefined') { 493 + console.log('[API REQUEST] Storing new DPoP nonce in localStorage:', newDpopNonce); 494 + localStorage.setItem('dpopNonce', newDpopNonce); 406 495 } 496 + 497 + console.log('[API REQUEST] Retrying request with new nonce:', newDpopNonce); 498 + return makeAuthenticatedRequest(endpoint, method, accessToken, keyPair, newDpopNonce, body, pdsEndpoint); 407 499 } 408 500 409 501 // Other 401 error, possibly expired token 502 + console.error('[API REQUEST] Request failed with 401 unauthorized:', responseBody); 503 + 504 + // Include nonce in error message if available 505 + if (newDpopNonce) { 506 + console.log('[API REQUEST] 401 response included nonce:', newDpopNonce); 507 + // Store the nonce even though we're not retrying now 508 + if (typeof localStorage !== 'undefined') { 509 + localStorage.setItem('dpopNonce', newDpopNonce); 510 + } 511 + } 512 + 410 513 throw new Error(`API request unauthorized: ${JSON.stringify(responseBody)}`); 411 514 } 412 515 ··· 433 536 // Save any nonce for future requests 434 537 const returnNonce = response.headers.get('DPoP-Nonce'); 435 538 if (returnNonce && returnNonce !== dpopNonce) { 436 - // If there's a place to store it, we would store it here 437 - console.log('New DPoP nonce received:', returnNonce); 539 + console.log('[API REQUEST] New DPoP nonce received in successful response:', returnNonce); 540 + 541 + // Always store the latest nonce for future requests 542 + if (typeof localStorage !== 'undefined') { 543 + localStorage.setItem('dpopNonce', returnNonce); 544 + console.log('[API REQUEST] Updated nonce in localStorage for future requests'); 545 + } 438 546 } 439 547 440 548 return result;