Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions internal/sso/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ type ClientRegistrationCache struct {
ReceivedAt time.Time `json:"receivedAt"`
}

// normalizeStartURL removes trailing /# or / to match aws-sso-util format
// normalizeStartURL removes a trailing "#" or "/" from startURL to normalize AWS SSO start URLs.
func normalizeStartURL(startURL string) string {
startURL = strings.TrimSuffix(startURL, "#")
startURL = strings.TrimSuffix(startURL, "/")
return startURL
}

// getTokenCachePath returns the filesystem path for the SSO token cache file corresponding to startURL.
// It ensures the ~/.aws/sso/cache directory exists, normalizes startURL (removes trailing "#" and "/"),
// and uses the SHA-1 hex of the normalized URL with a ".json" extension as the filename.
// An error is returned if the user's home directory cannot be determined or the cache directory cannot be created.
func getTokenCachePath(startURL string) (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand All @@ -52,6 +56,15 @@ func getTokenCachePath(startURL string) (string, error) {
return filepath.Join(cacheDir, hash+".json"), nil
}

// LoadTokenCache loads the SSO token cache for the given start URL.
// It first attempts a direct lookup using the normalized start URL hash, and if that
// fails it scans the SSO cache directory for a token whose stored StartUrl matches
// the provided start URL (after normalization).
//
// The startURL parameter is the SSO start URL used to locate the cached token.
// It returns a pointer to the TokenCache when a matching cache file is found,
// (nil, nil) if no matching token exists, or (nil, error) if an I/O or parsing
// error occurs.
func LoadTokenCache(startURL string) (*TokenCache, error) {
// First try direct path lookup with normalized URL
path, err := getTokenCachePath(startURL)
Expand All @@ -72,7 +85,10 @@ func LoadTokenCache(startURL string) (*TokenCache, error) {
return findTokenByStartURL(startURL)
}

// findTokenByStartURL scans all cache files to find a token matching the startURL
// findTokenByStartURL scans the user's SSO cache (~/.aws/sso/cache) for a token whose StartUrl matches the provided startURL after normalization.
// It ignores directories, non-`.json` files, and files prefixed with "botocore-client-id-"; unreadable or unparseable files are skipped.
// If a matching token is found it is returned. If no match is found or the cache directory does not exist, (nil, nil) is returned.
// Any error encountered while reading the cache directory is returned.
func findTokenByStartURL(startURL string) (*TokenCache, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand Down Expand Up @@ -119,6 +135,9 @@ func findTokenByStartURL(startURL string) (*TokenCache, error) {
return nil, nil
}

// SaveTokenCache writes a botocore-compatible SSO token cache file for the given start URL and region.
// It serializes a TokenCache containing the normalized StartUrl, Region, AccessToken, ExpiresAt, and the current ReceivedAt timestamp, and writes it to the per-start-URL cache file with file mode 0600.
// Returns an error if the cache path cannot be determined, JSON marshaling fails, or the file cannot be written.
func SaveTokenCache(accessToken, startUrl, region string, expiresAt time.Time) error {
path, err := getTokenCachePath(startUrl)
if err != nil {
Expand All @@ -141,6 +160,10 @@ func SaveTokenCache(accessToken, startUrl, region string, expiresAt time.Time) e
return os.WriteFile(path, data, 0600)
}

// ClearTokenCache removes all SSO token cache JSON files from the user's
// ~/.aws/sso/cache directory except for botocore client-registration files.
// It returns nil if the cache directory does not exist and otherwise
// propagates any filesystem errors encountered while reading or removing files.
func ClearTokenCache() error {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand Down Expand Up @@ -178,6 +201,7 @@ func ClearTokenCache() error {
return nil
}

// user home directory cannot be determined or the cache directory cannot be created.
func getClientRegistrationCachePath(region string) (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand All @@ -194,6 +218,8 @@ func getClientRegistrationCachePath(region string) (string, error) {
return filepath.Join(cacheDir, filename), nil
}

// LoadClientRegistration reads the botocore-style client registration cache for the given region.
// It returns the cached ClientRegistrationCache if present, `nil, nil` when no cache file exists, or an error if reading or JSON unmarshalling fails.
func LoadClientRegistration(region string) (*ClientRegistrationCache, error) {
path, err := getClientRegistrationCachePath(region)
if err != nil {
Expand All @@ -216,6 +242,8 @@ func LoadClientRegistration(region string) (*ClientRegistrationCache, error) {
return &cache, nil
}

// SaveClientRegistration writes botocore-compatible client registration data for the given region into the SSO cache directory.
// The stored record contains the client ID, client secret, the provided expiration time, and the current receipt timestamp; an error is returned if the cache path cannot be determined or the file cannot be written.
func SaveClientRegistration(region, clientId, clientSecret string, expiresAt time.Time) error {
path, err := getClientRegistrationCachePath(region)
if err != nil {
Expand All @@ -235,4 +263,4 @@ func SaveClientRegistration(region, clientId, clientSecret string, expiresAt tim
}

return os.WriteFile(path, data, 0600)
}
}
Loading