Skip to content

Commit

Permalink
feat(set-session): ignore existing token if it is not valid for a min…
Browse files Browse the repository at this point in the history
…imum duration
  • Loading branch information
reubenmiller committed Apr 27, 2024
1 parent 61fd14f commit b4fd72e
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 0 deletions.
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

0 comments on commit b4fd72e

Please sign in to comment.