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>
132 lines
4.2 KiB
JavaScript
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 }
|
|
);
|
|
}
|
|
}
|