9router/src/app/api/oauth/kiro/auto-import/route.js
quanturbo 4d9da5db26 fix: support Kiro IDC (organization) token import
When logged in to Kiro IDE as an organization (AWS IAM Identity Center),
token import fails because IDC tokens require clientId/clientSecret for
refresh and use a different profileArn than social/builder-id accounts.

Changes:
- auto-import: read clientId/clientSecret from SSO cache client registration
  file, read profileArn from Kiro IDE profile.json, normalize ARN region
- import: accept IDC credentials, use KiroService.refreshToken with them,
  persist credentials for future automatic refreshes
- KiroAuthModal: pass IDC credentials from auto-detect through to import

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 10:32:59 +07:00

132 lines
4.2 KiB
JavaScript

import { NextResponse } from "next/server";
import { readFile, readdir } from "fs/promises";
import { homedir } from "os";
import { join } from "path";
/**
* GET /api/oauth/kiro/auto-import
* Auto-detect and extract Kiro refresh token from AWS SSO cache.
* For IDC (organization) tokens, also resolves clientId/clientSecret from the
* linked client registration file so token refresh works.
*/
export async function GET() {
try {
const cachePath = join(homedir(), ".aws/sso/cache");
let files;
try {
files = await readdir(cachePath);
} catch (error) {
return NextResponse.json({
found: false,
error: "AWS SSO cache not found. Please login to Kiro IDE first.",
});
}
let refreshToken = null;
let foundFile = null;
let tokenData = null;
// First try kiro-auth-token.json
const kiroTokenFile = "kiro-auth-token.json";
if (files.includes(kiroTokenFile)) {
try {
const content = await readFile(join(cachePath, kiroTokenFile), "utf-8");
const data = JSON.parse(content);
if (data.refreshToken && data.refreshToken.startsWith("aorAAAAAG")) {
refreshToken = data.refreshToken;
foundFile = kiroTokenFile;
tokenData = data;
}
} catch (error) {
// Continue to search other files
}
}
// If not found, search all .json files
if (!refreshToken) {
for (const file of files) {
if (!file.endsWith(".json")) continue;
try {
const content = await readFile(join(cachePath, file), "utf-8");
const data = JSON.parse(content);
if (data.refreshToken && data.refreshToken.startsWith("aorAAAAAG")) {
refreshToken = data.refreshToken;
foundFile = file;
tokenData = data;
break;
}
} catch (error) {
continue;
}
}
}
if (!refreshToken) {
return NextResponse.json({
found: false,
error: "Kiro token not found in AWS SSO cache. Please login to Kiro IDE first.",
});
}
// For IDC/organization tokens, resolve clientId and clientSecret from
// the linked client registration file (referenced by clientIdHash).
let clientId = null;
let clientSecret = null;
const region = tokenData?.region || null;
const authMethod = tokenData?.authMethod || null;
if (tokenData?.clientIdHash) {
const clientFile = `${tokenData.clientIdHash}.json`;
try {
const clientContent = await readFile(join(cachePath, clientFile), "utf-8");
const clientData = JSON.parse(clientContent);
if (clientData.clientId && clientData.clientSecret) {
clientId = clientData.clientId;
clientSecret = clientData.clientSecret;
}
} catch (error) {
// Client registration file not found - continue without it
}
}
// Read profileArn from Kiro IDE's profile.json.
// Important: the runtime gateway requires us-east-1 in the ARN regardless
// of the IDC region, so we normalize the region in the ARN to us-east-1.
let profileArn = null;
const kiroProfilePaths = [
join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "Kiro", "User", "globalStorage", "kiro.kiroagent", "profile.json"),
join(homedir(), ".config", "Kiro", "User", "globalStorage", "kiro.kiroagent", "profile.json"),
];
for (const profilePath of kiroProfilePaths) {
try {
const profileContent = await readFile(profilePath, "utf-8");
const profileData = JSON.parse(profileContent);
if (profileData.arn) {
// Normalize region to us-east-1 for the runtime gateway
profileArn = profileData.arn.replace(/arn:aws:codewhisperer:[^:]+:/, "arn:aws:codewhisperer:us-east-1:");
break;
}
} catch (error) {
continue;
}
}
return NextResponse.json({
found: true,
refreshToken,
source: foundFile,
clientId,
clientSecret,
region,
authMethod,
profileArn,
});
} catch (error) {
console.log("Kiro auto-import error:", error);
return NextResponse.json(
{ found: false, error: error.message },
{ status: 500 }
);
}
}