Skip to content

Commit d612d5a

Browse files
committed
Successfully getting a token from Groq!
1 parent 81a8c15 commit d612d5a

File tree

2 files changed

+92
-56
lines changed

2 files changed

+92
-56
lines changed

examples/chat-ui/src/components/OAuthCallback.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ const OAuthCallback: React.FC<OAuthCallbackProps> = ({ provider }) => {
3434
throw new Error('Missing authorization code')
3535
}
3636

37-
// OpenRouter doesn't use state parameter, but other providers might
38-
const stateToUse = state || 'no-state'
39-
await completeOAuthFlow(provider, code, stateToUse)
37+
// TODO: Add state parameter handling back if needed later
38+
// const stateToUse = state || 'no-state'
39+
await completeOAuthFlow(provider, code)
4040
setStatus('success')
4141

4242
// Close popup after successful authentication

examples/chat-ui/src/utils/auth.ts

Lines changed: 89 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export interface OAuthToken {
1111
// Types for PKCE flow
1212
interface PKCEState {
1313
code_verifier: string
14-
state: string
14+
// TODO: Add state support back if needed later
15+
// state: string
1516
}
1617

1718
// Generate a random code verifier for PKCE
@@ -35,15 +36,15 @@ async function generateCodeChallenge(verifier: string): Promise<string> {
3536
.replace(/=/g, '')
3637
}
3738

38-
// Generate random state parameter
39-
function generateState(): string {
40-
const array = new Uint8Array(16)
41-
crypto.getRandomValues(array)
42-
return btoa(String.fromCharCode(...array))
43-
.replace(/\+/g, '-')
44-
.replace(/\//g, '_')
45-
.replace(/=/g, '')
46-
}
39+
// TODO: Add state generation back if needed later
40+
// function generateState(): string {
41+
// const array = new Uint8Array(16)
42+
// crypto.getRandomValues(array)
43+
// return btoa(String.fromCharCode(...array))
44+
// .replace(/\+/g, '-')
45+
// .replace(/\//g, '_')
46+
// .replace(/=/g, '')
47+
// }
4748

4849
// API Key functions (existing functionality)
4950
export function hasApiKey(providerId: SupportedProvider): boolean {
@@ -111,11 +112,17 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
111112

112113
const codeVerifier = generateCodeVerifier()
113114
const codeChallenge = await generateCodeChallenge(codeVerifier)
114-
const state = generateState()
115115

116-
// Store PKCE state in sessionStorage
117-
const pkceState: PKCEState = { code_verifier: codeVerifier, state }
118-
const storageKey = providerId === 'openrouter' ? `pkce_${providerId}_${Date.now()}` : `pkce_${providerId}_${state}`
116+
console.log('DEBUG: Generated PKCE values for', providerId, {
117+
codeVerifier: codeVerifier,
118+
codeChallenge: codeChallenge,
119+
verifierLength: codeVerifier.length,
120+
challengeLength: codeChallenge.length,
121+
})
122+
123+
// Store PKCE state in sessionStorage (using timestamp for both providers since no state)
124+
const pkceState: PKCEState = { code_verifier: codeVerifier }
125+
const storageKey = `pkce_${providerId}_${Date.now()}`
119126
sessionStorage.setItem(storageKey, JSON.stringify(pkceState))
120127

121128
// Construct authorization URL based on provider
@@ -132,6 +139,8 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
132139
}
133140
authUrl.searchParams.set('code_challenge', codeChallenge)
134141
authUrl.searchParams.set('code_challenge_method', 'S256')
142+
// TODO: Add state parameter back if needed later
143+
// authUrl.searchParams.set('state', state)
135144

136145
// Open popup or redirect
137146
const popup = window.open(authUrl.toString(), `oauth_${providerId}`, 'width=600,height=700')
@@ -141,50 +150,47 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
141150
}
142151
}
143152

144-
export async function completeOAuthFlow(providerId: SupportedProvider, code: string, state: string): Promise<void> {
145-
console.log('DEBUG: Starting OAuth completion for', providerId, 'with code:', code?.substring(0, 10) + '...', 'state:', state)
153+
export async function completeOAuthFlow(providerId: SupportedProvider, code: string): Promise<void> {
154+
console.log('DEBUG: Starting OAuth completion for', providerId, 'with code:', code?.substring(0, 10) + '...')
155+
console.log('DEBUG: Full code for debugging:', code)
156+
console.log('DEBUG: Provider config:', providers[providerId])
146157

147158
const provider = providers[providerId]
148159
if (!provider.oauth) {
149160
throw new Error(`Provider ${providerId} does not support OAuth`)
150161
}
151162

152-
// Retrieve PKCE state
153-
let pkceState: PKCEState
163+
// Retrieve PKCE state (find the most recent one since we don't use state parameter)
164+
const allKeys = Object.keys(sessionStorage)
165+
const pkceKeys = allKeys.filter((key) => key.startsWith(`pkce_${providerId}_`))
154166

155-
if (state === 'no-state' && (providerId === 'openrouter' || providerId === 'groq')) {
156-
// OpenRouter doesn't use state, find the most recent PKCE state for this provider
157-
const allKeys = Object.keys(sessionStorage)
158-
const pkceKeys = allKeys.filter((key) => key.startsWith(`pkce_${providerId}_`))
167+
console.log('DEBUG: Found PKCE keys:', pkceKeys)
159168

160-
console.log('DEBUG: Found PKCE keys:', pkceKeys)
169+
if (pkceKeys.length === 0) {
170+
throw new Error('PKCE state not found. Please try again.')
171+
}
161172

162-
if (pkceKeys.length === 0) {
163-
throw new Error('PKCE state not found. Please try again.')
164-
}
173+
// Use the most recent one (sort by timestamp)
174+
const sortedKeys = pkceKeys.sort((a, b) => {
175+
const aTime = parseInt(a.split('_').pop() || '0')
176+
const bTime = parseInt(b.split('_').pop() || '0')
177+
return bTime - aTime // Most recent first
178+
})
165179

166-
// Use the most recent one (sort by timestamp)
167-
const sortedKeys = pkceKeys.sort((a, b) => {
168-
const aTime = parseInt(a.split('_').pop() || '0')
169-
const bTime = parseInt(b.split('_').pop() || '0')
170-
return bTime - aTime // Most recent first
171-
})
180+
const pkceStateJson = sessionStorage.getItem(sortedKeys[0])!
181+
const pkceState: PKCEState = JSON.parse(pkceStateJson)
172182

173-
const pkceStateJson = sessionStorage.getItem(sortedKeys[0])!
174-
pkceState = JSON.parse(pkceStateJson)
183+
console.log('DEBUG: Using PKCE state:', { key: sortedKeys[0], state: pkceState })
184+
console.log('DEBUG: Code verifier length:', pkceState.code_verifier.length)
185+
console.log('DEBUG: Code verifier sample:', pkceState.code_verifier.substring(0, 20) + '...')
175186

176-
console.log('DEBUG: Using PKCE state:', { key: sortedKeys[0], state: pkceState })
187+
// Test: regenerate challenge from verifier to verify it matches server expectation
188+
const recomputedChallenge = await generateCodeChallenge(pkceState.code_verifier)
189+
console.log('DEBUG: Recomputed challenge from verifier:', recomputedChallenge)
190+
console.log('DEBUG: Server reported challenge was: dW2iEvNljlkhcRcryo3Z0GITcJM1liKcHlB5v8CDEu8')
177191

178-
// Clean up the state
179-
sessionStorage.removeItem(sortedKeys[0])
180-
} else {
181-
const pkceStateJson = sessionStorage.getItem(`pkce_${providerId}_${state}`)
182-
if (!pkceStateJson) {
183-
throw new Error('PKCE state not found. Please try again.')
184-
}
185-
pkceState = JSON.parse(pkceStateJson)
186-
console.log('DEBUG: Using PKCE state for state', state, ':', pkceState)
187-
}
192+
// Clean up the state
193+
sessionStorage.removeItem(sortedKeys[0])
188194

189195
// Exchange code for token
190196
let tokenResponse: Response
@@ -218,31 +224,37 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str
218224
duration: `${endTime - startTime}ms`,
219225
})
220226
} else {
221-
// Standard OAuth2 flow for other providers
227+
// Standard OAuth2 flow for other providers (Groq)
222228
const requestBody = new URLSearchParams({
223229
grant_type: 'authorization_code',
224230
code,
225-
// TODO: state??
226231
redirect_uri: getRedirectUri(providerId),
227232
code_verifier: pkceState.code_verifier,
228233
})
229-
console.log('DEBUG: Standard OAuth token request:', {
234+
235+
console.log('DEBUG: Groq token request:', {
230236
url: provider.oauth.tokenUrl,
231237
body: Object.fromEntries(requestBody.entries()),
238+
codeVerifierLength: pkceState.code_verifier.length,
239+
codeVerifierSample: pkceState.code_verifier.substring(0, 20) + '...',
240+
fullCodeVerifier: pkceState.code_verifier, // For debugging
232241
})
233242

243+
const startTime = performance.now()
234244
tokenResponse = await fetch(provider.oauth.tokenUrl, {
235245
method: 'POST',
236246
headers: {
237247
'Content-Type': 'application/x-www-form-urlencoded',
238248
},
239249
body: requestBody,
240250
})
251+
const endTime = performance.now()
241252

242-
console.log('DEBUG: Standard OAuth token response:', {
253+
console.log('DEBUG: Groq token response:', {
243254
status: tokenResponse.status,
244255
statusText: tokenResponse.statusText,
245256
headers: Object.fromEntries(tokenResponse.headers.entries()),
257+
duration: `${endTime - startTime}ms`,
246258
})
247259
}
248260

@@ -276,10 +288,7 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str
276288

277289
setOAuthToken(providerId, token)
278290

279-
// Clean up PKCE state (already cleaned up above for OpenRouter)
280-
if (state !== 'no-state') {
281-
sessionStorage.removeItem(`pkce_${providerId}_${state}`)
282-
}
291+
// PKCE state already cleaned up above
283292
}
284293

285294
// Get authentication headers for API calls
@@ -319,3 +328,30 @@ function getRedirectUri(providerId: SupportedProvider): string {
319328
const baseUrl = window.location.origin
320329
return `${baseUrl}/oauth/${providerId}/callback`
321330
}
331+
332+
// Test function to verify PKCE implementation with known values
333+
export async function testPKCEImplementation(): Promise<void> {
334+
console.log('=== TESTING PKCE IMPLEMENTATION ===')
335+
336+
// Test with a known code verifier (from RFC 7636 example)
337+
const testVerifier = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'
338+
const expectedChallenge = 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM'
339+
340+
const computedChallenge = await generateCodeChallenge(testVerifier)
341+
342+
console.log('DEBUG: Test verifier:', testVerifier)
343+
console.log('DEBUG: Expected challenge:', expectedChallenge)
344+
console.log('DEBUG: Computed challenge:', computedChallenge)
345+
console.log('DEBUG: Challenges match:', computedChallenge === expectedChallenge)
346+
347+
// Test with current implementation
348+
const currentVerifier = generateCodeVerifier()
349+
const currentChallenge = await generateCodeChallenge(currentVerifier)
350+
351+
console.log('DEBUG: Current verifier:', currentVerifier)
352+
console.log('DEBUG: Current challenge:', currentChallenge)
353+
console.log('DEBUG: Verifier length:', currentVerifier.length)
354+
console.log('DEBUG: Challenge length:', currentChallenge.length)
355+
356+
console.log('=== END PKCE TEST ===')
357+
}

0 commit comments

Comments
 (0)