@kervnet/opencode-kiro-auth 1.6.5 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/core/auth/auth-handler.d.ts +1 -1
- package/dist/core/auth/auth-handler.js +6 -8
- package/dist/core/auth/kiro-cli-auth-method.d.ts +2 -5
- package/dist/core/auth/kiro-cli-auth-method.js +8 -4
- package/dist/core/request/request-handler.js +1 -10
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/kiro/auth.js +0 -5
- package/dist/plugin/accounts.js +1 -3
- package/dist/plugin/config/schema.d.ts +3 -0
- package/dist/plugin/config/schema.js +2 -0
- package/dist/plugin/request.js +11 -17
- package/dist/plugin/storage/migrations.js +0 -8
- package/dist/plugin/storage/sqlite.js +4 -5
- package/dist/plugin/sync/aws-sso.d.ts +2 -0
- package/dist/plugin/sync/aws-sso.js +50 -0
- package/dist/plugin/sync/kiro-cli.js +44 -20
- package/dist/plugin/token.js +5 -11
- package/dist/plugin/types.d.ts +1 -3
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +4 -15
- package/package.json +4 -9
- package/dist/kiro/iam.d.ts +0 -4
- package/dist/kiro/iam.js +0 -34
- package/dist/plugin/sync/iam-cli.d.ts +0 -1
- package/dist/plugin/sync/iam-cli.js +0 -66
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ OpenCode plugin for AWS Kiro (CodeWhisperer) providing access to Claude Sonnet a
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **Multiple Auth Methods**: Supports AWS Builder ID (IDC), Kiro Desktop (CLI-based), and
|
|
11
|
+
- **Multiple Auth Methods**: Supports AWS Builder ID (IDC), Kiro Desktop (CLI-based), and AWS SSO authentication.
|
|
12
12
|
- **Auto-Sync Kiro CLI**: Automatically imports and synchronizes active sessions from your local `kiro-cli` SQLite database.
|
|
13
|
-
- **
|
|
13
|
+
- **Auto-Sync AWS SSO**: Automatically imports credentials from `~/.aws/sso/cache` for seamless integration with AWS profiles.
|
|
14
14
|
- **Gradual Context Truncation**: Intelligently prevents error 400 by reducing context size dynamically during retries.
|
|
15
15
|
- **Intelligent Account Rotation**: Prioritizes multi-account usage based on lowest available quota.
|
|
16
16
|
- **High-Performance Storage**: Efficient account and usage management using native Bun SQLite.
|
|
@@ -74,11 +74,15 @@ Add the plugin to your `opencode.json` or `opencode.jsonc`:
|
|
|
74
74
|
- Perform login directly in your terminal using `kiro-cli login`.
|
|
75
75
|
- The plugin will automatically detect and import your session on startup.
|
|
76
76
|
- For AWS IAM Identity Center (SSO/IDC), the plugin imports both the token and device registration (OIDC client credentials) from the `kiro-cli` database.
|
|
77
|
-
2. **
|
|
77
|
+
2. **Authentication via AWS SSO**:
|
|
78
|
+
- Ensure you have AWS SSO configured in `~/.aws/config` with active sessions.
|
|
79
|
+
- The plugin automatically imports credentials from `~/.aws/sso/cache` on startup.
|
|
80
|
+
- No additional configuration needed - just use your existing AWS SSO profiles.
|
|
81
|
+
3. **Direct Authentication**:
|
|
78
82
|
- Run `opencode auth login`.
|
|
79
83
|
- Select `Other`, type `kiro`, and press enter.
|
|
80
84
|
- Follow the instructions for **AWS Builder ID (IDC)**.
|
|
81
|
-
|
|
85
|
+
4. Configuration will be automatically managed at `~/.config/opencode/kiro.db`.
|
|
82
86
|
|
|
83
87
|
## Troubleshooting
|
|
84
88
|
|
|
@@ -99,6 +103,7 @@ The plugin supports extensive configuration options. Edit `~/.config/opencode/ki
|
|
|
99
103
|
```json
|
|
100
104
|
{
|
|
101
105
|
"auto_sync_kiro_cli": true,
|
|
106
|
+
"auto_sync_aws_sso": true,
|
|
102
107
|
"account_selection_strategy": "lowest-usage",
|
|
103
108
|
"default_region": "us-east-1",
|
|
104
109
|
"rate_limit_retry_delay_ms": 5000,
|
|
@@ -117,6 +122,7 @@ The plugin supports extensive configuration options. Edit `~/.config/opencode/ki
|
|
|
117
122
|
### Configuration Options
|
|
118
123
|
|
|
119
124
|
- `auto_sync_kiro_cli`: Automatically sync sessions from Kiro CLI (default: `true`).
|
|
125
|
+
- `auto_sync_aws_sso`: Automatically sync credentials from AWS SSO cache (default: `true`).
|
|
120
126
|
- `account_selection_strategy`: Account rotation strategy (`sticky`, `round-robin`, `lowest-usage`).
|
|
121
127
|
- `default_region`: AWS region (`us-east-1`, `us-west-2`).
|
|
122
128
|
- `rate_limit_retry_delay_ms`: Delay between rate limit retries (1000-60000ms).
|
|
@@ -10,9 +10,7 @@ export class AuthHandler {
|
|
|
10
10
|
}
|
|
11
11
|
async initialize() {
|
|
12
12
|
const { syncFromKiroCli } = await import('../../plugin/sync/kiro-cli.js');
|
|
13
|
-
|
|
14
|
-
await syncFromKiroCli();
|
|
15
|
-
}
|
|
13
|
+
await syncFromKiroCli();
|
|
16
14
|
}
|
|
17
15
|
setAccountManager(am) {
|
|
18
16
|
this.accountManager = am;
|
|
@@ -22,17 +20,17 @@ export class AuthHandler {
|
|
|
22
20
|
return [];
|
|
23
21
|
}
|
|
24
22
|
const idcMethod = new IdcAuthMethod(this.config, this.repository);
|
|
25
|
-
const
|
|
23
|
+
const kiroCliMethod = new KiroCliAuthMethod(this.config, this.repository);
|
|
26
24
|
return [
|
|
27
25
|
{
|
|
28
26
|
id: 'kiro-cli',
|
|
29
|
-
label: '
|
|
30
|
-
type: '
|
|
31
|
-
authorize: () =>
|
|
27
|
+
label: 'Kiro CLI (IAM Identity Center)',
|
|
28
|
+
type: 'oauth',
|
|
29
|
+
authorize: () => kiroCliMethod.authorize()
|
|
32
30
|
},
|
|
33
31
|
{
|
|
34
32
|
id: 'idc',
|
|
35
|
-
label: 'AWS Builder ID (
|
|
33
|
+
label: 'AWS Builder ID (Direct)',
|
|
36
34
|
type: 'oauth',
|
|
37
35
|
authorize: (inputs) => idcMethod.authorize(inputs)
|
|
38
36
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type { AccountRepository } from '../../infrastructure/database/account-repository.js';
|
|
2
1
|
export declare class KiroCliAuthMethod {
|
|
3
2
|
private config;
|
|
4
3
|
private repository;
|
|
5
|
-
constructor(config: any, repository:
|
|
6
|
-
authorize(): Promise<
|
|
7
|
-
success: boolean;
|
|
8
|
-
}>;
|
|
4
|
+
constructor(config: any, repository: any);
|
|
5
|
+
authorize(): Promise<any>;
|
|
9
6
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { syncFromKiroCli } from '../../plugin/sync/kiro-cli.js';
|
|
2
1
|
export class KiroCliAuthMethod {
|
|
3
2
|
config;
|
|
4
3
|
repository;
|
|
@@ -8,12 +7,17 @@ export class KiroCliAuthMethod {
|
|
|
8
7
|
}
|
|
9
8
|
async authorize() {
|
|
10
9
|
// Sync from kiro-cli
|
|
10
|
+
const { syncFromKiroCli } = await import('../../plugin/sync/kiro-cli.js');
|
|
11
11
|
await syncFromKiroCli();
|
|
12
|
-
// Check if we have accounts
|
|
12
|
+
// Check if we have any accounts
|
|
13
13
|
const accounts = await this.repository.findAll();
|
|
14
14
|
if (accounts.length === 0) {
|
|
15
|
-
throw new Error('No
|
|
15
|
+
throw new Error('No Kiro CLI session found. Please run "kiro-cli login" first, then try again.');
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
// Return success - no actual OAuth needed since we sync from kiro-cli
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
message: `Successfully synced ${accounts.length} account(s) from Kiro CLI`
|
|
21
|
+
};
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { signRequestWithIAM } from '../../kiro/iam';
|
|
2
1
|
import { isPermanentError } from '../../plugin/health';
|
|
3
2
|
import * as logger from '../../plugin/logger';
|
|
4
3
|
import { transformToCodeWhisperer } from '../../plugin/request';
|
|
@@ -76,15 +75,7 @@ export class RequestHandler {
|
|
|
76
75
|
this.logRequest(prep, acc, apiTimestamp);
|
|
77
76
|
}
|
|
78
77
|
try {
|
|
79
|
-
|
|
80
|
-
if (auth.authMethod === 'iam' && auth.awsProfile) {
|
|
81
|
-
const signedHeaders = await signRequestWithIAM(prep.url, prep.init.method || 'POST', prep.init.headers, prep.init.body, auth.awsProfile, auth.region);
|
|
82
|
-
finalInit = {
|
|
83
|
-
...prep.init,
|
|
84
|
-
headers: signedHeaders
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
const res = await fetch(prep.url, finalInit);
|
|
78
|
+
const res = await fetch(prep.url, prep.init);
|
|
88
79
|
if (apiTimestamp) {
|
|
89
80
|
this.logResponse(res, prep, apiTimestamp);
|
|
90
81
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/kiro/auth.js
CHANGED
|
@@ -6,8 +6,6 @@ export function decodeRefreshToken(refresh) {
|
|
|
6
6
|
const authMethod = parts[parts.length - 1];
|
|
7
7
|
if (authMethod === 'idc')
|
|
8
8
|
return { refreshToken, clientId: parts[1], clientSecret: parts[2], authMethod: 'idc' };
|
|
9
|
-
if (authMethod === 'iam')
|
|
10
|
-
return { refreshToken, profileArn: parts[1], authMethod: 'iam' };
|
|
11
9
|
if (authMethod === 'desktop')
|
|
12
10
|
return { refreshToken, authMethod: 'desktop' };
|
|
13
11
|
return { refreshToken, authMethod: 'desktop' };
|
|
@@ -23,8 +21,5 @@ export function encodeRefreshToken(parts) {
|
|
|
23
21
|
throw new Error('Missing credentials');
|
|
24
22
|
return `${parts.refreshToken}|${parts.clientId}|${parts.clientSecret}|idc`;
|
|
25
23
|
}
|
|
26
|
-
if (parts.authMethod === 'iam') {
|
|
27
|
-
return `${parts.refreshToken}|${parts.profileArn || ''}|iam`;
|
|
28
|
-
}
|
|
29
24
|
return `${parts.refreshToken}|desktop`;
|
|
30
25
|
}
|
package/dist/plugin/accounts.js
CHANGED
|
@@ -30,7 +30,6 @@ export class AccountManager {
|
|
|
30
30
|
clientId: r.client_id,
|
|
31
31
|
clientSecret: r.client_secret,
|
|
32
32
|
profileArn: r.profile_arn,
|
|
33
|
-
awsProfile: r.aws_profile,
|
|
34
33
|
refreshToken: r.refresh_token,
|
|
35
34
|
accessToken: r.access_token,
|
|
36
35
|
expiresAt: r.expires_at,
|
|
@@ -230,8 +229,7 @@ export class AccountManager {
|
|
|
230
229
|
profileArn: a.profileArn,
|
|
231
230
|
clientId: a.clientId,
|
|
232
231
|
clientSecret: a.clientSecret,
|
|
233
|
-
email: a.email
|
|
234
|
-
awsProfile: a.awsProfile
|
|
232
|
+
email: a.email
|
|
235
233
|
};
|
|
236
234
|
}
|
|
237
235
|
}
|
|
@@ -17,6 +17,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
17
17
|
auth_server_port_range: z.ZodDefault<z.ZodNumber>;
|
|
18
18
|
usage_tracking_enabled: z.ZodDefault<z.ZodBoolean>;
|
|
19
19
|
auto_sync_kiro_cli: z.ZodDefault<z.ZodBoolean>;
|
|
20
|
+
auto_sync_aws_sso: z.ZodDefault<z.ZodBoolean>;
|
|
20
21
|
enable_log_api_request: z.ZodDefault<z.ZodBoolean>;
|
|
21
22
|
}, "strip", z.ZodTypeAny, {
|
|
22
23
|
account_selection_strategy: "sticky" | "round-robin" | "lowest-usage";
|
|
@@ -31,6 +32,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
31
32
|
auth_server_port_range: number;
|
|
32
33
|
usage_tracking_enabled: boolean;
|
|
33
34
|
auto_sync_kiro_cli: boolean;
|
|
35
|
+
auto_sync_aws_sso: boolean;
|
|
34
36
|
enable_log_api_request: boolean;
|
|
35
37
|
$schema?: string | undefined;
|
|
36
38
|
}, {
|
|
@@ -47,6 +49,7 @@ export declare const KiroConfigSchema: z.ZodObject<{
|
|
|
47
49
|
auth_server_port_range?: number | undefined;
|
|
48
50
|
usage_tracking_enabled?: boolean | undefined;
|
|
49
51
|
auto_sync_kiro_cli?: boolean | undefined;
|
|
52
|
+
auto_sync_aws_sso?: boolean | undefined;
|
|
50
53
|
enable_log_api_request?: boolean | undefined;
|
|
51
54
|
}>;
|
|
52
55
|
export type KiroConfig = z.infer<typeof KiroConfigSchema>;
|
|
@@ -15,6 +15,7 @@ export const KiroConfigSchema = z.object({
|
|
|
15
15
|
auth_server_port_range: z.number().min(1).max(100).default(10),
|
|
16
16
|
usage_tracking_enabled: z.boolean().default(true),
|
|
17
17
|
auto_sync_kiro_cli: z.boolean().default(true),
|
|
18
|
+
auto_sync_aws_sso: z.boolean().default(true),
|
|
18
19
|
enable_log_api_request: z.boolean().default(false)
|
|
19
20
|
});
|
|
20
21
|
export const DEFAULT_CONFIG = {
|
|
@@ -30,5 +31,6 @@ export const DEFAULT_CONFIG = {
|
|
|
30
31
|
auth_server_port_range: 10,
|
|
31
32
|
usage_tracking_enabled: true,
|
|
32
33
|
auto_sync_kiro_cli: true,
|
|
34
|
+
auto_sync_aws_sso: true,
|
|
33
35
|
enable_log_api_request: false
|
|
34
36
|
};
|
package/dist/plugin/request.js
CHANGED
|
@@ -128,9 +128,6 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
};
|
|
131
|
-
if (auth.profileArn) {
|
|
132
|
-
request.profileArn = auth.profileArn;
|
|
133
|
-
}
|
|
134
131
|
const toolUsesInHistory = history.flatMap((h) => h.assistantResponseMessage?.toolUses || []);
|
|
135
132
|
const allToolUseIdsInHistory = new Set(toolUsesInHistory.map((tu) => tu.toolUseId));
|
|
136
133
|
const finalCurTrs = [];
|
|
@@ -218,24 +215,21 @@ export function transformToCodeWhisperer(url, body, model, auth, think = false,
|
|
|
218
215
|
const osP = os.platform(), osR = os.release(), nodeV = process.version.replace('v', ''), kiroV = KIRO_CONSTANTS.KIRO_VERSION;
|
|
219
216
|
const osN = osP === 'win32' ? `windows#${osR}` : osP === 'darwin' ? `macos#${osR}` : `${osP}#${osR}`;
|
|
220
217
|
const ua = `aws-sdk-js/1.0.0 ua/2.1 os/${osN} lang/js md/nodejs#${nodeV} api/codewhispererruntime#1.0.0 m/E KiroIDE-${kiroV}-${machineId}`;
|
|
221
|
-
const baseHeaders = {
|
|
222
|
-
'Content-Type': 'application/json',
|
|
223
|
-
Accept: 'application/json',
|
|
224
|
-
'amz-sdk-invocation-id': crypto.randomUUID(),
|
|
225
|
-
'amz-sdk-request': 'attempt=1; max=1',
|
|
226
|
-
'x-amzn-kiro-agent-mode': 'vibe',
|
|
227
|
-
'x-amz-user-agent': `aws-sdk-js/1.0.0 KiroIDE-${kiroV}-${machineId}`,
|
|
228
|
-
'user-agent': ua,
|
|
229
|
-
Connection: 'close'
|
|
230
|
-
};
|
|
231
|
-
if (auth.authMethod !== 'iam') {
|
|
232
|
-
baseHeaders.Authorization = `Bearer ${auth.access}`;
|
|
233
|
-
}
|
|
234
218
|
return {
|
|
235
219
|
url: KIRO_CONSTANTS.BASE_URL.replace('{{region}}', auth.region),
|
|
236
220
|
init: {
|
|
237
221
|
method: 'POST',
|
|
238
|
-
headers:
|
|
222
|
+
headers: {
|
|
223
|
+
'Content-Type': 'application/json',
|
|
224
|
+
Accept: 'application/json',
|
|
225
|
+
Authorization: `Bearer ${auth.access}`,
|
|
226
|
+
'amz-sdk-invocation-id': crypto.randomUUID(),
|
|
227
|
+
'amz-sdk-request': 'attempt=1; max=1',
|
|
228
|
+
'x-amzn-kiro-agent-mode': 'vibe',
|
|
229
|
+
'x-amz-user-agent': `aws-sdk-js/1.0.0 KiroIDE-${kiroV}-${machineId}`,
|
|
230
|
+
'user-agent': ua,
|
|
231
|
+
Connection: 'close'
|
|
232
|
+
},
|
|
239
233
|
body: JSON.stringify(request)
|
|
240
234
|
},
|
|
241
235
|
streaming: true,
|
|
@@ -2,7 +2,6 @@ export function runMigrations(db) {
|
|
|
2
2
|
migrateToUniqueRefreshToken(db);
|
|
3
3
|
migrateRealEmailColumn(db);
|
|
4
4
|
migrateUsageTable(db);
|
|
5
|
-
migrateAwsProfileColumn(db);
|
|
6
5
|
}
|
|
7
6
|
function migrateToUniqueRefreshToken(db) {
|
|
8
7
|
const hasIndex = db
|
|
@@ -108,10 +107,3 @@ function migrateUsageTable(db) {
|
|
|
108
107
|
db.run('DROP TABLE usage');
|
|
109
108
|
}
|
|
110
109
|
}
|
|
111
|
-
function migrateAwsProfileColumn(db) {
|
|
112
|
-
const columns = db.prepare('PRAGMA table_info(accounts)').all();
|
|
113
|
-
const names = new Set(columns.map((c) => c.name));
|
|
114
|
-
if (!names.has('aws_profile')) {
|
|
115
|
-
db.run('ALTER TABLE accounts ADD COLUMN aws_profile TEXT');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -45,20 +45,20 @@ export class KiroDatabase {
|
|
|
45
45
|
.prepare(`
|
|
46
46
|
INSERT INTO accounts (
|
|
47
47
|
id, email, auth_method, region, client_id, client_secret,
|
|
48
|
-
profile_arn,
|
|
48
|
+
profile_arn, refresh_token, access_token, expires_at, rate_limit_reset,
|
|
49
49
|
is_healthy, unhealthy_reason, recovery_time, fail_count, last_used,
|
|
50
50
|
used_count, limit_count, last_sync
|
|
51
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
51
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
52
52
|
ON CONFLICT(refresh_token) DO UPDATE SET
|
|
53
53
|
id=excluded.id, email=excluded.email, auth_method=excluded.auth_method,
|
|
54
54
|
region=excluded.region, client_id=excluded.client_id, client_secret=excluded.client_secret,
|
|
55
|
-
profile_arn=excluded.profile_arn,
|
|
55
|
+
profile_arn=excluded.profile_arn, access_token=excluded.access_token, expires_at=excluded.expires_at,
|
|
56
56
|
rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
|
|
57
57
|
unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
|
|
58
58
|
fail_count=excluded.fail_count, last_used=excluded.last_used,
|
|
59
59
|
used_count=excluded.used_count, limit_count=excluded.limit_count, last_sync=excluded.last_sync
|
|
60
60
|
`)
|
|
61
|
-
.run(acc.id, acc.email, acc.authMethod, acc.region, acc.clientId || null, acc.clientSecret || null, acc.profileArn || null, acc.
|
|
61
|
+
.run(acc.id, acc.email, acc.authMethod, acc.region, acc.clientId || null, acc.clientSecret || null, acc.profileArn || null, acc.refreshToken, acc.accessToken, acc.expiresAt, acc.rateLimitResetTime || 0, acc.isHealthy ? 1 : 0, acc.unhealthyReason || null, acc.recoveryTime || null, acc.failCount || 0, acc.lastUsed || 0, acc.usedCount || 0, acc.limitCount || 0, acc.lastSync || 0);
|
|
62
62
|
}
|
|
63
63
|
async upsertAccount(acc) {
|
|
64
64
|
await withDatabaseLock(this.path, async () => {
|
|
@@ -110,7 +110,6 @@ export class KiroDatabase {
|
|
|
110
110
|
clientId: row.client_id,
|
|
111
111
|
clientSecret: row.client_secret,
|
|
112
112
|
profileArn: row.profile_arn,
|
|
113
|
-
awsProfile: row.aws_profile,
|
|
114
113
|
refreshToken: row.refresh_token,
|
|
115
114
|
accessToken: row.access_token,
|
|
116
115
|
expiresAt: row.expires_at,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createDeterministicAccountId } from '../accounts';
|
|
5
|
+
import * as logger from '../logger';
|
|
6
|
+
export async function syncFromAwsSso() {
|
|
7
|
+
const accounts = [];
|
|
8
|
+
const ssoDir = join(homedir(), '.aws', 'sso', 'cache');
|
|
9
|
+
try {
|
|
10
|
+
const files = await readdir(ssoDir);
|
|
11
|
+
const jsonFiles = files.filter((f) => f.endsWith('.json') && !f.includes('.tmp'));
|
|
12
|
+
for (const file of jsonFiles) {
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(join(ssoDir, file), 'utf-8');
|
|
15
|
+
const entry = JSON.parse(content);
|
|
16
|
+
if (!entry.accessToken || !entry.refreshToken)
|
|
17
|
+
continue;
|
|
18
|
+
const expiresAt = new Date(entry.expiresAt).getTime();
|
|
19
|
+
if (expiresAt < Date.now())
|
|
20
|
+
continue;
|
|
21
|
+
const id = createDeterministicAccountId(entry.startUrl, 'aws-sso', entry.clientId, undefined);
|
|
22
|
+
accounts.push({
|
|
23
|
+
id,
|
|
24
|
+
email: entry.startUrl,
|
|
25
|
+
authMethod: 'aws-sso',
|
|
26
|
+
region: (entry.region || 'us-east-1'),
|
|
27
|
+
clientId: entry.clientId,
|
|
28
|
+
clientSecret: entry.clientSecret,
|
|
29
|
+
refreshToken: entry.refreshToken,
|
|
30
|
+
accessToken: entry.accessToken,
|
|
31
|
+
expiresAt,
|
|
32
|
+
rateLimitResetTime: 0,
|
|
33
|
+
isHealthy: true,
|
|
34
|
+
failCount: 0,
|
|
35
|
+
lastUsed: Date.now(),
|
|
36
|
+
usedCount: 0,
|
|
37
|
+
limitCount: 0
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger.debug('Failed to parse SSO cache file', { file, error: err });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
logger.log(`Synced ${accounts.length} AWS SSO accounts`);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
logger.debug('Failed to read AWS SSO cache', { error: err });
|
|
48
|
+
}
|
|
49
|
+
return accounts;
|
|
50
|
+
}
|
|
@@ -4,10 +4,8 @@ import { createDeterministicAccountId } from '../accounts';
|
|
|
4
4
|
import * as logger from '../logger';
|
|
5
5
|
import { kiroDb } from '../storage/sqlite';
|
|
6
6
|
import { fetchUsageLimits } from '../usage';
|
|
7
|
-
import { syncIAMFromKiroCli } from './iam-cli';
|
|
8
7
|
import { findClientCredsRecursive, getCliDbPath, makePlaceholderEmail, normalizeExpiresAt, safeJsonParse } from './kiro-cli-parser';
|
|
9
8
|
export async function syncFromKiroCli() {
|
|
10
|
-
await syncIAMFromKiroCli();
|
|
11
9
|
const dbPath = getCliDbPath();
|
|
12
10
|
if (!existsSync(dbPath))
|
|
13
11
|
return;
|
|
@@ -15,6 +13,28 @@ export async function syncFromKiroCli() {
|
|
|
15
13
|
const cliDb = new Database(dbPath, { readonly: true });
|
|
16
14
|
cliDb.run('PRAGMA busy_timeout = 5000');
|
|
17
15
|
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
16
|
+
// Get profile ARN from state table
|
|
17
|
+
let profileArn;
|
|
18
|
+
let profileRegion;
|
|
19
|
+
try {
|
|
20
|
+
const stateRow = cliDb
|
|
21
|
+
.prepare("SELECT value FROM state WHERE key = 'api.codewhisperer.profile'")
|
|
22
|
+
.get();
|
|
23
|
+
if (stateRow?.value) {
|
|
24
|
+
const profileData = safeJsonParse(stateRow.value);
|
|
25
|
+
profileArn = profileData?.arn;
|
|
26
|
+
// Extract region from ARN: arn:aws:codewhisperer:REGION:...
|
|
27
|
+
if (profileArn) {
|
|
28
|
+
const arnParts = profileArn.split(':');
|
|
29
|
+
if (arnParts.length >= 4) {
|
|
30
|
+
profileRegion = arnParts[3];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
logger.debug('Could not read profile from state table', e);
|
|
37
|
+
}
|
|
18
38
|
const deviceRegRow = rows.find((r) => typeof r?.key === 'string' && r.key.includes('device-registration'));
|
|
19
39
|
const deviceReg = safeJsonParse(deviceRegRow?.value);
|
|
20
40
|
const regCreds = deviceReg ? findClientCredsRecursive(deviceReg) : {};
|
|
@@ -23,11 +43,16 @@ export async function syncFromKiroCli() {
|
|
|
23
43
|
const data = safeJsonParse(row.value);
|
|
24
44
|
if (!data)
|
|
25
45
|
continue;
|
|
46
|
+
const tokenExpiresAt = normalizeExpiresAt(data.expires_at ?? data.expiresAt) || Date.now() + 3600000;
|
|
47
|
+
// Skip expired tokens
|
|
48
|
+
if (tokenExpiresAt < Date.now()) {
|
|
49
|
+
logger.debug('Kiro CLI sync: skipping expired token', { key: row.key });
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
26
52
|
const isIdc = row.key.includes('odic');
|
|
27
53
|
const authMethod = isIdc ? 'idc' : 'desktop';
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
const profileArn = data.profile_arn || data.profileArn;
|
|
54
|
+
const region = profileRegion || data.region || 'us-east-1';
|
|
55
|
+
const tokenProfileArn = data.profile_arn || data.profileArn || profileArn;
|
|
31
56
|
const accessToken = data.access_token || data.accessToken || '';
|
|
32
57
|
const refreshToken = data.refresh_token || data.refreshToken;
|
|
33
58
|
if (!refreshToken)
|
|
@@ -38,7 +63,6 @@ export async function syncFromKiroCli() {
|
|
|
38
63
|
logger.warn('Kiro CLI sync: missing IDC device credentials; skipping token import');
|
|
39
64
|
continue;
|
|
40
65
|
}
|
|
41
|
-
const cliExpiresAt = normalizeExpiresAt(data.expires_at ?? data.expiresAt) || Date.now() + 3600000;
|
|
42
66
|
let usedCount = 0;
|
|
43
67
|
let limitCount = 0;
|
|
44
68
|
let email;
|
|
@@ -47,10 +71,10 @@ export async function syncFromKiroCli() {
|
|
|
47
71
|
const authForUsage = {
|
|
48
72
|
refresh: '',
|
|
49
73
|
access: accessToken,
|
|
50
|
-
expires:
|
|
74
|
+
expires: tokenExpiresAt,
|
|
51
75
|
authMethod,
|
|
52
76
|
region,
|
|
53
|
-
profileArn,
|
|
77
|
+
profileArn: tokenProfileArn,
|
|
54
78
|
clientId,
|
|
55
79
|
clientSecret,
|
|
56
80
|
email: ''
|
|
@@ -73,8 +97,8 @@ export async function syncFromKiroCli() {
|
|
|
73
97
|
const all = kiroDb.getAccounts();
|
|
74
98
|
if (!email) {
|
|
75
99
|
let existing;
|
|
76
|
-
if (
|
|
77
|
-
existing = all.find((a) => a.auth_method === authMethod && a.profile_arn ===
|
|
100
|
+
if (tokenProfileArn) {
|
|
101
|
+
existing = all.find((a) => a.auth_method === authMethod && a.profile_arn === tokenProfileArn);
|
|
78
102
|
}
|
|
79
103
|
if (!existing && authMethod === 'idc' && clientId) {
|
|
80
104
|
existing = all.find((a) => a.auth_method === 'idc' && a.client_id === clientId);
|
|
@@ -83,19 +107,19 @@ export async function syncFromKiroCli() {
|
|
|
83
107
|
email = existing.email;
|
|
84
108
|
}
|
|
85
109
|
else {
|
|
86
|
-
email = makePlaceholderEmail(authMethod, region, clientId,
|
|
110
|
+
email = makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
87
111
|
}
|
|
88
112
|
}
|
|
89
|
-
const resolvedEmail = email || makePlaceholderEmail(authMethod, region, clientId,
|
|
90
|
-
const id = createDeterministicAccountId(resolvedEmail, authMethod, clientId,
|
|
113
|
+
const resolvedEmail = email || makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
114
|
+
const id = createDeterministicAccountId(resolvedEmail, authMethod, clientId, tokenProfileArn);
|
|
91
115
|
const existingById = all.find((a) => a.id === id);
|
|
92
116
|
if (existingById &&
|
|
93
117
|
existingById.is_healthy === 1 &&
|
|
94
|
-
existingById.expires_at >=
|
|
118
|
+
existingById.expires_at >= tokenExpiresAt)
|
|
95
119
|
continue;
|
|
96
120
|
if (usageOk) {
|
|
97
|
-
const placeholderEmail = makePlaceholderEmail(authMethod, region, clientId,
|
|
98
|
-
const placeholderId = createDeterministicAccountId(placeholderEmail, authMethod, clientId,
|
|
121
|
+
const placeholderEmail = makePlaceholderEmail(authMethod, region, clientId, tokenProfileArn);
|
|
122
|
+
const placeholderId = createDeterministicAccountId(placeholderEmail, authMethod, clientId, tokenProfileArn);
|
|
99
123
|
if (placeholderId !== id) {
|
|
100
124
|
const placeholderRow = all.find((a) => a.id === placeholderId);
|
|
101
125
|
if (placeholderRow) {
|
|
@@ -106,10 +130,10 @@ export async function syncFromKiroCli() {
|
|
|
106
130
|
region: placeholderRow.region || region,
|
|
107
131
|
clientId,
|
|
108
132
|
clientSecret,
|
|
109
|
-
profileArn,
|
|
133
|
+
profileArn: tokenProfileArn,
|
|
110
134
|
refreshToken: placeholderRow.refresh_token || refreshToken,
|
|
111
135
|
accessToken: placeholderRow.access_token || accessToken,
|
|
112
|
-
expiresAt: placeholderRow.expires_at ||
|
|
136
|
+
expiresAt: placeholderRow.expires_at || tokenExpiresAt,
|
|
113
137
|
rateLimitResetTime: 0,
|
|
114
138
|
isHealthy: false,
|
|
115
139
|
failCount: 10,
|
|
@@ -129,10 +153,10 @@ export async function syncFromKiroCli() {
|
|
|
129
153
|
region,
|
|
130
154
|
clientId,
|
|
131
155
|
clientSecret,
|
|
132
|
-
profileArn,
|
|
156
|
+
profileArn: tokenProfileArn,
|
|
133
157
|
refreshToken,
|
|
134
158
|
accessToken,
|
|
135
|
-
expiresAt:
|
|
159
|
+
expiresAt: tokenExpiresAt,
|
|
136
160
|
rateLimitResetTime: 0,
|
|
137
161
|
isHealthy: true,
|
|
138
162
|
failCount: 0,
|
package/dist/plugin/token.js
CHANGED
|
@@ -2,22 +2,16 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import { decodeRefreshToken, encodeRefreshToken } from '../kiro/auth';
|
|
3
3
|
import { KiroTokenRefreshError } from './errors';
|
|
4
4
|
export async function refreshAccessToken(auth) {
|
|
5
|
-
if (auth.authMethod === 'iam') {
|
|
6
|
-
return {
|
|
7
|
-
...auth,
|
|
8
|
-
access: 'iam-signed',
|
|
9
|
-
expires: Date.now() + 3600000
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
5
|
const p = decodeRefreshToken(auth.refresh);
|
|
13
6
|
const isIdc = auth.authMethod === 'idc';
|
|
14
|
-
const
|
|
7
|
+
const isAwsSso = auth.authMethod === 'aws-sso';
|
|
8
|
+
const url = isIdc || isAwsSso
|
|
15
9
|
? `https://oidc.${auth.region}.amazonaws.com/token`
|
|
16
10
|
: `https://prod.${auth.region}.auth.desktop.kiro.dev/refreshToken`;
|
|
17
|
-
if (isIdc && (!p.clientId || !p.clientSecret)) {
|
|
11
|
+
if ((isIdc || isAwsSso) && (!p.clientId || !p.clientSecret)) {
|
|
18
12
|
throw new KiroTokenRefreshError('Missing creds', 'MISSING_CREDENTIALS');
|
|
19
13
|
}
|
|
20
|
-
const requestBody = isIdc
|
|
14
|
+
const requestBody = isIdc || isAwsSso
|
|
21
15
|
? {
|
|
22
16
|
refreshToken: p.refreshToken,
|
|
23
17
|
clientId: p.clientId,
|
|
@@ -31,7 +25,7 @@ export async function refreshAccessToken(auth) {
|
|
|
31
25
|
.createHash('sha256')
|
|
32
26
|
.update(auth.profileArn || auth.clientId || 'KIRO_DEFAULT_MACHINE')
|
|
33
27
|
.digest('hex');
|
|
34
|
-
const ua = isIdc ? 'aws-sdk-js/1.0.0' : `KiroIDE-0.7.45-${machineId}`;
|
|
28
|
+
const ua = isIdc || isAwsSso ? 'aws-sdk-js/1.0.0' : `KiroIDE-0.7.45-${machineId}`;
|
|
35
29
|
try {
|
|
36
30
|
const res = await fetch(url, {
|
|
37
31
|
method: 'POST',
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type KiroAuthMethod = 'idc' | 'desktop' | '
|
|
1
|
+
export type KiroAuthMethod = 'idc' | 'desktop' | 'aws-sso';
|
|
2
2
|
export type KiroRegion = 'us-east-1' | 'us-west-2';
|
|
3
3
|
export interface KiroAuthDetails {
|
|
4
4
|
refresh: string;
|
|
@@ -10,7 +10,6 @@ export interface KiroAuthDetails {
|
|
|
10
10
|
clientSecret?: string;
|
|
11
11
|
email?: string;
|
|
12
12
|
profileArn?: string;
|
|
13
|
-
awsProfile?: string;
|
|
14
13
|
}
|
|
15
14
|
export interface RefreshParts {
|
|
16
15
|
refreshToken: string;
|
|
@@ -27,7 +26,6 @@ export interface ManagedAccount {
|
|
|
27
26
|
clientId?: string;
|
|
28
27
|
clientSecret?: string;
|
|
29
28
|
profileArn?: string;
|
|
30
|
-
awsProfile?: string;
|
|
31
29
|
refreshToken: string;
|
|
32
30
|
accessToken: string;
|
|
33
31
|
expiresAt: number;
|
package/dist/plugin.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare const createKiroPlugin: (id: string) => ({ client, directory }: a
|
|
|
9
9
|
methods: {
|
|
10
10
|
id: string;
|
|
11
11
|
label: string;
|
|
12
|
-
type: "oauth"
|
|
12
|
+
type: "oauth";
|
|
13
13
|
authorize: (inputs?: any) => Promise<any>;
|
|
14
14
|
}[];
|
|
15
15
|
};
|
|
@@ -25,7 +25,7 @@ export declare const KiroOAuthPlugin: ({ client, directory }: any) => Promise<{
|
|
|
25
25
|
methods: {
|
|
26
26
|
id: string;
|
|
27
27
|
label: string;
|
|
28
|
-
type: "oauth"
|
|
28
|
+
type: "oauth";
|
|
29
29
|
authorize: (inputs?: any) => Promise<any>;
|
|
30
30
|
}[];
|
|
31
31
|
};
|
package/dist/plugin.js
CHANGED
|
@@ -15,28 +15,17 @@ export const createKiroPlugin = (id) => async ({ client, directory }) => {
|
|
|
15
15
|
const authHandler = new AuthHandler(config, repository);
|
|
16
16
|
const accountManager = await AccountManager.loadFromDisk(config.account_selection_strategy);
|
|
17
17
|
authHandler.setAccountManager(accountManager);
|
|
18
|
+
// Sync AWS SSO before OpenCode checks for accounts
|
|
19
|
+
await authHandler.initialize();
|
|
18
20
|
const requestHandler = new RequestHandler(accountManager, config, repository);
|
|
19
21
|
return {
|
|
20
22
|
auth: {
|
|
21
23
|
provider: id,
|
|
22
24
|
loader: async (getAuth) => {
|
|
23
|
-
|
|
24
|
-
const accounts = accountManager.getAccounts();
|
|
25
|
-
if (accounts.length === 0) {
|
|
26
|
-
// No accounts, need to authenticate
|
|
27
|
-
await getAuth();
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// We have accounts, skip auth prompt
|
|
31
|
-
console.log('[KIRO] Using existing account:', accounts[0]?.email || 'unknown');
|
|
32
|
-
}
|
|
33
|
-
await authHandler.initialize();
|
|
34
|
-
const region = config?.default_region || 'us-east-1';
|
|
35
|
-
const baseURL = `https://q.${region}.amazonaws.com`;
|
|
36
|
-
console.log('[KIRO] Loader called - baseURL:', baseURL, 'region:', region);
|
|
25
|
+
await getAuth();
|
|
37
26
|
return {
|
|
38
27
|
apiKey: '',
|
|
39
|
-
baseURL,
|
|
28
|
+
baseURL: 'https://q.us-east-1.amazonaws.com',
|
|
40
29
|
fetch: (input, init) => requestHandler.handle(input, init, showToast)
|
|
41
30
|
};
|
|
42
31
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kervnet/opencode-kiro-auth",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "OpenCode plugin for AWS Kiro (CodeWhisperer)
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "OpenCode plugin for AWS Kiro (CodeWhisperer) with IAM Identity Center profile support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -21,22 +21,17 @@
|
|
|
21
21
|
"ai",
|
|
22
22
|
"auth"
|
|
23
23
|
],
|
|
24
|
-
"author": "
|
|
24
|
+
"author": "tickernelz",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/
|
|
28
|
+
"url": "git+https://github.com/tickernelz/opencode-kiro-auth.git"
|
|
29
29
|
},
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@aws-crypto/sha256-js": "^5.2.0",
|
|
35
|
-
"@aws-sdk/credential-providers": "^3.978.0",
|
|
36
|
-
"@aws-sdk/signature-v4": "^3.370.0",
|
|
37
34
|
"@opencode-ai/plugin": "^0.15.30",
|
|
38
|
-
"@smithy/protocol-http": "^5.3.8",
|
|
39
|
-
"@smithy/signature-v4": "^5.3.8",
|
|
40
35
|
"proper-lockfile": "^4.1.2",
|
|
41
36
|
"zod": "^3.24.0"
|
|
42
37
|
},
|
package/dist/kiro/iam.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { KiroAuthDetails } from '../plugin/types';
|
|
2
|
-
export declare function getIAMCredentials(profileName: string, region: string): Promise<import("@smithy/types").AwsCredentialIdentity>;
|
|
3
|
-
export declare function signRequestWithIAM(url: string, method: string, headers: Record<string, string>, body: string, profileName: string, region: string): Promise<Record<string, string>>;
|
|
4
|
-
export declare function isIAMAuth(auth: KiroAuthDetails): boolean;
|
package/dist/kiro/iam.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { fromIni } from '@aws-sdk/credential-providers';
|
|
2
|
-
import { SignatureV4 } from '@smithy/signature-v4';
|
|
3
|
-
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
4
|
-
import { HttpRequest } from '@smithy/protocol-http';
|
|
5
|
-
export async function getIAMCredentials(profileName, region) {
|
|
6
|
-
const credentials = fromIni({ profile: profileName });
|
|
7
|
-
return await credentials();
|
|
8
|
-
}
|
|
9
|
-
export async function signRequestWithIAM(url, method, headers, body, profileName, region) {
|
|
10
|
-
const credentials = await getIAMCredentials(profileName, region);
|
|
11
|
-
const parsedUrl = new URL(url);
|
|
12
|
-
const request = new HttpRequest({
|
|
13
|
-
method,
|
|
14
|
-
protocol: parsedUrl.protocol,
|
|
15
|
-
hostname: parsedUrl.hostname,
|
|
16
|
-
path: parsedUrl.pathname + parsedUrl.search,
|
|
17
|
-
headers: {
|
|
18
|
-
...headers,
|
|
19
|
-
host: parsedUrl.hostname,
|
|
20
|
-
},
|
|
21
|
-
body,
|
|
22
|
-
});
|
|
23
|
-
const signer = new SignatureV4({
|
|
24
|
-
credentials,
|
|
25
|
-
region,
|
|
26
|
-
service: 'codewhisperer',
|
|
27
|
-
sha256: Sha256,
|
|
28
|
-
});
|
|
29
|
-
const signedRequest = await signer.sign(request);
|
|
30
|
-
return signedRequest.headers;
|
|
31
|
-
}
|
|
32
|
-
export function isIAMAuth(auth) {
|
|
33
|
-
return auth.authMethod === 'iam';
|
|
34
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function syncIAMFromKiroCli(): Promise<void>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { createDeterministicAccountId } from '../accounts';
|
|
3
|
-
import * as logger from '../logger';
|
|
4
|
-
import { kiroDb } from '../storage/sqlite';
|
|
5
|
-
export async function syncIAMFromKiroCli() {
|
|
6
|
-
try {
|
|
7
|
-
// Check if AWS credentials are configured
|
|
8
|
-
const { existsSync } = await import('node:fs');
|
|
9
|
-
const { homedir } = await import('node:os');
|
|
10
|
-
const { join } = await import('node:path');
|
|
11
|
-
const awsConfigPath = join(homedir(), '.aws', 'config');
|
|
12
|
-
const awsCredsPath = join(homedir(), '.aws', 'credentials');
|
|
13
|
-
if (!existsSync(awsConfigPath) && !existsSync(awsCredsPath)) {
|
|
14
|
-
logger.debug('IAM sync: No AWS credentials configured, skipping IAM auth');
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const output = execSync('kiro-cli whoami', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
18
|
-
const lines = output.split('\n').map(l => l.trim()).filter(Boolean);
|
|
19
|
-
let profileName;
|
|
20
|
-
let profileArn;
|
|
21
|
-
let region = 'us-east-1';
|
|
22
|
-
for (const line of lines) {
|
|
23
|
-
if (line.startsWith('arn:aws:codewhisperer:')) {
|
|
24
|
-
profileArn = line;
|
|
25
|
-
const match = line.match(/:([a-z]+-[a-z]+-\d+):/);
|
|
26
|
-
if (match && match[1])
|
|
27
|
-
region = match[1];
|
|
28
|
-
}
|
|
29
|
-
else if (!line.includes('Logged in') && !line.includes('Profile:') && !line.startsWith('arn:') && !line.startsWith('http')) {
|
|
30
|
-
profileName = line;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (!profileArn || !profileName) {
|
|
34
|
-
logger.debug('IAM sync: No IAM profile detected from kiro-cli whoami');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const email = `iam-${profileName}@aws.amazon.com`;
|
|
38
|
-
const id = createDeterministicAccountId(email, 'iam', undefined, profileArn);
|
|
39
|
-
const existing = kiroDb.getAccounts().find((a) => a.id === id);
|
|
40
|
-
if (existing && existing.is_healthy === 1) {
|
|
41
|
-
logger.debug('IAM sync: Profile already synced and healthy');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
await kiroDb.upsertAccount({
|
|
45
|
-
id,
|
|
46
|
-
email,
|
|
47
|
-
authMethod: 'iam',
|
|
48
|
-
region: region,
|
|
49
|
-
profileArn,
|
|
50
|
-
awsProfile: profileName,
|
|
51
|
-
refreshToken: profileArn,
|
|
52
|
-
accessToken: 'iam-signed',
|
|
53
|
-
expiresAt: Date.now() + 3600000,
|
|
54
|
-
rateLimitResetTime: 0,
|
|
55
|
-
isHealthy: true,
|
|
56
|
-
failCount: 0,
|
|
57
|
-
usedCount: 0,
|
|
58
|
-
limitCount: 0,
|
|
59
|
-
lastSync: Date.now()
|
|
60
|
-
});
|
|
61
|
-
logger.log('IAM sync: Successfully synced IAM profile', { profileName, profileArn, region });
|
|
62
|
-
}
|
|
63
|
-
catch (e) {
|
|
64
|
-
logger.debug('IAM sync: Failed to sync from kiro-cli', e);
|
|
65
|
-
}
|
|
66
|
-
}
|