From dc991c69175d02e189cd70a3321bd713b3b8617e Mon Sep 17 00:00:00 2001 From: ngocdb Date: Wed, 29 Apr 2026 11:43:40 +0700 Subject: [PATCH 1/2] fix(kiro): support organization IDC OAuth with regional endpoints and refresh --- .../api/oauth/[provider]/[action]/route.ts | 21 ++- src/lib/oauth/providers.ts | 4 +- src/lib/oauth/providers/kiro.ts | 37 ++++-- src/shared/components/KiroAuthModal.tsx | 10 +- src/shared/components/OAuthModal.tsx | 86 ++++++++---- tests/unit/oauth-kiro-idc.test.ts | 123 ++++++++++++++++++ 6 files changed, 238 insertions(+), 43 deletions(-) create mode 100644 tests/unit/oauth-kiro-idc.test.ts diff --git a/src/app/api/oauth/[provider]/[action]/route.ts b/src/app/api/oauth/[provider]/[action]/route.ts index cf5dd090ebdc..0b31439e5cfb 100755 --- a/src/app/api/oauth/[provider]/[action]/route.ts +++ b/src/app/api/oauth/[provider]/[action]/route.ts @@ -81,6 +81,8 @@ export async function GET( } const authData = generateAuthData(provider, null); + const startUrl = searchParams.get("startUrl"); + const region = searchParams.get("region") || "us-east-1"; // Resolve proxy for this provider (provider-level → global → direct) const proxy = await resolveProxyForProvider(provider); @@ -95,7 +97,24 @@ export async function GET( provider === "kilocode" ) { // GitHub, Kiro/Amazon Q, Kimi Coding, and KiloCode don't use PKCE for device code - deviceData = await runWithProxyContext(proxy, () => (requestDeviceCode as any)(provider)); + if ((provider === "kiro" || provider === "amazon-q") && startUrl) { + const providerOverrideConfig = { + ...providerData.config, + startUrl, + region, + skipIssuerUrlForRegistration: true, + registerClientUrl: `https://oidc.${region}.amazonaws.com/client/register`, + deviceAuthUrl: `https://oidc.${region}.amazonaws.com/device_authorization`, + tokenUrl: `https://oidc.${region}.amazonaws.com/token`, + ssoOidcEndpoint: `https://oidc.${region}.amazonaws.com`, + }; + + deviceData = await runWithProxyContext(proxy, () => + (requestDeviceCode as any)(provider, null, providerOverrideConfig) + ); + } else { + deviceData = await runWithProxyContext(proxy, () => (requestDeviceCode as any)(provider)); + } } else { // Qwen and other providers use PKCE deviceData = await runWithProxyContext(proxy, () => diff --git a/src/lib/oauth/providers.ts b/src/lib/oauth/providers.ts index e00cd994ec7d..5709042630d2 100644 --- a/src/lib/oauth/providers.ts +++ b/src/lib/oauth/providers.ts @@ -81,12 +81,12 @@ export async function exchangeTokens(providerName, code, redirectUri, codeVerifi /** * Request device code (for device_code flow) */ -export async function requestDeviceCode(providerName, codeChallenge) { +export async function requestDeviceCode(providerName, codeChallenge, configOverride = null) { const provider = getProvider(providerName); if (provider.flowType !== "device_code") { throw new Error(`Provider ${providerName} does not support device code flow`); } - return await provider.requestDeviceCode(provider.config, codeChallenge); + return await provider.requestDeviceCode(configOverride || provider.config, codeChallenge); } /** diff --git a/src/lib/oauth/providers/kiro.ts b/src/lib/oauth/providers/kiro.ts index 945c75cd7423..80c03a7d8148 100644 --- a/src/lib/oauth/providers/kiro.ts +++ b/src/lib/oauth/providers/kiro.ts @@ -4,6 +4,27 @@ export const kiro = { config: KIRO_CONFIG, flowType: "device_code", requestDeviceCode: async (config) => { + const regionMatch = String(config.tokenUrl || "").match(/oidc\.([a-z0-9-]+)\.amazonaws\.com/i); + const resolvedRegion = regionMatch?.[1] || "us-east-1"; + const registerPayload: { + clientName: string; + clientType: string; + scopes: string[]; + grantTypes: string[]; + issuerUrl?: string; + } = { + clientName: config.clientName, + clientType: config.clientType, + scopes: config.scopes, + grantTypes: config.grantTypes, + }; + + // For enterprise IDC custom startUrl flows, issuerUrl can differ per tenant. + // Sending a fixed issuerUrl often causes invalid_request during device auth. + if (config.issuerUrl && !config.skipIssuerUrlForRegistration) { + registerPayload.issuerUrl = config.issuerUrl; + } + // Step 1: Register client with AWS SSO OIDC const registerRes = await fetch(config.registerClientUrl, { method: "POST", @@ -11,13 +32,7 @@ export const kiro = { "Content-Type": "application/json", Accept: "application/json", }, - body: JSON.stringify({ - clientName: config.clientName, - clientType: config.clientType, - scopes: config.scopes, - grantTypes: config.grantTypes, - issuerUrl: config.issuerUrl, - }), + body: JSON.stringify(registerPayload), }); if (!registerRes.ok) { @@ -57,10 +72,14 @@ export const kiro = { interval: deviceData.interval || 5, _clientId: clientInfo.clientId, _clientSecret: clientInfo.clientSecret, + _region: resolvedRegion, }; }, pollToken: async (config, deviceCode, codeVerifier, extraData) => { - const response = await fetch(config.tokenUrl, { + const tokenRegion = extraData?._region || "us-east-1"; + const tokenUrl = `https://oidc.${tokenRegion}.amazonaws.com/token`; + + const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json", @@ -91,6 +110,7 @@ export const kiro = { expires_in: data.expiresIn, _clientId: extraData?._clientId, _clientSecret: extraData?._clientSecret, + _region: tokenRegion, }, }; } @@ -110,6 +130,7 @@ export const kiro = { providerSpecificData: { clientId: tokens._clientId, clientSecret: tokens._clientSecret, + region: tokens._region, }, }), }; diff --git a/src/shared/components/KiroAuthModal.tsx b/src/shared/components/KiroAuthModal.tsx index 5a49d0d5bf22..588771f40f99 100644 --- a/src/shared/components/KiroAuthModal.tsx +++ b/src/shared/components/KiroAuthModal.tsx @@ -138,17 +138,19 @@ export default function KiroAuthModal({ - {/* AWS IAM Identity Center (IDC) - HIDDEN */} + {/* AWS IAM Identity Center (IDC) */}