@zhafron/opencode-kiro-auth 1.2.7 → 1.3.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 +31 -45
- package/dist/index.d.ts +1 -1
- package/dist/kiro/auth.js +15 -6
- package/dist/plugin/accounts.d.ts +2 -1
- package/dist/plugin/accounts.js +43 -20
- package/dist/plugin/cli.js +1 -1
- package/dist/plugin/config/index.d.ts +2 -2
- package/dist/plugin/config/index.js +2 -2
- package/dist/plugin/config/loader.js +3 -3
- package/dist/plugin/config/schema.d.ts +3 -0
- package/dist/plugin/config/schema.js +2 -0
- package/dist/plugin/request.d.ts +1 -1
- package/dist/plugin/request.js +77 -16
- package/dist/plugin/response.d.ts +1 -1
- package/dist/plugin/server.js +1 -1
- package/dist/plugin/storage/migration.d.ts +1 -0
- package/dist/plugin/storage/migration.js +53 -0
- package/dist/plugin/storage/sqlite.d.ts +16 -0
- package/dist/plugin/storage/sqlite.js +104 -0
- package/dist/plugin/sync/kiro-cli.d.ts +2 -0
- package/dist/plugin/sync/kiro-cli.js +96 -0
- package/dist/plugin/token.js +29 -17
- package/dist/plugin/types.d.ts +1 -27
- package/dist/plugin/usage.d.ts +1 -1
- package/dist/plugin.js +173 -249
- package/package.json +3 -2
- package/dist/plugin/storage.d.ts +0 -7
- package/dist/plugin/storage.js +0 -124
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function getBaseDir() {
|
|
6
|
+
const p = process.platform;
|
|
7
|
+
if (p === 'win32')
|
|
8
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'opencode');
|
|
9
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
|
|
10
|
+
}
|
|
11
|
+
export const DB_PATH = join(getBaseDir(), 'kiro.db');
|
|
12
|
+
export class KiroDatabase {
|
|
13
|
+
db;
|
|
14
|
+
constructor(path = DB_PATH) {
|
|
15
|
+
const dir = join(path, '..');
|
|
16
|
+
if (!existsSync(dir))
|
|
17
|
+
mkdirSync(dir, { recursive: true });
|
|
18
|
+
this.db = new Database(path);
|
|
19
|
+
this.db.run('PRAGMA busy_timeout = 5000');
|
|
20
|
+
this.init();
|
|
21
|
+
}
|
|
22
|
+
init() {
|
|
23
|
+
this.db.run('PRAGMA journal_mode = WAL');
|
|
24
|
+
this.db.run(`
|
|
25
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
26
|
+
id TEXT PRIMARY KEY, email TEXT NOT NULL, real_email TEXT, auth_method TEXT NOT NULL,
|
|
27
|
+
region TEXT NOT NULL, client_id TEXT, client_secret TEXT, profile_arn TEXT,
|
|
28
|
+
refresh_token TEXT NOT NULL, access_token TEXT NOT NULL, expires_at INTEGER NOT NULL,
|
|
29
|
+
rate_limit_reset INTEGER DEFAULT 0, is_healthy INTEGER DEFAULT 1, unhealthy_reason TEXT,
|
|
30
|
+
recovery_time INTEGER, last_used INTEGER DEFAULT 0
|
|
31
|
+
)
|
|
32
|
+
`);
|
|
33
|
+
this.db.run(`
|
|
34
|
+
CREATE TABLE IF NOT EXISTS usage (
|
|
35
|
+
account_id TEXT PRIMARY KEY, used_count INTEGER DEFAULT 0, limit_count INTEGER DEFAULT 0,
|
|
36
|
+
real_email TEXT, last_sync INTEGER,
|
|
37
|
+
FOREIGN KEY(account_id) REFERENCES accounts(id) ON DELETE CASCADE
|
|
38
|
+
)
|
|
39
|
+
`);
|
|
40
|
+
this.db.run('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT)');
|
|
41
|
+
}
|
|
42
|
+
getAccounts() {
|
|
43
|
+
return this.db.prepare('SELECT * FROM accounts').all();
|
|
44
|
+
}
|
|
45
|
+
upsertAccount(acc) {
|
|
46
|
+
this.db
|
|
47
|
+
.prepare(`
|
|
48
|
+
INSERT INTO accounts (
|
|
49
|
+
id, email, real_email, auth_method, region, client_id, client_secret,
|
|
50
|
+
profile_arn, refresh_token, access_token, expires_at, rate_limit_reset,
|
|
51
|
+
is_healthy, unhealthy_reason, recovery_time, last_used
|
|
52
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
53
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
54
|
+
email=excluded.email, real_email=excluded.real_email, auth_method=excluded.auth_method,
|
|
55
|
+
region=excluded.region, client_id=excluded.client_id, client_secret=excluded.client_secret,
|
|
56
|
+
profile_arn=excluded.profile_arn, refresh_token=excluded.refresh_token,
|
|
57
|
+
access_token=excluded.access_token, expires_at=excluded.expires_at,
|
|
58
|
+
rate_limit_reset=excluded.rate_limit_reset, is_healthy=excluded.is_healthy,
|
|
59
|
+
unhealthy_reason=excluded.unhealthy_reason, recovery_time=excluded.recovery_time,
|
|
60
|
+
last_used=excluded.last_used
|
|
61
|
+
`)
|
|
62
|
+
.run(acc.id, acc.email, acc.realEmail || null, 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.lastUsed || 0);
|
|
63
|
+
}
|
|
64
|
+
deleteAccount(id) {
|
|
65
|
+
this.db.prepare('DELETE FROM accounts WHERE id = ?').run(id);
|
|
66
|
+
}
|
|
67
|
+
getUsage() {
|
|
68
|
+
const rows = this.db.prepare('SELECT * FROM usage').all();
|
|
69
|
+
const usage = {};
|
|
70
|
+
for (const r of rows) {
|
|
71
|
+
usage[r.account_id] = {
|
|
72
|
+
usedCount: r.used_count,
|
|
73
|
+
limitCount: r.limit_count,
|
|
74
|
+
realEmail: r.real_email,
|
|
75
|
+
lastSync: r.last_sync
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return usage;
|
|
79
|
+
}
|
|
80
|
+
upsertUsage(id, meta) {
|
|
81
|
+
this.db
|
|
82
|
+
.prepare(`
|
|
83
|
+
INSERT INTO usage (account_id, used_count, limit_count, real_email, last_sync)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?)
|
|
85
|
+
ON CONFLICT(account_id) DO UPDATE SET
|
|
86
|
+
used_count=excluded.used_count, limit_count=excluded.limit_count,
|
|
87
|
+
real_email=excluded.real_email, last_sync=excluded.last_sync
|
|
88
|
+
`)
|
|
89
|
+
.run(id, meta.usedCount, meta.limitCount, meta.realEmail || null, meta.lastSync);
|
|
90
|
+
}
|
|
91
|
+
getSetting(key) {
|
|
92
|
+
const row = this.db.prepare('SELECT value FROM settings WHERE key = ?').get(key);
|
|
93
|
+
return row ? row.value : null;
|
|
94
|
+
}
|
|
95
|
+
setSetting(key, value) {
|
|
96
|
+
this.db
|
|
97
|
+
.prepare('INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value')
|
|
98
|
+
.run(key, value);
|
|
99
|
+
}
|
|
100
|
+
close() {
|
|
101
|
+
this.db.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export const kiroDb = new KiroDatabase();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { homedir, platform } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { createDeterministicAccountId } from '../accounts';
|
|
6
|
+
import * as logger from '../logger';
|
|
7
|
+
import { kiroDb } from '../storage/sqlite';
|
|
8
|
+
function getCliDbPath() {
|
|
9
|
+
const p = platform();
|
|
10
|
+
if (p === 'win32')
|
|
11
|
+
return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'kiro-cli', 'data.sqlite3');
|
|
12
|
+
if (p === 'darwin')
|
|
13
|
+
return join(homedir(), 'Library', 'Application Support', 'kiro-cli', 'data.sqlite3');
|
|
14
|
+
return join(homedir(), '.local', 'share', 'kiro-cli', 'data.sqlite3');
|
|
15
|
+
}
|
|
16
|
+
export async function syncFromKiroCli() {
|
|
17
|
+
const dbPath = getCliDbPath();
|
|
18
|
+
if (!existsSync(dbPath))
|
|
19
|
+
return;
|
|
20
|
+
try {
|
|
21
|
+
const cliDb = new Database(dbPath, { readonly: true });
|
|
22
|
+
cliDb.run('PRAGMA busy_timeout = 5000');
|
|
23
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
if (row.key.includes(':token')) {
|
|
26
|
+
let data;
|
|
27
|
+
try {
|
|
28
|
+
data = JSON.parse(row.value);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (!data.access_token)
|
|
34
|
+
continue;
|
|
35
|
+
const email = data.email || 'cli-account@kiro.dev';
|
|
36
|
+
const authMethod = row.key.includes('odic') ? 'idc' : 'desktop';
|
|
37
|
+
const clientId = data.client_id ||
|
|
38
|
+
(authMethod === 'idc'
|
|
39
|
+
? JSON.parse(rows.find((r) => r.key.includes('device-registration'))?.value || '{}')
|
|
40
|
+
.client_id
|
|
41
|
+
: undefined);
|
|
42
|
+
const clientSecret = data.client_secret ||
|
|
43
|
+
(authMethod === 'idc'
|
|
44
|
+
? JSON.parse(rows.find((r) => r.key.includes('device-registration'))?.value || '{}')
|
|
45
|
+
.client_secret
|
|
46
|
+
: undefined);
|
|
47
|
+
const id = createDeterministicAccountId(email, authMethod, clientId, data.profile_arn);
|
|
48
|
+
const existing = kiroDb.getAccounts().find((a) => a.id === id);
|
|
49
|
+
const cliExpiresAt = data.expires_at ? new Date(data.expires_at).getTime() : 0;
|
|
50
|
+
if (existing && existing.expires_at >= cliExpiresAt)
|
|
51
|
+
continue;
|
|
52
|
+
kiroDb.upsertAccount({
|
|
53
|
+
id,
|
|
54
|
+
email,
|
|
55
|
+
realEmail: data.real_email || email,
|
|
56
|
+
authMethod,
|
|
57
|
+
region: data.region || 'us-east-1',
|
|
58
|
+
clientId,
|
|
59
|
+
clientSecret,
|
|
60
|
+
profileArn: data.profile_arn,
|
|
61
|
+
refreshToken: data.refresh_token,
|
|
62
|
+
accessToken: data.access_token,
|
|
63
|
+
expiresAt: cliExpiresAt || Date.now() + 3600000,
|
|
64
|
+
isHealthy: 1
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
cliDb.close();
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
logger.error('Sync failed', e);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function writeToKiroCli(acc) {
|
|
75
|
+
const dbPath = getCliDbPath();
|
|
76
|
+
if (!existsSync(dbPath))
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
const cliDb = new Database(dbPath);
|
|
80
|
+
cliDb.exec('PRAGMA busy_timeout = 5000');
|
|
81
|
+
const rows = cliDb.prepare('SELECT key, value FROM auth_kv').all();
|
|
82
|
+
const targetKey = acc.authMethod === 'idc' ? 'kirocli:odic:token' : 'kirocli:social:token';
|
|
83
|
+
const row = rows.find((r) => r.key === targetKey || r.key.endsWith(targetKey));
|
|
84
|
+
if (row) {
|
|
85
|
+
const data = JSON.parse(row.value);
|
|
86
|
+
data.access_token = acc.accessToken;
|
|
87
|
+
data.refresh_token = acc.refreshToken;
|
|
88
|
+
data.expires_at = new Date(acc.expiresAt).toISOString();
|
|
89
|
+
cliDb.prepare('UPDATE auth_kv SET value = ? WHERE key = ?').run(JSON.stringify(data), row.key);
|
|
90
|
+
}
|
|
91
|
+
cliDb.close();
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
logger.warn('Write back failed', e);
|
|
95
|
+
}
|
|
96
|
+
}
|
package/dist/plugin/token.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
2
|
import { decodeRefreshToken, encodeRefreshToken } from '../kiro/auth';
|
|
3
|
+
import { KiroTokenRefreshError } from './errors';
|
|
3
4
|
export async function refreshAccessToken(auth) {
|
|
4
|
-
const url = `https://oidc.${auth.region}.amazonaws.com/token`;
|
|
5
5
|
const p = decodeRefreshToken(auth.refresh);
|
|
6
|
-
|
|
6
|
+
const isIdc = auth.authMethod === 'idc';
|
|
7
|
+
const url = isIdc
|
|
8
|
+
? `https://oidc.${auth.region}.amazonaws.com/token`
|
|
9
|
+
: `https://prod.${auth.region}.auth.desktop.kiro.dev/refreshToken`;
|
|
10
|
+
if (isIdc && (!p.clientId || !p.clientSecret)) {
|
|
7
11
|
throw new KiroTokenRefreshError('Missing creds', 'MISSING_CREDENTIALS');
|
|
8
12
|
}
|
|
9
|
-
const requestBody =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const requestBody = isIdc
|
|
14
|
+
? {
|
|
15
|
+
refreshToken: p.refreshToken,
|
|
16
|
+
clientId: p.clientId,
|
|
17
|
+
clientSecret: p.clientSecret,
|
|
18
|
+
grantType: 'refresh_token'
|
|
19
|
+
}
|
|
20
|
+
: {
|
|
21
|
+
refreshToken: p.refreshToken
|
|
22
|
+
};
|
|
23
|
+
const machineId = crypto
|
|
24
|
+
.createHash('sha256')
|
|
25
|
+
.update(auth.profileArn || auth.clientId || 'KIRO_DEFAULT_MACHINE')
|
|
26
|
+
.digest('hex');
|
|
27
|
+
const ua = isIdc ? 'aws-sdk-js/1.0.0' : `KiroIDE-0.7.45-${machineId}`;
|
|
15
28
|
try {
|
|
16
29
|
const res = await fetch(url, {
|
|
17
30
|
method: 'POST',
|
|
@@ -20,6 +33,7 @@ export async function refreshAccessToken(auth) {
|
|
|
20
33
|
Accept: 'application/json',
|
|
21
34
|
'amz-sdk-request': 'attempt=1; max=1',
|
|
22
35
|
'x-amzn-kiro-agent-mode': 'vibe',
|
|
36
|
+
'user-agent': ua,
|
|
23
37
|
Connection: 'close'
|
|
24
38
|
},
|
|
25
39
|
body: JSON.stringify(requestBody)
|
|
@@ -37,30 +51,28 @@ export async function refreshAccessToken(auth) {
|
|
|
37
51
|
}
|
|
38
52
|
const d = await res.json();
|
|
39
53
|
const acc = d.access_token || d.accessToken;
|
|
40
|
-
if (!acc)
|
|
54
|
+
if (!acc)
|
|
41
55
|
throw new KiroTokenRefreshError('No access token', 'INVALID_RESPONSE');
|
|
42
|
-
}
|
|
43
56
|
const upP = {
|
|
44
57
|
refreshToken: d.refresh_token || d.refreshToken || p.refreshToken,
|
|
45
58
|
clientId: p.clientId,
|
|
46
59
|
clientSecret: p.clientSecret,
|
|
47
|
-
authMethod:
|
|
60
|
+
authMethod: auth.authMethod
|
|
48
61
|
};
|
|
49
62
|
return {
|
|
50
63
|
refresh: encodeRefreshToken(upP),
|
|
51
64
|
access: acc,
|
|
52
|
-
expires: Date.now() + (d.expires_in || 3600) * 1000,
|
|
53
|
-
authMethod:
|
|
65
|
+
expires: Date.now() + (d.expires_in || d.expiresIn || 3600) * 1000,
|
|
66
|
+
authMethod: auth.authMethod,
|
|
54
67
|
region: auth.region,
|
|
55
68
|
clientId: auth.clientId,
|
|
56
69
|
clientSecret: auth.clientSecret,
|
|
57
|
-
email: auth.email
|
|
70
|
+
email: auth.email || d.userInfo?.email
|
|
58
71
|
};
|
|
59
72
|
}
|
|
60
73
|
catch (error) {
|
|
61
|
-
if (error instanceof KiroTokenRefreshError)
|
|
74
|
+
if (error instanceof KiroTokenRefreshError)
|
|
62
75
|
throw error;
|
|
63
|
-
}
|
|
64
76
|
throw new KiroTokenRefreshError(`Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'NETWORK_ERROR', error instanceof Error ? error : undefined);
|
|
65
77
|
}
|
|
66
78
|
}
|
package/dist/plugin/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type KiroAuthMethod = 'idc';
|
|
1
|
+
export type KiroAuthMethod = 'idc' | 'desktop';
|
|
2
2
|
export type KiroRegion = 'us-east-1' | 'us-west-2';
|
|
3
3
|
export interface KiroAuthDetails {
|
|
4
4
|
refresh: string;
|
|
@@ -38,38 +38,12 @@ export interface ManagedAccount {
|
|
|
38
38
|
limitCount?: number;
|
|
39
39
|
lastUsed?: number;
|
|
40
40
|
}
|
|
41
|
-
export interface AccountMetadata {
|
|
42
|
-
id: string;
|
|
43
|
-
email: string;
|
|
44
|
-
realEmail?: string;
|
|
45
|
-
authMethod: KiroAuthMethod;
|
|
46
|
-
region: KiroRegion;
|
|
47
|
-
clientId?: string;
|
|
48
|
-
clientSecret?: string;
|
|
49
|
-
profileArn?: string;
|
|
50
|
-
refreshToken: string;
|
|
51
|
-
accessToken: string;
|
|
52
|
-
expiresAt: number;
|
|
53
|
-
rateLimitResetTime: number;
|
|
54
|
-
isHealthy: boolean;
|
|
55
|
-
unhealthyReason?: string;
|
|
56
|
-
recoveryTime?: number;
|
|
57
|
-
}
|
|
58
|
-
export interface AccountStorage {
|
|
59
|
-
version: 1;
|
|
60
|
-
accounts: AccountMetadata[];
|
|
61
|
-
activeIndex: number;
|
|
62
|
-
}
|
|
63
41
|
export interface UsageMetadata {
|
|
64
42
|
usedCount: number;
|
|
65
43
|
limitCount: number;
|
|
66
44
|
realEmail?: string;
|
|
67
45
|
lastSync: number;
|
|
68
46
|
}
|
|
69
|
-
export interface UsageStorage {
|
|
70
|
-
version: 1;
|
|
71
|
-
usage: Record<string, UsageMetadata>;
|
|
72
|
-
}
|
|
73
47
|
export interface CodeWhispererMessage {
|
|
74
48
|
userInputMessage?: {
|
|
75
49
|
content: string;
|
package/dist/plugin/usage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KiroAuthDetails, ManagedAccount } from './types';
|
|
2
2
|
export declare function fetchUsageLimits(auth: KiroAuthDetails): Promise<any>;
|
|
3
3
|
export declare function updateAccountQuota(account: ManagedAccount, usage: any, accountManager?: any): void;
|