@@ -145,6 +145,23 @@ export function buildXaiOAuthAuthorizeUrl(params: {
|
145 | 145 | return url.toString(); |
146 | 146 | } |
147 | 147 | |
| 148 | +export function buildXaiOAuthAuthorizationCodeTokenBody(params: { |
| 149 | +code: string; |
| 150 | +codeVerifier: string; |
| 151 | +codeChallenge: string; |
| 152 | +}): Record<string, string> { |
| 153 | +return { |
| 154 | +grant_type: "authorization_code", |
| 155 | +code: params.code, |
| 156 | +redirect_uri: XAI_OAUTH_REDIRECT_URI, |
| 157 | +client_id: XAI_OAUTH_CLIENT_ID, |
| 158 | +code_verifier: params.codeVerifier, |
| 159 | +// xAI validates these PKCE fields again at token exchange for this client. |
| 160 | +code_challenge: params.codeChallenge, |
| 161 | +code_challenge_method: "S256", |
| 162 | +}; |
| 163 | +} |
| 164 | + |
148 | 165 | function normalizeExpires(value: unknown, now: () => number): number | undefined { |
149 | 166 | const seconds = |
150 | 167 | typeof value === "number" |
@@ -312,13 +329,11 @@ export async function loginXaiOAuth(ctx: ProviderAuthContext): Promise<ProviderA
|
312 | 329 | tokenEndpoint: discovery.tokenEndpoint, |
313 | 330 | context: "xAI OAuth token exchange", |
314 | 331 | requireRefreshToken: true, |
315 | | -body: { |
316 | | -grant_type: "authorization_code", |
| 332 | +body: buildXaiOAuthAuthorizationCodeTokenBody({ |
317 | 333 | code: callback.code, |
318 | | -redirect_uri: XAI_OAUTH_REDIRECT_URI, |
319 | | -client_id: XAI_OAUTH_CLIENT_ID, |
320 | | -code_verifier: pkce.verifier, |
321 | | -}, |
| 334 | +codeVerifier: pkce.verifier, |
| 335 | +codeChallenge: pkce.challenge, |
| 336 | +}), |
322 | 337 | }); |
323 | 338 | const identity = resolveXaiOAuthIdentity(tokens); |
324 | 339 | progress.stop("xAI OAuth complete"); |
|