Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(set-session): ignore existing token if it is not valid for a minimum duration #363

Merged
merged 1 commit into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
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
4 changes: 4 additions & 0 deletions docs/go-c8y-cli/docs/configuration/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,10 @@ Enable `UPDATE` commands. If set to `false` then all `UPDATE` related commands w

Default username which is used when creating a new command via `c8y sessions create`

### session.tokenValidFor: string

The minimum duration (e.g. `8h`) that a token should be valid for in order to reuse it. You can control when to renew it when setting the active session, and ignore the token if it is to expire soon based on the duration.

### storage.storepassword: boolean

Enable storage of your password in your session file. Disable if you do not want to store sensitive information to file. However this means you will be prompted for your password when you select a session via `set-session`.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ require github.com/hashicorp/go-version v1.6.0

require (
github.com/cli/browser v1.3.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/hashicorp/go-retryablehttp v0.7.5
github.com/reubenmiller/gojsonq/v2 v2.0.0-20221119213524-0fd921ac20a3
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand Down
32 changes: 32 additions & 0 deletions pkg/cmd/sessions/set/set.manual.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

"github.com/MakeNowJust/heredoc/v2"
"github.com/golang-jwt/jwt/v5"
"github.com/reubenmiller/go-c8y-cli/v2/pkg/c8ylogin"
"github.com/reubenmiller/go-c8y-cli/v2/pkg/c8ysession"
"github.com/reubenmiller/go-c8y-cli/v2/pkg/cmd/factory"
Expand Down Expand Up @@ -154,6 +155,19 @@ func (n *CmdSet) RunE(cmd *cobra.Command, args []string) error {

if n.ClearToken {
client.SetToken("")
} else {
// Check if token is valid for the minimum period
if tok := cfg.MustGetToken(); tok != "" {
shouldBeValidFor := cfg.TokenValidFor()
expiresSoon, expiresAt := ShouldRenewToken(tok, shouldBeValidFor)

if expiresSoon {
log.Warnf("Ignoring existing token as it will expire soon. minimumValidFor=%s, tokenExpiresAt=%s", shouldBeValidFor, expiresAt.Format(time.RFC3339))
client.SetToken("")
} else if expiresAt != nil {
log.Infof("Token expiresAt: %s", expiresAt.Format(time.RFC3339))
}
}
}

if err := utilities.CheckEncryption(n.factory.IOStreams, cfg, client); err != nil {
Expand Down Expand Up @@ -233,3 +247,21 @@ func hasChanged(client *c8y.Client, cfg *config.Config) bool {
}
return false
}

func ShouldRenewToken(t string, validFor time.Duration) (bool, *time.Time) {
claims := jwt.RegisteredClaims{}
parser := jwt.NewParser()
_, _, err := parser.ParseUnverified(t, &claims)

if err != nil {
// Invalid token
return true, nil
}

if claims.ExpiresAt != nil {
limit := claims.ExpiresAt.Add(-1 * validFor)
expiresSoon := limit.Before(time.Now())
return expiresSoon, &claims.ExpiresAt.Time
}
return true, nil
}
7 changes: 7 additions & 0 deletions pkg/cmd/settings/update/update.manual.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ var updateSettingsOptions = map[string]argumentHandler{
"true",
"false",
}, nil, cobra.ShellCompDirectiveNoFileComp},
"session.tokenValidFor": {"session.tokenValidFor", "string", config.SettingsSessionTokenValidFor, []string{
"1h",
"8h",
"24h",
"48h",
"7d",
}, nil, cobra.ShellCompDirectiveNoFileComp},

// cache
"defaults.cache": {"defaults.cache", "bool", config.SettingsDefaultsCacheEnabled, []string{
Expand Down
15 changes: 15 additions & 0 deletions pkg/config/cliConfiguration.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,9 @@ const (
// SettingsSessionAlwaysIncludePassword should the password always be included in the session variables or not
SettingsSessionAlwaysIncludePassword = "settings.session.alwaysIncludePassword"

// SettingsSessionTokenValidFor interval which the token must be valid for in order to reuse it
SettingsSessionTokenValidFor = "settings.session.tokenValidFor"

// Cache settings
// SettingsDefaultsCacheEnabled enable caching
SettingsDefaultsCacheEnabled = "settings.defaults.cache"
Expand Down Expand Up @@ -498,6 +501,7 @@ func (c *Config) bindSettings() {

// Session options
WithBindEnv(SettingsSessionAlwaysIncludePassword, false),
WithBindEnv(SettingsSessionTokenValidFor, "8h"),

WithBindEnv(SettingsBrowser, ""),

Expand Down Expand Up @@ -978,6 +982,17 @@ func (c *Config) AlwaysIncludePassword() bool {
return c.viper.GetBool(SettingsSessionAlwaysIncludePassword)
}

// TokenValidFor minimum validity of a token in order to reuse it
func (c *Config) TokenValidFor() time.Duration {
value := c.viper.GetString(SettingsSessionTokenValidFor)
duration, err := flags.GetDuration(value, true, time.Second)
if err != nil {
c.Logger.Warnf("Invalid duration. value=%s, err=%s", duration, err)
return 0
}
return duration
}

// CachePassphraseVariables return true if the passphrase variables should be persisted or not
func (c *Config) CachePassphraseVariables() bool {
return c.viper.GetBool(SettingEncryptionCachePassphrase)
Expand Down