fix(kiro): auto-resolve profileArn to prevent 403 on IDC login
AWS OIDC IDC/Builder-ID tokens omit profileArn, so CodeWhisperer calls return 403 "User is not authorized". Resolve it natively via the ListAvailableProfiles API instead of reading Kiro IDE profile.json. - providers.js: add fetchKiroProfileArn() and resolve on poll (new logins) - tokenRefresh.js: backfill profileArn on refresh so existing IDC connections self-heal without re-login Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -367,6 +367,17 @@ export async function refreshCodexToken(refreshToken, log) {
|
|||||||
* Specialized refresh for Kiro (AWS CodeWhisperer) tokens
|
* Specialized refresh for Kiro (AWS CodeWhisperer) tokens
|
||||||
* Supports both AWS SSO OIDC (Builder ID/IDC) and Social Auth (Google/GitHub)
|
* Supports both AWS SSO OIDC (Builder ID/IDC) and Social Auth (Google/GitHub)
|
||||||
*/
|
*/
|
||||||
|
// Backfill missing Kiro profileArn on refresh so existing IDC connections self-heal
|
||||||
|
async function resolveKiroProfileArnPatch(providerSpecificData, accessToken, refreshedArn) {
|
||||||
|
if (providerSpecificData?.profileArn) return {};
|
||||||
|
let profileArn = refreshedArn?.trim?.() || null;
|
||||||
|
if (!profileArn) {
|
||||||
|
const { fetchKiroProfileArn } = await import("../../src/lib/oauth/providers.js");
|
||||||
|
profileArn = await fetchKiroProfileArn(accessToken);
|
||||||
|
}
|
||||||
|
return profileArn ? { providerSpecificData: { profileArn } } : {};
|
||||||
|
}
|
||||||
|
|
||||||
export async function refreshKiroToken(refreshToken, providerSpecificData, log, proxyOptions = null) {
|
export async function refreshKiroToken(refreshToken, providerSpecificData, log, proxyOptions = null) {
|
||||||
if (!refreshToken) return null;
|
if (!refreshToken) return null;
|
||||||
return dedupRefresh("kiro", refreshToken, async () => {
|
return dedupRefresh("kiro", refreshToken, async () => {
|
||||||
@@ -417,6 +428,7 @@ export async function refreshKiroToken(refreshToken, providerSpecificData, log,
|
|||||||
accessToken: tokens.accessToken,
|
accessToken: tokens.accessToken,
|
||||||
refreshToken: tokens.refreshToken || refreshToken,
|
refreshToken: tokens.refreshToken || refreshToken,
|
||||||
expiresIn: tokens.expiresIn,
|
expiresIn: tokens.expiresIn,
|
||||||
|
...(await resolveKiroProfileArnPatch(providerSpecificData, tokens.accessToken)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +465,7 @@ export async function refreshKiroToken(refreshToken, providerSpecificData, log,
|
|||||||
accessToken: tokens.accessToken,
|
accessToken: tokens.accessToken,
|
||||||
refreshToken: tokens.refreshToken || refreshToken,
|
refreshToken: tokens.refreshToken || refreshToken,
|
||||||
expiresIn: tokens.expiresIn,
|
expiresIn: tokens.expiresIn,
|
||||||
|
...(await resolveKiroProfileArnPatch(providerSpecificData, tokens.accessToken, tokens.profileArn)),
|
||||||
};
|
};
|
||||||
}, log);
|
}, log);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,27 @@ function extractEmailFromAccessToken(accessToken) {
|
|||||||
return payload.email || payload.preferred_username || payload.sub || undefined;
|
return payload.email || payload.preferred_username || payload.sub || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve Kiro profileArn via CodeWhisperer (IDC/Builder-ID tokens omit it, causing 403)
|
||||||
|
export async function fetchKiroProfileArn(accessToken) {
|
||||||
|
if (!accessToken) return null;
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://codewhisperer.us-east-1.amazonaws.com/ListAvailableProfiles", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ maxResults: 10 }),
|
||||||
|
});
|
||||||
|
if (!response.ok) return null;
|
||||||
|
const data = await response.json();
|
||||||
|
return data.profiles?.find((p) => p.arn?.trim())?.arn?.trim() || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Extract codex account info from id_token or access token
|
// Extract codex account info from id_token or access token
|
||||||
export function extractCodexAccountInfo(idToken) {
|
export function extractCodexAccountInfo(idToken) {
|
||||||
const payload = decodeJwtPayload(idToken);
|
const payload = decodeJwtPayload(idToken);
|
||||||
@@ -1413,7 +1434,13 @@ export async function pollForToken(providerName, deviceCode, codeVerifier, extra
|
|||||||
if (provider.postExchange) {
|
if (provider.postExchange) {
|
||||||
extra = await provider.postExchange(result.data);
|
extra = await provider.postExchange(result.data);
|
||||||
}
|
}
|
||||||
return { success: true, tokens: provider.mapTokens(result.data, extra) };
|
const tokens = provider.mapTokens(result.data, extra);
|
||||||
|
// Kiro IDC/Builder-ID tokens lack profileArn; resolve it to avoid 403
|
||||||
|
if (providerName === "kiro" && !tokens.providerSpecificData?.profileArn) {
|
||||||
|
const profileArn = await fetchKiroProfileArn(tokens.accessToken);
|
||||||
|
if (profileArn) tokens.providerSpecificData.profileArn = profileArn;
|
||||||
|
}
|
||||||
|
return { success: true, tokens };
|
||||||
} else {
|
} else {
|
||||||
// Check if it's still pending authorization
|
// Check if it's still pending authorization
|
||||||
if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
|
if (result.data.error === 'authorization_pending' || result.data.error === 'slow_down') {
|
||||||
|
|||||||
Reference in New Issue
Block a user