diff --git a/service/ccm/credential.go b/service/ccm/credential.go index f14d4d2bc..6b3000861 100644 --- a/service/ccm/credential.go +++ b/service/ccm/credential.go @@ -108,6 +108,14 @@ func readCredentialsFromFile(path string) (*oauthCredentials, error) { return credentialsContainer.ClaudeAIAuth, nil } +func checkCredentialFileWritable(path string) error { + file, err := os.OpenFile(path, os.O_WRONLY, 0) + if err != nil { + return err + } + return file.Close() +} + func writeCredentialsToFile(oauthCredentials *oauthCredentials, path string) error { data, err := json.MarshalIndent(map[string]any{ "claudeAiOauth": oauthCredentials, diff --git a/service/ccm/credential_darwin.go b/service/ccm/credential_darwin.go index 24047b858..aef10c874 100644 --- a/service/ccm/credential_darwin.go +++ b/service/ccm/credential_darwin.go @@ -69,6 +69,13 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(defaultPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + return nil + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { if customPath != "" { return writeCredentialsToFile(oauthCredentials, customPath) diff --git a/service/ccm/credential_other.go b/service/ccm/credential_other.go index 11888b508..02c52e71e 100644 --- a/service/ccm/credential_other.go +++ b/service/ccm/credential_other.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(oauthCredentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ccm/credential_state.go b/service/ccm/credential_state.go index 937129597..ff64b24bc 100644 --- a/service/ccm/credential_state.go +++ b/service/ccm/credential_state.go @@ -225,6 +225,11 @@ func (c *defaultCredential) getAccessToken() (string, error) { return c.credentials.AccessToken, nil } + err = platformCanWriteCredentials(c.credentialPath) + if err != nil { + return "", E.Cause(err, "credential file not writable, refusing refresh to avoid invalidation") + } + baseCredentials := cloneCredentials(c.credentials) newCredentials, err := refreshToken(c.httpClient, c.credentials) if err != nil { @@ -258,7 +263,7 @@ func (c *defaultCredential) getAccessToken() (string, error) { err = platformWriteCredentials(newCredentials, c.credentialPath) if err != nil { - c.logger.Warn("persist refreshed token for ", c.tag, ": ", err) + c.logger.Error("persist refreshed token for ", c.tag, ": ", err) } return newCredentials.AccessToken, nil diff --git a/service/ocm/credential.go b/service/ocm/credential.go index f16beb916..c143f868a 100644 --- a/service/ocm/credential.go +++ b/service/ocm/credential.go @@ -55,6 +55,14 @@ func readCredentialsFromFile(path string) (*oauthCredentials, error) { return &credentials, nil } +func checkCredentialFileWritable(path string) error { + file, err := os.OpenFile(path, os.O_WRONLY, 0) + if err != nil { + return err + } + return file.Close() +} + func writeCredentialsToFile(credentials *oauthCredentials, path string) error { data, err := json.MarshalIndent(credentials, "", " ") if err != nil { diff --git a/service/ocm/credential_darwin.go b/service/ocm/credential_darwin.go index f3da2a63e..37e7c1c7a 100644 --- a/service/ocm/credential_darwin.go +++ b/service/ocm/credential_darwin.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ocm/credential_other.go b/service/ocm/credential_other.go index 22dfd0337..9da2a569d 100644 --- a/service/ocm/credential_other.go +++ b/service/ocm/credential_other.go @@ -13,6 +13,17 @@ func platformReadCredentials(customPath string) (*oauthCredentials, error) { return readCredentialsFromFile(customPath) } +func platformCanWriteCredentials(customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return checkCredentialFileWritable(customPath) +} + func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { if customPath == "" { var err error diff --git a/service/ocm/credential_state.go b/service/ocm/credential_state.go index e6c0642e4..547926b87 100644 --- a/service/ocm/credential_state.go +++ b/service/ocm/credential_state.go @@ -234,6 +234,11 @@ func (c *defaultCredential) getAccessToken() (string, error) { return c.credentials.getAccessToken(), nil } + err = platformCanWriteCredentials(c.credentialPath) + if err != nil { + return "", E.Cause(err, "credential file not writable, refusing refresh to avoid invalidation") + } + baseCredentials := cloneCredentials(c.credentials) newCredentials, err := refreshToken(c.httpClient, c.credentials) if err != nil { @@ -265,7 +270,7 @@ func (c *defaultCredential) getAccessToken() (string, error) { err = platformWriteCredentials(newCredentials, c.credentialPath) if err != nil { - c.logger.Warn("persist refreshed token for ", c.tag, ": ", err) + c.logger.Error("persist refreshed token for ", c.tag, ": ", err) } return newCredentials.getAccessToken(), nil