- Updated Kiro OAuth configuration
This commit is contained in:
@@ -113,22 +113,33 @@ export const GITHUB_CONFIG = {
|
||||
editorPluginVersion: "copilot-chat/0.26.7",
|
||||
};
|
||||
|
||||
// Kiro OAuth Configuration (AWS SSO OIDC Device Code Flow)
|
||||
// Kiro OAuth Configuration
|
||||
// Supports multiple auth methods:
|
||||
// 1. AWS Builder ID (Device Code Flow)
|
||||
// 2. AWS IAM Identity Center/IDC (Device Code Flow with custom startUrl/region)
|
||||
// 3. Google/GitHub Social Login (Authorization Code Flow - manual callback)
|
||||
// 4. Import Token (paste refresh token from Kiro IDE)
|
||||
export const KIRO_CONFIG = {
|
||||
// AWS SSO OIDC endpoints for Builder ID
|
||||
// AWS SSO OIDC endpoints for Builder ID/IDC (Device Code Flow)
|
||||
ssoOidcEndpoint: "https://oidc.us-east-1.amazonaws.com",
|
||||
registerClientUrl: "https://oidc.us-east-1.amazonaws.com/client/register",
|
||||
deviceAuthUrl: "https://oidc.us-east-1.amazonaws.com/device_authorization",
|
||||
tokenUrl: "https://oidc.us-east-1.amazonaws.com/token",
|
||||
refreshTokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
|
||||
// AWS Builder ID start URL
|
||||
// AWS Builder ID default start URL
|
||||
startUrl: "https://view.awsapps.com/start",
|
||||
// Client registration params
|
||||
clientName: "kiro-cli",
|
||||
clientName: "kiro-oauth-client",
|
||||
clientType: "public",
|
||||
scopes: ["codewhisperer:completions", "codewhisperer:analysis", "codewhisperer:conversations"],
|
||||
grantTypes: ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
|
||||
issuerUrl: "https://identitycenter.amazonaws.com/ssoins-722374e8c3c8e6c6",
|
||||
// Social auth endpoints (Google/GitHub via AWS Cognito)
|
||||
socialAuthEndpoint: "https://prod.us-east-1.auth.desktop.kiro.dev",
|
||||
socialLoginUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/login",
|
||||
socialTokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token",
|
||||
socialRefreshUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
|
||||
// Auth methods
|
||||
authMethods: ["builder-id", "idc", "google", "github", "import"],
|
||||
};
|
||||
|
||||
// OAuth timeout (5 minutes)
|
||||
|
||||
@@ -11,4 +11,5 @@ export { IFlowService } from "./iflow.js";
|
||||
export { AntigravityService } from "./antigravity.js";
|
||||
export { OpenAIService } from "./openai.js";
|
||||
export { GitHubService } from "./github.js";
|
||||
export { KiroService } from "./kiro.js";
|
||||
|
||||
|
||||
276
src/lib/oauth/services/kiro.js
Normal file
276
src/lib/oauth/services/kiro.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import { KIRO_CONFIG } from "../constants/oauth.js";
|
||||
|
||||
/**
|
||||
* Kiro OAuth Service
|
||||
* Supports multiple authentication methods:
|
||||
* 1. AWS Builder ID (Device Code Flow)
|
||||
* 2. AWS IAM Identity Center/IDC (Device Code Flow)
|
||||
* 3. Google/GitHub Social Login (Authorization Code Flow + Manual Callback)
|
||||
* 4. Import Token (Manual refresh token paste)
|
||||
*/
|
||||
|
||||
const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev";
|
||||
|
||||
export class KiroService {
|
||||
/**
|
||||
* Register OIDC client with AWS SSO
|
||||
* Returns clientId and clientSecret for device code flow
|
||||
*/
|
||||
async registerClient(region = "us-east-1") {
|
||||
const endpoint = `https://oidc.${region}.amazonaws.com/client/register`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientName: KIRO_CONFIG.clientName,
|
||||
clientType: KIRO_CONFIG.clientType,
|
||||
scopes: KIRO_CONFIG.scopes,
|
||||
grantTypes: KIRO_CONFIG.grantTypes,
|
||||
issuerUrl: KIRO_CONFIG.issuerUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Failed to register client: ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
clientId: data.clientId,
|
||||
clientSecret: data.clientSecret,
|
||||
clientSecretExpiresAt: data.clientSecretExpiresAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start device authorization for AWS Builder ID or IDC
|
||||
*/
|
||||
async startDeviceAuthorization(clientId, clientSecret, startUrl, region = "us-east-1") {
|
||||
const endpoint = `https://oidc.${region}.amazonaws.com/device_authorization`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientId,
|
||||
clientSecret,
|
||||
startUrl,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Failed to start device authorization: ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
deviceCode: data.deviceCode,
|
||||
userCode: data.userCode,
|
||||
verificationUri: data.verificationUri,
|
||||
verificationUriComplete: data.verificationUriComplete,
|
||||
expiresIn: data.expiresIn,
|
||||
interval: data.interval || 5,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for token using device code (AWS Builder ID/IDC)
|
||||
*/
|
||||
async pollDeviceToken(clientId, clientSecret, deviceCode, region = "us-east-1") {
|
||||
const endpoint = `https://oidc.${region}.amazonaws.com/token`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientId,
|
||||
clientSecret,
|
||||
deviceCode,
|
||||
grantType: "urn:ietf:params:oauth:grant-type:device_code",
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Handle pending/slow_down/errors
|
||||
if (!response.ok || data.error) {
|
||||
return {
|
||||
success: false,
|
||||
error: data.error,
|
||||
errorDescription: data.error_description,
|
||||
pending: data.error === "authorization_pending" || data.error === "slow_down",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
tokens: {
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken,
|
||||
expiresIn: data.expiresIn,
|
||||
tokenType: data.tokenType,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Google/GitHub social login URL
|
||||
* Returns authorization URL for manual callback flow
|
||||
* Uses kiro:// custom protocol as required by AWS Cognito whitelist
|
||||
*/
|
||||
buildSocialLoginUrl(provider, codeChallenge, state) {
|
||||
const idp = provider === "google" ? "Google" : "Github";
|
||||
// AWS Cognito only whitelists kiro:// protocol, not localhost
|
||||
const redirectUri = "kiro://kiro.kiroAgent/authenticate-success";
|
||||
return `${KIRO_AUTH_SERVICE}/login?idp=${idp}&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256&state=${state}&prompt=select_account`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange authorization code for tokens (Social Login)
|
||||
* Must use same redirect_uri as authorization request
|
||||
*/
|
||||
async exchangeSocialCode(code, codeVerifier) {
|
||||
// Must match the redirect_uri used in buildSocialLoginUrl
|
||||
const redirectUri = "kiro://kiro.kiroAgent/authenticate-success";
|
||||
|
||||
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/token`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: redirectUri,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Token exchange failed: ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken,
|
||||
profileArn: data.profileArn,
|
||||
expiresIn: data.expiresIn || 3600,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token using refresh token
|
||||
*/
|
||||
async refreshToken(refreshToken, providerSpecificData = {}) {
|
||||
const { authMethod, clientId, clientSecret, region } = providerSpecificData;
|
||||
|
||||
// AWS SSO OIDC refresh (Builder ID or IDC)
|
||||
if (clientId && clientSecret) {
|
||||
const endpoint = `https://oidc.${region || "us-east-1"}.amazonaws.com/token`;
|
||||
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientId,
|
||||
clientSecret,
|
||||
refreshToken,
|
||||
grantType: "refresh_token",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Token refresh failed: ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken || refreshToken,
|
||||
expiresIn: data.expiresIn,
|
||||
};
|
||||
}
|
||||
|
||||
// Social auth refresh (Google/GitHub)
|
||||
const response = await fetch(`${KIRO_AUTH_SERVICE}/refreshToken`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
refreshToken,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
throw new Error(`Token refresh failed: ${error}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken || refreshToken,
|
||||
profileArn: data.profileArn,
|
||||
expiresIn: data.expiresIn || 3600,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and import refresh token
|
||||
*/
|
||||
async validateImportToken(refreshToken) {
|
||||
// Validate token format
|
||||
if (!refreshToken.startsWith("aorAAAAAG")) {
|
||||
throw new Error("Invalid token format. Token should start with aorAAAAAG...");
|
||||
}
|
||||
|
||||
// Try to refresh to validate
|
||||
try {
|
||||
const result = await this.refreshToken(refreshToken);
|
||||
return {
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken || refreshToken,
|
||||
profileArn: result.profileArn,
|
||||
expiresIn: result.expiresIn,
|
||||
authMethod: "imported",
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Token validation failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch user email from access token (optional, for display)
|
||||
*/
|
||||
extractEmailFromJWT(accessToken) {
|
||||
try {
|
||||
const parts = accessToken.split(".");
|
||||
if (parts.length !== 3) return null;
|
||||
|
||||
// Decode payload (add padding if needed)
|
||||
let payload = parts[1];
|
||||
while (payload.length % 4) {
|
||||
payload += "=";
|
||||
}
|
||||
|
||||
const decoded = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
|
||||
return decoded.email || decoded.preferred_username || decoded.sub;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user