providers: Kiro is a subscription added in magpie, signed in on Kiro's own page (Image #21: 为什么在订阅列表里看不到Kiro订阅) — Kiro was used through kiro-cli's or the Kiro IDE's sign-in, or a key, so it had no card among the subscriptions and no way to sign in from magpie. It now has a card, and signing in opens app.kiro.dev as the IDE does: Google and GitHub are exchanged with Kiro's auth service, Builder ID and Identity Center go on to AWS with a client registered for magpie, and the sign-in is kept in magpie's own kiro-auth-token.json, refreshed there, and used ahead of kiro-cli's and the IDE's, which are left as they were. A company's own identity provider is still kiro-cli's (kiro-cli login), and the sign-in says so. Tested with a fake Kiro and AWS (Google, Builder ID, a stale answer, an external IdP) and against the live page, token and register endpoints; not yet with a real Kiro account

This commit is contained in:
yetone committed 2026-09-29 13:01:09 +08:00
1 parent 1d6b415a68
commit 21cc3d7647
6 files changed
+498 -4

No files matched your search

+2
View File
@@ -3643,6 +3643,8 @@ const SUBS = [
{ agent: "commandcode-plan", name: "Command Code", icon: "commandcode", plans: "Pro · GOAT · Max · Ultra", own: true },
// devin's credentials.toml keeps one account too
{ agent: "devin", name: "Devin", icon: "devin", plans: "Pro · Enterprise", single: true },
// Kiro's own sign-in page (Google, GitHub, Builder ID, Identity Center); kiro-cli's or the IDE's is read too
{ agent: "kiro", name: "Kiro", icon: "kiro-color", plans: "Free · Pro · Pro+ · Power", single: true },
// Google's sign-ins; Gemini CLI's own account is read too
{ agent: "gemini", name: "Gemini CLI", icon: "geminicli-color", plans: "Code Assist Standard · Enterprise", own: true },
// accounts can also come from another tool's export (Antigravity Cockpit, Antigravity Manager, CLIProxyAPI)
+1 -1
View File
@@ -212,7 +212,7 @@ func providerInfo(p provider.Provider, agents []agentUse) providerJSON {
// a Cursor subscription is served by the gateway, not an agent magpie configures
out.Account.Name, out.Account.Icon = "Cursor CLI", "cursor"
} else if a.Agent == "kiro" {
// Kiro's sign-in is kiro-cli's or the Kiro IDE's
// Kiro's sign-in is magpie's own, kiro-cli's or the Kiro IDE's
out.Account.Name, out.Account.Icon = "Kiro", "kiro-color"
} else if a.Agent == "antigravity" {
out.Account.Name, out.Account.Icon = "Antigravity", "antigravity-color"
+6 -3
View File
@@ -200,12 +200,15 @@ func readKiroIDE() (kiroCred, bool) {
return c, true
}
// readKiro is the sign-in to use: the key saved on the provider, else
// kiro-cli's, else the IDE's.
// readKiro is the sign-in to use: the key saved on the provider, else the
// one made in magpie, else kiro-cli's, else the IDE's.
func readKiro(key string) (kiroCred, bool) {
if key != "" {
return kiroCred{access: key, method: "apikey", region: "us-east-1"}, true
}
if c, ok := readKiroMagpie(); ok {
return c, true
}
if c, ok := readKiroCLI(); ok {
return c, true
}
@@ -253,7 +256,7 @@ func KiroAuthOf(ctx context.Context, key string, stale bool) (KiroAuth, error) {
read, ok := readKiro(key)
if !ok {
kiroAuthCache.ok = false
return KiroAuth{}, errors.New("Kiro isn't signed in; sign in with `kiro-cli login` or the Kiro IDE, or save a Kiro API key on the provider")
return KiroAuth{}, errors.New("Kiro isn't signed in; add the Kiro subscription in magpie, sign in with `kiro-cli login` or the Kiro IDE, or save a Kiro API key on the provider")
}
c := kiroAuthCache.cred
if !kiroAuthCache.ok || kiroAuthCache.key != key || read.access != c.access || !c.fresh() || stale {
+314
View File
@@ -0,0 +1,314 @@
package provider
// Signing in to Kiro from magpie, the way the Kiro IDE does: Kiro's sign-in
// page (app.kiro.dev) offers Google, GitHub, AWS Builder ID and IAM
// Identity Center, and sends the browser back to a port on this machine.
// Google and GitHub come back with a code Kiro's auth service trades for
// tokens; Builder ID and Identity Center come back with where to sign in
// at AWS, which is then done with a client registered for it, as the IDE
// does. The sign-in is magpie's own, kept beside logins.json, so kiro-cli's
// and the IDE's are left as they are; while it is there it is the one used.
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
)
// Where Kiro signs in; vars so tests can answer them.
var (
kiroPortalURL = "https://app.kiro.dev"
kiroAuthService = "https://prod.us-east-1.auth.desktop.kiro.dev"
kiroOIDC = func(region string) string { return "https://oidc." + region + ".amazonaws.com" }
// the ports Kiro's sign-in page sends the browser back to, the IDE's
kiroCallbackPorts = []int{3128, 4649, 6588, 8008, 9091, 49153, 50153, 51153, 52153, 53153}
)
// kiroScopes are what the IDE asks AWS for.
var kiroScopes = []string{"codewhisperer:completions", "codewhisperer:analysis", "codewhisperer:conversations",
"codewhisperer:transformations", "codewhisperer:taskassist"}
// kiroIDEUA is how the IDE names itself to Kiro's auth service.
var kiroIDEUA = func() string {
h, _ := os.Hostname()
sum := sha256.Sum256([]byte("magpie:" + h))
return "KiroIDE-1.1.70-" + hex.EncodeToString(sum[:])
}()
// kiroSignInPath is magpie's own Kiro sign-in.
func kiroSignInPath() string { return filepath.Join(filepath.Dir(Path()), "kiro-auth-token.json") }
// kiroSaved is magpie's Kiro sign-in as it is kept: the IDE's shape, with
// an AWS sign-in's client beside it.
type kiroSaved struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresAt string `json:"expiresAt"`
AuthMethod string `json:"authMethod"` // social | IdC
Provider string `json:"provider"` // Google, Github, BuilderId, Enterprise
Region string `json:"region,omitempty"`
ProfileArn string `json:"profileArn,omitempty"`
ClientID string `json:"clientId,omitempty"`
ClientSecret string `json:"clientSecret,omitempty"`
}
// readKiroMagpie reads the sign-in made in magpie.
func readKiroMagpie() (kiroCred, bool) {
path := kiroSignInPath()
b, err := os.ReadFile(path)
if err != nil {
return kiroCred{}, false
}
var t kiroSaved
if json.Unmarshal(b, &t) != nil || t.AccessToken == "" {
return kiroCred{}, false
}
c := kiroCred{access: t.AccessToken, refresh: t.RefreshToken, region: t.Region, profile: t.ProfileArn,
clientID: t.ClientID, clientSecret: t.ClientSecret, idePath: path}
c.expires, _ = time.Parse(time.RFC3339Nano, t.ExpiresAt)
if c.region == "" {
c.region = "us-east-1"
}
if strings.EqualFold(t.AuthMethod, "IdC") && c.clientID != "" {
c.method = "idc"
} else {
c.method, c.social = "social", true
}
return c, true
}
// kiroFlow is where a Kiro sign-in is between its two pages.
type kiroFlow struct {
port int
// an AWS sign-in, once Kiro's page has sent the browser on to it
region, clientID, clientSecret, provider string
state, verifier string
}
// startKiroSignIn opens Kiro's sign-in page, to come back to one of the
// ports it sends browsers back to.
func startKiroSignIn(s *signInFlow, challenge string) (net.Listener, error) {
var ln net.Listener
var err error
for _, p := range kiroCallbackPorts {
if ln, err = net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", p)); err == nil {
break
}
}
if ln == nil {
return nil, errors.New("the ports Kiro's sign-in comes back to are all busy; close the Kiro IDE's sign-in and try again")
}
port := ln.Addr().(*net.TCPAddr).Port
s.kiro = &kiroFlow{port: port}
s.redirect = fmt.Sprintf("http://localhost:%d", port)
q := url.Values{}
q.Set("state", s.state)
q.Set("code_challenge", challenge)
q.Set("code_challenge_method", "S256")
q.Set("redirect_uri", s.redirect)
q.Set("redirect_from", "KiroIDE")
s.mu.Lock()
s.st.URL = kiroPortalURL + "/signin?" + q.Encode()
s.mu.Unlock()
return ln, nil
}
// kiroCallback is the browser back from Kiro's page, or from AWS's.
func (s *signInFlow) kiroCallback(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/oauth/callback", "/signin/callback":
default:
http.NotFound(w, r)
return
}
q := r.URL.Query()
if s.status().State != "waiting" {
signInPage(w, false, "This sign-in is over", "Start it again from magpie.")
return
}
fail := func(msg string) {
s.finish(SignInState{State: "failed", Error: msg})
signInPage(w, false, "Sign-in didn't finish", msg)
}
if e := q.Get("error"); e != "" {
msg := q.Get("error_description")
if msg == "" {
msg = e
}
fail(msg)
return
}
k := s.kiro
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
var saved kiroSaved
switch {
case k.state != "" && q.Get("state") == k.state:
// back from AWS
if q.Get("code") == "" {
fail("AWS sent back no code")
return
}
var tok struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresIn int `json:"expiresIn"`
}
err := kiroSignInPost(ctx, kiroOIDC(k.region)+"/token", map[string]string{"clientId": k.clientID, "clientSecret": k.clientSecret,
"grantType": "authorization_code", "redirectUri": k.awsRedirect(), "code": q.Get("code"), "codeVerifier": k.verifier}, &tok)
if err == nil && tok.AccessToken == "" {
err = errors.New("AWS sent back no token")
}
if err != nil {
fail(err.Error())
return
}
saved = kiroSaved{AccessToken: tok.AccessToken, RefreshToken: tok.RefreshToken, ExpiresAt: kiroExpiry(tok.ExpiresIn),
AuthMethod: "IdC", Provider: k.provider, Region: k.region, ClientID: k.clientID, ClientSecret: k.clientSecret}
case q.Get("state") != s.state:
// not ours: someone else's page, or a stale tab
signInPage(w, false, "This link isn't from magpie's sign-in", "Start it again from magpie.")
return
default:
switch opt := q.Get("login_option"); opt {
case "google", "github":
var tok struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ProfileArn string `json:"profileArn"`
ExpiresIn int `json:"expiresIn"`
}
err := kiroSignInPost(ctx, kiroAuthService+"/oauth/token", map[string]string{"code": q.Get("code"),
"code_verifier": s.verifier, "redirect_uri": s.redirect + r.URL.Path + "?login_option=" + opt}, &tok)
if err == nil && tok.AccessToken == "" {
err = errors.New("Kiro sent back no token")
}
if err != nil {
fail(err.Error())
return
}
provider := map[string]string{"google": "Google", "github": "Github"}[opt]
saved = kiroSaved{AccessToken: tok.AccessToken, RefreshToken: tok.RefreshToken, ExpiresAt: kiroExpiry(tok.ExpiresIn),
AuthMethod: "social", Provider: provider, Region: "us-east-1", ProfileArn: tok.ProfileArn}
case "builderid", "awsidc", "internal":
// on to AWS, with a client registered for this sign-in
issuer, region := q.Get("issuer_url"), q.Get("idc_region")
if issuer == "" || region == "" {
fail("Kiro's page didn't say where to sign in at AWS")
return
}
var reg struct {
ClientID string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
}
err := kiroSignInPost(ctx, kiroOIDC(region)+"/client/register", map[string]any{"clientName": "Kiro IDE", "clientType": "public",
"scopes": kiroScopes, "grantTypes": []string{"authorization_code", "refresh_token"},
"redirectUris": []string{"http://127.0.0.1/oauth/callback"}, "issuerUrl": issuer}, &reg)
if err == nil && reg.ClientID == "" {
err = errors.New("AWS registered no client")
}
if err != nil {
fail(err.Error())
return
}
k.region, k.clientID, k.clientSecret = region, reg.ClientID, reg.ClientSecret
k.provider = map[string]string{"builderid": "BuilderId", "awsidc": "Enterprise", "internal": "Internal"}[opt]
k.state, k.verifier = randomToken(24), randomToken(48)
sum := sha256.Sum256([]byte(k.verifier))
a := url.Values{}
a.Set("response_type", "code")
a.Set("client_id", k.clientID)
a.Set("redirect_uri", k.awsRedirect())
a.Set("scopes", strings.Join(kiroScopes, ","))
a.Set("state", k.state)
a.Set("code_challenge", base64.RawURLEncoding.EncodeToString(sum[:]))
a.Set("code_challenge_method", "S256")
http.Redirect(w, r, kiroOIDC(region)+"/authorize?"+a.Encode(), http.StatusFound)
return
case "external_idp":
fail("magpie can't sign in with a company's own identity provider yet; sign in with `kiro-cli login`, and magpie uses that")
return
default:
fail(fmt.Sprintf("Kiro's page came back with a sign-in magpie doesn't know (%q)", opt))
return
}
}
b, _ := json.MarshalIndent(saved, "", " ")
if err := writePrivate(kiroSignInPath(), append(b, '\n')); err != nil {
fail(err.Error())
return
}
forgetAccountCaches()
user, plan := askKiroIdentity("")
kiroStatus.Lock()
kiroStatus.key, kiroStatus.user, kiroStatus.plan, kiroStatus.at = "", user, plan, time.Now()
kiroStatus.Unlock()
if user == "" {
user = "Kiro account"
}
s.finish(SignInState{State: "done", User: user, Plan: plan, Using: true})
signInPage(w, true, "You're signed in", fmt.Sprintf("%s is added to magpie. You can close this tab.", user))
}
// awsRedirect is where AWS sends the browser back.
func (k *kiroFlow) awsRedirect() string {
return fmt.Sprintf("http://127.0.0.1:%d/oauth/callback", k.port)
}
func kiroExpiry(in int) string {
if in <= 0 {
in = 3600
}
return time.Now().Add(time.Duration(in) * time.Second).UTC().Format(time.RFC3339Nano)
}
// kiroSignInPost posts JSON to Kiro's auth service or AWS's sign-in.
func kiroSignInPost(ctx context.Context, u string, body any, dst any) error {
b, _ := json.Marshal(body)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, bytes.NewReader(b))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", kiroIDEUA)
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
out, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
if res.StatusCode/100 != 2 {
var e struct {
Message string `json:"message"`
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
_ = json.Unmarshal(out, &e)
msg := e.ErrorDescription
if msg == "" {
msg = e.Message
}
if msg == "" {
msg = e.Error
}
if msg == "" {
msg = http.StatusText(res.StatusCode)
}
return fmt.Errorf("the sign-in was refused (%d): %s", res.StatusCode, msg)
}
return json.Unmarshal(out, dst)
}
+165
View File
@@ -0,0 +1,165 @@
package provider
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
)
// kiroFakeSignIn answers Kiro's auth service and AWS's sign-in, checking
// what the IDE would send; it returns the sign-in's page and a browser that
// doesn't follow redirects.
func kiroFakeSignIn(t *testing.T) (start func() (SignInState, url.Values), browser *http.Client) {
t.Helper()
kiroSandbox(t)
var challenge, redirect string
var awsChallenge string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var in map[string]any
json.NewDecoder(r.Body).Decode(&in)
str := func(k string) string { s, _ := in[k].(string); return s }
verify := func(verifier, want string) bool {
sum := sha256.Sum256([]byte(verifier))
return base64.RawURLEncoding.EncodeToString(sum[:]) == want
}
switch r.URL.Path {
case "/oauth/token": // Kiro's, for Google and GitHub
if str("code") != "gcode" || !verify(str("code_verifier"), challenge) || str("redirect_uri") != redirect+"/oauth/callback?login_option=google" ||
!strings.HasPrefix(r.Header.Get("User-Agent"), "KiroIDE-") {
t.Errorf("Kiro token request %v", in)
w.WriteHeader(400)
return
}
json.NewEncoder(w).Encode(map[string]any{"accessToken": "social-at", "refreshToken": "social-rt", "profileArn": "arn:aws:codewhisperer:us-east-1:1:profile/P", "expiresIn": 3600})
case "/client/register":
if in["issuerUrl"] != "https://view.awsapps.com/start" || in["clientType"] != "public" {
t.Errorf("register %v", in)
}
json.NewEncoder(w).Encode(map[string]any{"clientId": "cid", "clientSecret": "csecret", "clientSecretExpiresAt": time.Now().Add(90 * 24 * time.Hour).Unix()})
case "/token": // AWS's
if str("grantType") != "authorization_code" || str("code") != "awscode" || str("clientId") != "cid" || str("clientSecret") != "csecret" ||
!verify(str("codeVerifier"), awsChallenge) || !strings.HasPrefix(str("redirectUri"), "http://127.0.0.1:") {
t.Errorf("AWS token request %v", in)
w.WriteHeader(400)
return
}
json.NewEncoder(w).Encode(map[string]any{"accessToken": "idc-at", "refreshToken": "idc-rt", "expiresIn": 3600})
default:
http.NotFound(w, r)
}
}))
t.Cleanup(srv.Close)
auth, oidc, ports, ask := kiroAuthService, kiroOIDC, kiroCallbackPorts, askKiroIdentity
kiroAuthService, kiroOIDC, kiroCallbackPorts = srv.URL, func(string) string { return srv.URL }, []int{0}
askKiroIdentity = func(string) (string, string) {
if c, ok := readKiro(""); ok && c.access == "social-at" {
return "me@example.com", "KIRO PRO"
}
return "", ""
}
t.Cleanup(func() { kiroAuthService, kiroOIDC, kiroCallbackPorts, askKiroIdentity = auth, oidc, ports, ask })
browser = &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
start = func() (SignInState, url.Values) {
st, err := StartSignIn("kiro")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { CancelSignIn(st.ID) })
u, _ := url.Parse(st.URL)
q := u.Query()
if !strings.HasPrefix(st.URL, "https://app.kiro.dev/signin?") || q.Get("redirect_from") != "KiroIDE" || q.Get("code_challenge_method") != "S256" {
t.Fatalf("page %s", st.URL)
}
challenge, redirect = q.Get("code_challenge"), q.Get("redirect_uri")
return st, q
}
kiroSetAWSChallenge = func(c string) { awsChallenge = c }
return start, browser
}
// kiroSetAWSChallenge tells the fake AWS the challenge its page was sent.
var kiroSetAWSChallenge func(string)
// Signed in with Google on Kiro's page, the account is magpie's own and
// the one used, ahead of kiro-cli's, which is left as it was.
func TestKiroSignInWithGoogle(t *testing.T) {
start, browser := kiroFakeSignIn(t)
writeKiroCLI(t, map[string]any{"kirocli:social:token": map[string]any{"access_token": "cli-at", "expires_at": "2099-01-01T00:00:00Z"}})
st, q := start()
res, err := browser.Get(q.Get("redirect_uri") + "/oauth/callback?login_option=google&code=gcode&state=" + url.QueryEscape(q.Get("state")))
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if st, _ = SignInStatus(st.ID); st.State != "done" || st.User != "me@example.com" || st.Plan != "KIRO PRO" || !st.Using {
t.Fatalf("status %+v", st)
}
c, ok := readKiro("")
if !ok || c.access != "social-at" || c.refresh != "social-rt" || c.method != "social" || c.profile != "arn:aws:codewhisperer:us-east-1:1:profile/P" || !c.fresh() {
t.Fatalf("cred %+v", c)
}
if row := readKiroRow(t, "kirocli:social:token"); row["access_token"] != "cli-at" {
t.Fatalf("kiro-cli's sign-in changed: %v", row)
}
if p, ok := kiroAccount(); !ok || p.Account.User != "me@example.com" {
t.Fatalf("account %+v", p.Account)
}
}
// Builder ID goes on from Kiro's page to AWS's, with a client registered
// for it, and comes back an AWS sign-in magpie can refresh.
func TestKiroSignInWithBuilderID(t *testing.T) {
start, browser := kiroFakeSignIn(t)
st, q := start()
res, err := browser.Get(q.Get("redirect_uri") + "/signin/callback?login_option=builderid&issuer_url=" + url.QueryEscape("https://view.awsapps.com/start") +
"&idc_region=us-east-1&state=" + url.QueryEscape(q.Get("state")))
if err != nil {
t.Fatal(err)
}
res.Body.Close()
loc, _ := url.Parse(res.Header.Get("Location"))
a := loc.Query()
if res.StatusCode != http.StatusFound || loc.Path != "/authorize" || a.Get("client_id") != "cid" || a.Get("response_type") != "code" ||
!strings.Contains(a.Get("scopes"), "codewhisperer:conversations") || !strings.HasSuffix(a.Get("redirect_uri"), "/oauth/callback") {
t.Fatalf("on to AWS: %d %s", res.StatusCode, loc)
}
kiroSetAWSChallenge(a.Get("code_challenge"))
// a stale tab's answer is turned away
if res, err = browser.Get(a.Get("redirect_uri") + "?code=awscode&state=nope"); err == nil {
res.Body.Close()
}
if st, _ = SignInStatus(st.ID); st.State != "waiting" {
t.Fatalf("a stranger's answer ended it: %+v", st)
}
if res, err = browser.Get(a.Get("redirect_uri") + "?code=awscode&state=" + url.QueryEscape(a.Get("state"))); err != nil {
t.Fatal(err)
}
res.Body.Close()
if st, _ = SignInStatus(st.ID); st.State != "done" || st.User != "Kiro account" {
t.Fatalf("status %+v", st)
}
c, ok := readKiro("")
if !ok || c.access != "idc-at" || c.method != "idc" || c.clientID != "cid" || c.clientSecret != "csecret" || c.region != "us-east-1" {
t.Fatalf("cred %+v", c)
}
}
// A company's own identity provider is left to kiro-cli, and said so.
func TestKiroSignInExternalIdP(t *testing.T) {
start, browser := kiroFakeSignIn(t)
st, q := start()
res, err := browser.Get(q.Get("redirect_uri") + "/oauth/callback?login_option=external_idp&state=" + url.QueryEscape(q.Get("state")))
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if st, _ = SignInStatus(st.ID); st.State != "failed" || !strings.Contains(st.Error, "kiro-cli login") {
t.Fatalf("status %+v", st)
}
}
+10
View File
@@ -68,6 +68,7 @@ type signInFlow struct {
redirect string
srv *http.Server
stop func() // ends an agent's own login command, when that is the sign-in
kiro *kiroFlow
done chan struct{}
}
@@ -228,6 +229,11 @@ func (s *signInFlow) begin() error {
s.mu.Lock()
s.st.URL = devinAuthorizeURL + "?" + q.Encode()
s.mu.Unlock()
case "kiro":
// Kiro's sign-in page, as the Kiro IDE opens it
if ln, err = startKiroSignIn(s, challenge); err != nil {
return err
}
case "copilot":
// GitHub's device code, as Copilot's editors sign in
if err := startCopilotSignIn(s); err != nil {
@@ -367,6 +373,10 @@ func (s *signInFlow) finish(out SignInState) bool {
}
func (s *signInFlow) callback(w http.ResponseWriter, r *http.Request) {
if s.kiro != nil {
s.kiroCallback(w, r)
return
}
switch r.URL.Path {
case "/callback", "/auth/callback", "/oauth2callback", "/oauth-callback":
case "/cancel":