diff --git a/internal/auth/kiro/aws.go b/internal/auth/kiro/aws.go index 247a365c..6ec67c49 100644 --- a/internal/auth/kiro/aws.go +++ b/internal/auth/kiro/aws.go @@ -92,7 +92,7 @@ const KiroIDETokenFile = ".aws/sso/cache/kiro-auth-token.json" // Default retry configuration for file reading const ( - defaultTokenReadMaxAttempts = 10 // Maximum retry attempts + defaultTokenReadMaxAttempts = 10 // Maximum retry attempts defaultTokenReadBaseDelay = 50 * time.Millisecond // Base delay between retries ) @@ -301,7 +301,7 @@ func ListKiroTokenFiles() ([]string, error) { } cacheDir := filepath.Join(homeDir, ".aws", "sso", "cache") - + // Check if directory exists if _, err := os.Stat(cacheDir); os.IsNotExist(err) { return nil, nil // No token files @@ -488,14 +488,16 @@ func ExtractIDCIdentifier(startURL string) string { // GenerateTokenFileName generates a unique filename for token storage. // Priority: email > startUrl identifier (for IDC) > authMethod only -// Format: kiro-{authMethod}-{identifier}.json +// Email is unique, so no sequence suffix needed. Sequence is only added +// when email is unavailable to prevent filename collisions. +// Format: kiro-{authMethod}-{identifier}[-{seq}].json func GenerateTokenFileName(tokenData *KiroTokenData) string { authMethod := tokenData.AuthMethod if authMethod == "" { authMethod = "unknown" } - // Priority 1: Use email if available + // Priority 1: Use email if available (no sequence needed, email is unique) if tokenData.Email != "" { // Sanitize email for filename (replace @ and . with -) sanitizedEmail := tokenData.Email @@ -504,14 +506,17 @@ func GenerateTokenFileName(tokenData *KiroTokenData) string { return fmt.Sprintf("kiro-%s-%s.json", authMethod, sanitizedEmail) } - // Priority 2: For IDC, use startUrl identifier + // Generate sequence only when email is unavailable + seq := time.Now().UnixNano() % 100000 + + // Priority 2: For IDC, use startUrl identifier with sequence if authMethod == "idc" && tokenData.StartURL != "" { identifier := ExtractIDCIdentifier(tokenData.StartURL) if identifier != "" { - return fmt.Sprintf("kiro-%s-%s.json", authMethod, identifier) + return fmt.Sprintf("kiro-%s-%s-%05d.json", authMethod, identifier, seq) } } - // Priority 3: Fallback to authMethod only - return fmt.Sprintf("kiro-%s.json", authMethod) + // Priority 3: Fallback to authMethod only with sequence + return fmt.Sprintf("kiro-%s-%05d.json", authMethod, seq) } diff --git a/sdk/auth/kiro.go b/sdk/auth/kiro.go index b6a13265..ad165b75 100644 --- a/sdk/auth/kiro.go +++ b/sdk/auth/kiro.go @@ -70,14 +70,25 @@ func (a *KiroAuthenticator) createAuthRecord(tokenData *kiroauth.KiroTokenData, } // Determine label and identifier based on auth method + // Generate sequence number for uniqueness + seq := time.Now().UnixNano() % 100000 + var label, idPart string if tokenData.AuthMethod == "idc" { label = "kiro-idc" - // For IDC auth, always use clientID as identifier - if tokenData.ClientID != "" { - idPart = kiroauth.SanitizeEmailForFilename(tokenData.ClientID) + // Priority: email > startUrl identifier > sequence only + // Email is unique, so no sequence needed when email is available + if tokenData.Email != "" { + idPart = kiroauth.SanitizeEmailForFilename(tokenData.Email) + } else if tokenData.StartURL != "" { + identifier := kiroauth.ExtractIDCIdentifier(tokenData.StartURL) + if identifier != "" { + idPart = fmt.Sprintf("%s-%05d", identifier, seq) + } else { + idPart = fmt.Sprintf("%05d", seq) + } } else { - idPart = fmt.Sprintf("%d", time.Now().UnixNano()%100000) + idPart = fmt.Sprintf("%05d", seq) } } else { label = fmt.Sprintf("kiro-%s", source) @@ -126,14 +137,14 @@ func (a *KiroAuthenticator) createAuthRecord(tokenData *kiroauth.KiroTokenData, } record := &coreauth.Auth{ - ID: fileName, - Provider: "kiro", - FileName: fileName, - Label: label, - Status: coreauth.StatusActive, - CreatedAt: now, - UpdatedAt: now, - Metadata: metadata, + ID: fileName, + Provider: "kiro", + FileName: fileName, + Label: label, + Status: coreauth.StatusActive, + CreatedAt: now, + UpdatedAt: now, + Metadata: metadata, Attributes: attributes, // NextRefreshAfter: 20 minutes before expiry NextRefreshAfter: expiresAt.Add(-20 * time.Minute),