@magpie-community/opencode-kiro-auth 0.1.12 → 0.1.14

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.
Files changed (3) hide show
  1. package/README.md +15 -2
  2. package/index.mjs +44 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -17,8 +17,21 @@ Power) and makes its requests, in OpenCode and in magpie. Provider id:
17
17
  - Builder ID and Identity Center come back with an AWS sign-in URL and
18
18
  region. The plugin registers a client with AWS's OIDC service, sends
19
19
  the browser to AWS, and trades the code AWS returns.
20
- - The account is named by the email Kiro has for it (`Get-Usage-Limits`),
21
- and its plan is read from the same call.
20
+ - The account is named by the email Kiro has for it (`Get-Usage-Limits`)
21
+ and the way it was signed in to: `email (Google)`, `email (GitHub)`,
22
+ `email (Builder ID)`, `email (IAM Identity Center)`, `email
23
+ (Internal)`. Its plan is read from the same call, and its usage names
24
+ it the same way.
25
+ - Kiro keeps a Google, a GitHub, a Builder ID and an Identity Center
26
+ sign-in of one email as different accounts. The sign-in carries a `uid`
27
+ (`google`, `github`, `builderid`, `enterprise`), so magpie keeps each one
28
+ beside the others, and a sign-in again the same way replaces only its
29
+ own account. An account signed in before 0.1.13 is given its `uid`,
30
+ and one named by its bare email before 0.1.14 its name, on its first
31
+ request, model listing, usage read or renewal. Until then, a sign-in
32
+ again the same way goes beside it rather than replacing it: magpie
33
+ matches a sign-in by its name, and the plugin can't see the other
34
+ accounts to match it by `uid`.
22
35
  - A company's own identity provider (`external_idp`) is not supported
23
36
  here. Sign in with `kiro-cli login` and use the next method instead.
24
37
  - **Kiro CLI's or Kiro IDE's sign-in**: uses the account kiro-cli is signed
package/index.mjs CHANGED
@@ -50,6 +50,21 @@ const SIGN_IN_TIMEOUT = 10 * 60 * 1000
50
50
  const BUILDER_ID_START = "https://view.awsapps.com/start"
51
51
  const BUILDER_ID_PROFILE = "arn:aws:codewhisperer:us-east-1:638616132270:profile/AAAACCCCXXXX"
52
52
  const isBuilderID = (provider) => String(provider ?? "").toLowerCase() === "builderid"
53
+ // uidOf is what tells two Kiro accounts of one email apart: the way it was
54
+ // signed in to. Kiro keeps a Google and a GitHub sign-in of one email (or
55
+ // Builder ID's, or Identity Center's) as different accounts, and magpie
56
+ // keeps a sign-in of an email already signed in as that account unless
57
+ // both have a uid and the two differ (#309). "" when it isn't known.
58
+ const uidOf = (loginProvider) => String(loginProvider ?? "").trim().toLowerCase()
59
+ // nameOf is what an account is called: its email and the way it was signed
60
+ // in to, as Kiro's sign-in page names it, so two accounts of one email read
61
+ // apart (#309; magpie names a plugin's account by its accountId). A way not
62
+ // known, kiro-cli's or the Kiro IDE's sign-in among them, leaves the email
63
+ // as it is, and magpie tells two such apart by their slot.
64
+ const WAYS = { google: "Google", github: "GitHub", builderid: "Builder ID", enterprise: "IAM Identity Center", internal: "Internal" }
65
+ const nameOf = (email, loginProvider) => (WAYS[uidOf(loginProvider)] ? `${email} (${WAYS[uidOf(loginProvider)]})` : email)
66
+ // a bare email: what an account was called before nameOf
67
+ const bareEmail = (id) => typeof id === "string" && /^[^\s()]+@[^\s()]+$/.test(id)
53
68
 
54
69
  const env = (k) => process.env[k] ?? ""
55
70
  const home = () => env("HOME") || homedir()
@@ -438,11 +453,21 @@ function account(client) {
438
453
  lock = run.catch(() => {})
439
454
  return run
440
455
  }
456
+ // an account signed in here before its uid was kept is given it, so a
457
+ // sign-in with another way to the same email no longer replaces it
458
+ const uidSaved = new Set()
459
+ const uidField = (auth) => (!auth?.uid && uidOf(auth?.loginProvider) ? { uid: uidOf(auth.loginProvider) } : {})
460
+ // and one called by its bare email is given its name, the way it was
461
+ // signed in to with it
462
+ const nameField = (auth) =>
463
+ bareEmail(auth?.accountId) && nameOf(auth.accountId, auth.loginProvider) !== auth.accountId
464
+ ? { accountId: nameOf(auth.accountId, auth.loginProvider) } : {}
465
+ const idFields = (auth) => ({ ...uidField(auth), ...nameField(auth) })
441
466
  const save = async (auth, c) => {
442
467
  if (!c.own || !client?.auth?.set) return
443
468
  const { type: _t, ...rest } = auth
444
469
  await client.auth.set({ path: { id: ID }, body: { ...rest, type: "oauth", access: c.access, refresh: c.refresh, expires: c.expires,
445
- profileArn: c.profile, region: c.region } }).catch(() => {})
470
+ profileArn: c.profile, region: c.region, ...idFields(auth) } }).catch(() => {})
446
471
  }
447
472
  const creds = (auth, stale = false) =>
448
473
  locked(async () => {
@@ -460,6 +485,7 @@ function account(client) {
460
485
  }
461
486
  read = newest(read)
462
487
  let c = held?.id === id ? held.c : null
488
+ let saved = false
463
489
  if (!c || read.access !== c.access || !fresh(c) || stale) {
464
490
  if (!read.profile && c && read.access === c.access) read.profile = c.profile
465
491
  if (stale && c && read.access !== c.access && fresh(read)) stale = false // the owner refreshed it already
@@ -468,11 +494,20 @@ function account(client) {
468
494
  if (c.method === "apikey") throw gone("Kiro turned down the API key saved on the provider")
469
495
  c = renewed(c, await refresh(c))
470
496
  await save(auth, c)
497
+ saved = true
471
498
  }
472
499
  }
473
500
  if (!c.profile) {
474
501
  c.profile = await profileOf(c)
475
- if (!auth?.profileArn) await save(auth, c)
502
+ if (!auth?.profileArn && !saved) {
503
+ await save(auth, c)
504
+ saved = true
505
+ }
506
+ }
507
+ // once per token: a save that didn't take isn't tried on every request
508
+ if (!saved && c.own && Object.keys(idFields(auth)).length && !uidSaved.has(c.access)) {
509
+ uidSaved.add(c.access)
510
+ await save(auth, c)
476
511
  }
477
512
  held = { id, c }
478
513
  return { token: c.access, tokenType: tokenType(c), profile: c.profile, region: regionOf(c.profile, c.region) }
@@ -493,7 +528,7 @@ function account(client) {
493
528
  const out = { access: c.access, expires: c.expires }
494
529
  if (c.refresh !== auth.refresh) out.refresh = c.refresh
495
530
  if (c.profile && c.profile !== auth.profileArn) out.profileArn = c.profile
496
- return out
531
+ return { ...out, ...idFields(auth) }
497
532
  })
498
533
  return creds
499
534
  }
@@ -1403,8 +1438,9 @@ async function browserSignIn() {
1403
1438
  return fail(e.message)
1404
1439
  }
1405
1440
  const who = await whoIs(c)
1406
- const user = who.email || "Kiro account"
1441
+ const user = who.email ? nameOf(who.email, c.loginProvider) : "Kiro account"
1407
1442
  finish({ type: "success", provider: ID, refresh: c.refresh, access: c.access, expires: c.expires, accountId: user,
1443
+ ...(uidOf(c.loginProvider) ? { uid: uidOf(c.loginProvider) } : {}),
1408
1444
  method: c.method, loginProvider: c.loginProvider, region: c.region, profileArn: who.profile,
1409
1445
  ...(c.clientId ? { clientId: c.clientId, clientSecret: c.clientSecret } : {}), ...(who.plan ? { plan: who.plan } : {}) })
1410
1446
  html(200, page(true, "You're signed in", `${user} is signed in. You can close this tab.`))
@@ -1494,7 +1530,10 @@ export async function KiroAuthPlugin({ client } = {}) {
1494
1530
  const auth = await getAuth()
1495
1531
  if (!(auth?.type === "api" && auth.key) && auth?.type !== "oauth") return { error: "not signed in", signIn: "kept" }
1496
1532
  try {
1497
- return { ...usageOf(await usageLimits(await creds(auth), true)), signIn: "kept" }
1533
+ const u = usageOf(await usageLimits(await creds(auth), true))
1534
+ // named as the account is
1535
+ if (u.user && auth.type === "oauth" && !auth.source) u.user = nameOf(u.user, auth.loginProvider)
1536
+ return { ...u, signIn: "kept" }
1498
1537
  } catch (e) {
1499
1538
  return { error: e.message, signIn: "kept" }
1500
1539
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-kiro-auth",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Kiro subscriptions (Free, Pro, Pro+, Power) in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",