@@ -11,7 +11,8 @@ export interface OAuthToken {
11
11
// Types for PKCE flow
12
12
interface PKCEState {
13
13
code_verifier : string
14
- state : string
14
+ // TODO: Add state support back if needed later
15
+ // state: string
15
16
}
16
17
17
18
// Generate a random code verifier for PKCE
@@ -35,15 +36,15 @@ async function generateCodeChallenge(verifier: string): Promise<string> {
35
36
. replace ( / = / g, '' )
36
37
}
37
38
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
+ // }
47
48
48
49
// API Key functions (existing functionality)
49
50
export function hasApiKey ( providerId : SupportedProvider ) : boolean {
@@ -111,11 +112,17 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
111
112
112
113
const codeVerifier = generateCodeVerifier ( )
113
114
const codeChallenge = await generateCodeChallenge ( codeVerifier )
114
- const state = generateState ( )
115
115
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 ( ) } `
119
126
sessionStorage . setItem ( storageKey , JSON . stringify ( pkceState ) )
120
127
121
128
// Construct authorization URL based on provider
@@ -132,6 +139,8 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
132
139
}
133
140
authUrl . searchParams . set ( 'code_challenge' , codeChallenge )
134
141
authUrl . searchParams . set ( 'code_challenge_method' , 'S256' )
142
+ // TODO: Add state parameter back if needed later
143
+ // authUrl.searchParams.set('state', state)
135
144
136
145
// Open popup or redirect
137
146
const popup = window . open ( authUrl . toString ( ) , `oauth_${ providerId } ` , 'width=600,height=700' )
@@ -141,50 +150,47 @@ export async function beginOAuthFlow(providerId: SupportedProvider): Promise<voi
141
150
}
142
151
}
143
152
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 ] )
146
157
147
158
const provider = providers [ providerId ]
148
159
if ( ! provider . oauth ) {
149
160
throw new Error ( `Provider ${ providerId } does not support OAuth` )
150
161
}
151
162
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 } _` ) )
154
166
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 )
159
168
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
+ }
161
172
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
+ } )
165
179
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 )
172
182
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 ) + '...' )
175
186
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' )
177
191
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 ] )
188
194
189
195
// Exchange code for token
190
196
let tokenResponse : Response
@@ -218,31 +224,37 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str
218
224
duration : `${ endTime - startTime } ms` ,
219
225
} )
220
226
} else {
221
- // Standard OAuth2 flow for other providers
227
+ // Standard OAuth2 flow for other providers (Groq)
222
228
const requestBody = new URLSearchParams ( {
223
229
grant_type : 'authorization_code' ,
224
230
code,
225
- // TODO: state??
226
231
redirect_uri : getRedirectUri ( providerId ) ,
227
232
code_verifier : pkceState . code_verifier ,
228
233
} )
229
- console . log ( 'DEBUG: Standard OAuth token request:' , {
234
+
235
+ console . log ( 'DEBUG: Groq token request:' , {
230
236
url : provider . oauth . tokenUrl ,
231
237
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
232
241
} )
233
242
243
+ const startTime = performance . now ( )
234
244
tokenResponse = await fetch ( provider . oauth . tokenUrl , {
235
245
method : 'POST' ,
236
246
headers : {
237
247
'Content-Type' : 'application/x-www-form-urlencoded' ,
238
248
} ,
239
249
body : requestBody ,
240
250
} )
251
+ const endTime = performance . now ( )
241
252
242
- console . log ( 'DEBUG: Standard OAuth token response:' , {
253
+ console . log ( 'DEBUG: Groq token response:' , {
243
254
status : tokenResponse . status ,
244
255
statusText : tokenResponse . statusText ,
245
256
headers : Object . fromEntries ( tokenResponse . headers . entries ( ) ) ,
257
+ duration : `${ endTime - startTime } ms` ,
246
258
} )
247
259
}
248
260
@@ -276,10 +288,7 @@ export async function completeOAuthFlow(providerId: SupportedProvider, code: str
276
288
277
289
setOAuthToken ( providerId , token )
278
290
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
283
292
}
284
293
285
294
// Get authentication headers for API calls
@@ -319,3 +328,30 @@ function getRedirectUri(providerId: SupportedProvider): string {
319
328
const baseUrl = window . location . origin
320
329
return `${ baseUrl } /oauth/${ providerId } /callback`
321
330
}
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