fix(kiro): improve auto-refresh and IDC auth file handling

Amp-Thread-ID: https://ampcode.com/threads/T-019bdb94-80e3-7302-be0f-a69937826d13
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
781456868@qq.com
2026-01-20 21:57:45 +08:00
parent 73cef3a25a
commit a9ee971e1c
6 changed files with 53 additions and 32 deletions

View File

@@ -280,6 +280,11 @@ func (k *KiroAuth) CreateTokenStorage(tokenData *KiroTokenData) *KiroTokenStorag
AuthMethod: tokenData.AuthMethod, AuthMethod: tokenData.AuthMethod,
Provider: tokenData.Provider, Provider: tokenData.Provider,
LastRefresh: time.Now().Format(time.RFC3339), LastRefresh: time.Now().Format(time.RFC3339),
ClientID: tokenData.ClientID,
ClientSecret: tokenData.ClientSecret,
Region: tokenData.Region,
StartURL: tokenData.StartURL,
Email: tokenData.Email,
} }
} }
@@ -311,4 +316,19 @@ func (k *KiroAuth) UpdateTokenStorage(storage *KiroTokenStorage, tokenData *Kiro
storage.AuthMethod = tokenData.AuthMethod storage.AuthMethod = tokenData.AuthMethod
storage.Provider = tokenData.Provider storage.Provider = tokenData.Provider
storage.LastRefresh = time.Now().Format(time.RFC3339) storage.LastRefresh = time.Now().Format(time.RFC3339)
if tokenData.ClientID != "" {
storage.ClientID = tokenData.ClientID
}
if tokenData.ClientSecret != "" {
storage.ClientSecret = tokenData.ClientSecret
}
if tokenData.Region != "" {
storage.Region = tokenData.Region
}
if tokenData.StartURL != "" {
storage.StartURL = tokenData.StartURL
}
if tokenData.Email != "" {
storage.Email = tokenData.Email
}
} }

View File

@@ -377,17 +377,18 @@ func (h *OAuthWebHandler) pollForToken(ctx context.Context, session *webAuthSess
email := FetchUserEmailWithFallback(ctx, h.cfg, tokenResp.AccessToken) email := FetchUserEmailWithFallback(ctx, h.cfg, tokenResp.AccessToken)
tokenData := &KiroTokenData{ tokenData := &KiroTokenData{
AccessToken: tokenResp.AccessToken, AccessToken: tokenResp.AccessToken,
RefreshToken: tokenResp.RefreshToken, RefreshToken: tokenResp.RefreshToken,
ProfileArn: profileArn, ProfileArn: profileArn,
ExpiresAt: expiresAt.Format(time.RFC3339), ExpiresAt: expiresAt.Format(time.RFC3339),
AuthMethod: session.authMethod, AuthMethod: session.authMethod,
Provider: "AWS", Provider: "AWS",
ClientID: session.clientID, ClientID: session.clientID,
ClientSecret: session.clientSecret, ClientSecret: session.clientSecret,
Email: email, Email: email,
Region: session.region, Region: session.region,
} StartURL: session.startURL,
}
h.mu.Lock() h.mu.Lock()
session.status = statusSuccess session.status = statusSuccess
@@ -828,7 +829,7 @@ func (h *OAuthWebHandler) handleImportToken(c *gin.Context) {
// handleManualRefresh handles manual token refresh requests from the web UI. // handleManualRefresh handles manual token refresh requests from the web UI.
// This allows users to trigger a token refresh when needed, without waiting // This allows users to trigger a token refresh when needed, without waiting
// for the automatic 5-second check and 10-minute-before-expiry refresh cycle. // for the automatic 30-second check and 20-minute-before-expiry refresh cycle.
// Uses the same refresh logic as kiro_executor.Refresh for consistency. // Uses the same refresh logic as kiro_executor.Refresh for consistency.
func (h *OAuthWebHandler) handleManualRefresh(c *gin.Context) { func (h *OAuthWebHandler) handleManualRefresh(c *gin.Context) {
authDir := "" authDir := ""

View File

@@ -3513,14 +3513,14 @@ func (e *KiroExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*c
// Also check if expires_at is now in the future with sufficient buffer // Also check if expires_at is now in the future with sufficient buffer
if expiresAt, ok := auth.Metadata["expires_at"].(string); ok { if expiresAt, ok := auth.Metadata["expires_at"].(string); ok {
if expTime, err := time.Parse(time.RFC3339, expiresAt); err == nil { if expTime, err := time.Parse(time.RFC3339, expiresAt); err == nil {
// If token expires more than 5 minutes from now, it's still valid // If token expires more than 20 minutes from now, it's still valid
if time.Until(expTime) > 5*time.Minute { if time.Until(expTime) > 20*time.Minute {
log.Debugf("kiro executor: token is still valid (expires in %v), skipping refresh", time.Until(expTime)) log.Debugf("kiro executor: token is still valid (expires in %v), skipping refresh", time.Until(expTime))
// CRITICAL FIX: Set NextRefreshAfter to prevent frequent refresh checks // CRITICAL FIX: Set NextRefreshAfter to prevent frequent refresh checks
// Without this, shouldRefresh() will return true again in 5 seconds // Without this, shouldRefresh() will return true again in 30 seconds
updated := auth.Clone() updated := auth.Clone()
// Set next refresh to 5 minutes before expiry, or at least 30 seconds from now // Set next refresh to 20 minutes before expiry, or at least 30 seconds from now
nextRefresh := expTime.Add(-5 * time.Minute) nextRefresh := expTime.Add(-20 * time.Minute)
minNextRefresh := time.Now().Add(30 * time.Second) minNextRefresh := time.Now().Add(30 * time.Second)
if nextRefresh.Before(minNextRefresh) { if nextRefresh.Before(minNextRefresh) {
nextRefresh = minNextRefresh nextRefresh = minNextRefresh
@@ -3626,9 +3626,9 @@ func (e *KiroExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*c
updated.Attributes["profile_arn"] = tokenData.ProfileArn updated.Attributes["profile_arn"] = tokenData.ProfileArn
} }
// NextRefreshAfter is aligned with RefreshLead (5min) // NextRefreshAfter is aligned with RefreshLead (20min)
if expiresAt, parseErr := time.Parse(time.RFC3339, tokenData.ExpiresAt); parseErr == nil { if expiresAt, parseErr := time.Parse(time.RFC3339, tokenData.ExpiresAt); parseErr == nil {
updated.NextRefreshAfter = expiresAt.Add(-5 * time.Minute) updated.NextRefreshAfter = expiresAt.Add(-20 * time.Minute)
} }
log.Infof("kiro executor: token refreshed successfully, expires at %s", tokenData.ExpiresAt) log.Infof("kiro executor: token refreshed successfully, expires at %s", tokenData.ExpiresAt)

View File

@@ -217,11 +217,11 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth,
} }
id := s.idFor(path, baseDir) id := s.idFor(path, baseDir)
// Calculate NextRefreshAfter from expires_at (10 minutes before expiry) // Calculate NextRefreshAfter from expires_at (20 minutes before expiry)
var nextRefreshAfter time.Time var nextRefreshAfter time.Time
if expiresAtStr, ok := metadata["expires_at"].(string); ok && expiresAtStr != "" { if expiresAtStr, ok := metadata["expires_at"].(string); ok && expiresAtStr != "" {
if expiresAt, err := time.Parse(time.RFC3339, expiresAtStr); err == nil { if expiresAt, err := time.Parse(time.RFC3339, expiresAtStr); err == nil {
nextRefreshAfter = expiresAt.Add(-10 * time.Minute) nextRefreshAfter = expiresAt.Add(-20 * time.Minute)
} }
} }

View File

@@ -52,9 +52,9 @@ func (a *KiroAuthenticator) Provider() string {
} }
// RefreshLead indicates how soon before expiry a refresh should be attempted. // RefreshLead indicates how soon before expiry a refresh should be attempted.
// Set to 10 minutes for proactive refresh before token expiry. // Set to 20 minutes for proactive refresh before token expiry.
func (a *KiroAuthenticator) RefreshLead() *time.Duration { func (a *KiroAuthenticator) RefreshLead() *time.Duration {
d := 10 * time.Minute d := 20 * time.Minute
return &d return &d
} }
@@ -132,8 +132,8 @@ func (a *KiroAuthenticator) createAuthRecord(tokenData *kiroauth.KiroTokenData,
UpdatedAt: now, UpdatedAt: now,
Metadata: metadata, Metadata: metadata,
Attributes: attributes, Attributes: attributes,
// NextRefreshAfter: 10 minutes before expiry // NextRefreshAfter: 20 minutes before expiry
NextRefreshAfter: expiresAt.Add(-10 * time.Minute), NextRefreshAfter: expiresAt.Add(-20 * time.Minute),
} }
if tokenData.Email != "" { if tokenData.Email != "" {
@@ -214,8 +214,8 @@ func (a *KiroAuthenticator) LoginWithAuthCode(ctx context.Context, cfg *config.C
"source": "aws-builder-id-authcode", "source": "aws-builder-id-authcode",
"email": tokenData.Email, "email": tokenData.Email,
}, },
// NextRefreshAfter: 10 minutes before expiry // NextRefreshAfter: 20 minutes before expiry
NextRefreshAfter: expiresAt.Add(-10 * time.Minute), NextRefreshAfter: expiresAt.Add(-20 * time.Minute),
} }
if tokenData.Email != "" { if tokenData.Email != "" {
@@ -298,8 +298,8 @@ func (a *KiroAuthenticator) ImportFromKiroIDE(ctx context.Context, cfg *config.C
"email": tokenData.Email, "email": tokenData.Email,
"region": tokenData.Region, "region": tokenData.Region,
}, },
// NextRefreshAfter: 10 minutes before expiry // NextRefreshAfter: 20 minutes before expiry
NextRefreshAfter: expiresAt.Add(-10 * time.Minute), NextRefreshAfter: expiresAt.Add(-20 * time.Minute),
} }
// Display the email if extracted // Display the email if extracted
@@ -367,8 +367,8 @@ func (a *KiroAuthenticator) Refresh(ctx context.Context, cfg *config.Config, aut
updated.Metadata["refresh_token"] = tokenData.RefreshToken updated.Metadata["refresh_token"] = tokenData.RefreshToken
updated.Metadata["expires_at"] = tokenData.ExpiresAt updated.Metadata["expires_at"] = tokenData.ExpiresAt
updated.Metadata["last_refresh"] = now.Format(time.RFC3339) // For double-check optimization updated.Metadata["last_refresh"] = now.Format(time.RFC3339) // For double-check optimization
// NextRefreshAfter: 10 minutes before expiry // NextRefreshAfter: 20 minutes before expiry
updated.NextRefreshAfter = expiresAt.Add(-10 * time.Minute) updated.NextRefreshAfter = expiresAt.Add(-20 * time.Minute)
return updated, nil return updated, nil
} }

View File

@@ -47,7 +47,7 @@ type RefreshEvaluator interface {
} }
const ( const (
refreshCheckInterval = 5 * time.Second refreshCheckInterval = 30 * time.Second
refreshPendingBackoff = time.Minute refreshPendingBackoff = time.Minute
refreshFailureBackoff = 1 * time.Minute refreshFailureBackoff = 1 * time.Minute
quotaBackoffBase = time.Second quotaBackoffBase = time.Second