Skip to content

Merge feature/Issue-#95-profile-update#100

Open
gildas wants to merge 10 commits into
devfrom
feature/Issue-#95-profile-update
Open

Merge feature/Issue-#95-profile-update#100
gildas wants to merge 10 commits into
devfrom
feature/Issue-#95-profile-update

Conversation

@gildas
Copy link
Copy Markdown
Owner

@gildas gildas commented May 28, 2026

Feature Issue-#95-profile-update. Do not delete the feature branch after the merge.

Copilot AI review requested due to automatic review settings May 28, 2026 02:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses Issue #95 by ensuring profile credential updates respect the existing vault/plain-text storage strategy, and adds support for migrating existing plain-text credentials into the vault.

Changes:

  • Add bb profile update --to-vault to migrate an existing profile’s stored credentials into the OS keychain/vault.
  • Refactor token handling into a dedicated token cache/vault loader (and remove eager vault loading from Profiles.Load).
  • Update documentation around profile management, access token types, and logging sensitivity.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
README.md Updates profile management docs, vault behavior notes, and logging cautions.
cmd/profile/update.go Adds --to-vault and adjusts update logic to preserve vault vs plain-text behavior.
cmd/profile/token.go Introduces token load/save helpers (cache + vault) and token parsing utilities.
cmd/profile/profiles.go Removes eager secret loading from vault during profile load.
cmd/profile/profile.go Adds lazy vault getters for secrets, updates token storage model, and adjusts validation defaults.
cmd/profile/profile_client.go Updates auth flow to use cached/vault-backed token handling and secret retrieval helpers.
cmd/profile/profile_client_test.go Adjusts tests for GetAll behavior with updated auth/token logic.
cmd/profile/delete.go Updates vault deletion logging and logic.
cmd/profile/create.go Improves vault-related logging to include the vault key name.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +370 to +374
if err := profile.loadAccessToken(context); err == nil {
if !profile.isTokenExpired() {
log.Infof("Using access token for profile %s", profile.Name)
log.Debugf("Token expires on %s in %s", profile.TokenExpires.Format(time.RFC3339), time.Until(profile.TokenExpires))
return request.BearerAuthorization(profile.AccessToken), nil
log.Debugf("Token expires on %s in %s", profile.token.GetExpiresOn().Format(time.RFC3339), profile.token.GetExpiresIn())
return request.BearerAuthorization(profile.token.AccessToken), nil
Comment on lines +379 to 385
if profile.token != nil && len(profile.token.RefreshToken) > 0 {
log.Warnf("Access token for profile %s expired %s ago and we have a refresh token", profile.Name, profile.token.GetExpiredSince())
payload["grant_type"] = "refresh_token"
payload["refresh_token"] = profile.RefreshToken
payload["refresh_token"] = profile.token.RefreshToken
} else {
log.Warnf("Access token for profile %s expired %s ago and we don't have a refresh token, we need to re-authorize the profile", profile.Name, profile.token.GetExpiredSince())
payload["grant_type"] = "client_credentials"
Comment on lines +352 to +358
if _, err := profile.saveAccessToken(r.Context(), result.Data); err != nil {
log.Errorf("Failed to save access token for profile %s: %v", profile.Name, err)
if errors.Is(err, errors.JSONUnmarshalError) {
http.Error(w, "Failed to parse access token response from BitBucket: "+err.Error(), http.StatusBadRequest)
} else {
http.Error(w, "Failed to save access token for profile "+profile.Name+": "+err.Error(), http.StatusInternalServerError)
}
Comment thread cmd/profile/update.go
Comment on lines +108 to +114
if updateOptions.ToVault {
updateOptions.NoVault = false
if len(profile.ClientSecret) > 0 {
vaultKey := profile.VaultKey
if cmd.Flag("vault-key").Changed && len(updateOptions.VaultKey) > 0 {
vaultKey = updateOptions.VaultKey
}
Comment thread cmd/profile/update.go
Comment on lines +110 to +136
if len(profile.ClientSecret) > 0 {
vaultKey := profile.VaultKey
if cmd.Flag("vault-key").Changed && len(updateOptions.VaultKey) > 0 {
vaultKey = updateOptions.VaultKey
}
if err := profile.SetCredentialInVault(vaultKey, profile.ClientID, profile.ClientSecret); err != nil {
return errors.Join(errors.Errorf("Failed to store client secret in the vault"), err)
}
log.Infof("Stored client secret in the vault for %s", profile.ClientID)
profile.ClientSecret = ""
updateOptions.ClientSecret = ""
} else if len(profile.Password) > 0 {
vaultKey := profile.VaultKey
if cmd.Flag("vault-key").Changed && len(updateOptions.VaultKey) > 0 {
vaultKey = updateOptions.VaultKey
}
if err := profile.SetCredentialInVault(vaultKey, profile.User, profile.Password); err != nil {
return errors.Join(errors.Errorf("Failed to store user password in the vault"), err)
}
log.Infof("Stored user password in the vault for %s", profile.User)
profile.Password = ""
updateOptions.Password = ""
} else if len(profile.AccessToken) > 0 {
vaultKey := profile.VaultKey
if cmd.Flag("vault-key").Changed && len(updateOptions.VaultKey) > 0 {
vaultKey = updateOptions.VaultKey
}
Comment thread cmd/profile/delete.go
Comment on lines 75 to 81
} else if len(profile.Name) > 0 {
_ = profile.DeleteCredentialFromVault(profile.VaultKey, profile.Name)
log.Debugf("Deleted name secret for profile %s from the vault", profile.Name)
log.Debugf("Deleted name secret for profile %s from the %s vault", profile.Name, profile.VaultKey)
} else if len(profile.AccessToken) > 0 {
_ = profile.DeleteCredentialFromVault(profile.VaultKey, profile.Name)
log.Debugf("Deleted access token for profile %s from the %s vault", profile.Name, profile.VaultKey)
}
apiRoot, err := url.Parse(server.URL)
suite.Require().NoError(err)
profile.Current = &profile.Profile{APIRoot: apiRoot, DefaultPageLength: 0, AccessToken: "fake-token"}
profile.Current = &profile.Profile{APIRoot: apiRoot, DefaultPageLength: 0}
apiRoot, err := url.Parse(server.URL)
suite.Require().NoError(err)
profile.Current = &profile.Profile{APIRoot: apiRoot, DefaultPageLength: 0, AccessToken: "fake-token"}
profile.Current = &profile.Profile{APIRoot: apiRoot, DefaultPageLength: 0}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants