From d046d0bf00028f33b847537fcbf83c9f7440fa10 Mon Sep 17 00:00:00 2001 From: David Orozco Date: Thu, 3 Jul 2025 14:27:20 -0500 Subject: [PATCH 1/2] add auto refresh token if access token is expired --- go.mod | 6 +-- go.sum | 14 +++--- internal/auth/auth.go | 10 ++-- internal/command/auth/login.go | 7 +-- internal/config/api.go | 88 +++++++++++++++++++++++++++++++--- 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 6e6e3cd..40e5453 100644 --- a/go.mod +++ b/go.mod @@ -118,9 +118,9 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/crypto v0.38.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/sys v0.33.0 // indirect diff --git a/go.sum b/go.sum index 2653468..72fa1f2 100644 --- a/go.sum +++ b/go.sum @@ -354,8 +354,6 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/signadot/go-sdk v0.3.8-0.20250617214735-334394d59150 h1:ASJG4pszRfB3POcOi3FvCyl5OSvRntUmtjCdfMpTvXo= -github.com/signadot/go-sdk v0.3.8-0.20250617214735-334394d59150/go.mod h1:e/WZPadqyJ7S57OgDfUQn9TojmEGQO7z6B5vqF9TjKo= github.com/signadot/libconnect v0.1.1-0.20250702071230-3bb360c4ca13 h1:KR7XMD3A7b8lj5L+Yar+fklrnQW6Ff0G7L+6dBPc9uY= github.com/signadot/libconnect v0.1.1-0.20250702071230-3bb360c4ca13/go.mod h1:3OVwfvTsENxOIY7mRuhvygEPkdYMKKhaWl35Ll3LcEI= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= @@ -414,16 +412,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 472d31e..09078c2 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -17,10 +17,11 @@ const ( ) type Auth struct { - APIKey string `json:"apiKey,omitempty"` - BearerToken string `json:"bearerToken,omitempty"` - OrgName string `json:"orgName"` - ExpiresAt *time.Time `json:"expiresAt,omitempty"` + APIKey string `json:"apiKey,omitempty"` + BearerToken string `json:"bearerToken,omitempty"` + RefreshToken string `json:"refreshToken,omitempty"` + OrgName string `json:"orgName"` + ExpiresAt *time.Time `json:"expiresAt,omitempty"` } type ResolvedAuth struct { @@ -33,6 +34,7 @@ func ResolveAuth() (*ResolvedAuth, error) { if err != nil { return nil, err } + if auth == nil { return nil, nil } diff --git a/internal/command/auth/login.go b/internal/command/auth/login.go index 4016d46..173f6c9 100644 --- a/internal/command/auth/login.go +++ b/internal/command/auth/login.go @@ -108,9 +108,10 @@ func bearerTokenLogin(cfg *config.AuthLogin, out io.Writer) error { // store the auth info expiresAt := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second) err = auth.StoreAuthInKeyring(&auth.Auth{ - BearerToken: token.AccessToken, - OrgName: org.Name, - ExpiresAt: &expiresAt, + BearerToken: token.AccessToken, + RefreshToken: token.RefreshToken, + OrgName: org.Name, + ExpiresAt: &expiresAt, }) if err != nil { return fmt.Errorf("failed to store auth info: %w", err) diff --git a/internal/config/api.go b/internal/config/api.go index a6134f5..0384fac 100644 --- a/internal/config/api.go +++ b/internal/config/api.go @@ -4,16 +4,25 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "time" "github.com/signadot/cli/internal/auth" "github.com/signadot/cli/internal/buildinfo" "github.com/signadot/go-sdk/client" + sdkauth "github.com/signadot/go-sdk/client/auth" + "github.com/signadot/go-sdk/models" "github.com/signadot/go-sdk/transport" "github.com/spf13/viper" "gopkg.in/yaml.v2" ) +var ( + AuthExpiredError = errors.New("Authentication expired. Please log in using 'signadot auth login'") + AuthNoOrgFoundError = errors.New("No organisation found. Please log in using 'signadot auth login'") + AuthNoFoundError = errors.New("No authentication found. Please log in using 'signadot auth login'") +) + type API struct { Root @@ -66,24 +75,91 @@ func (a *API) init() error { } if authInfo == nil || (authInfo.APIKey == "" && authInfo.BearerToken == "") { - return errors.New("No authentication found. Please log in using 'signadot auth login'") + return AuthNoFoundError } - if authInfo.ExpiresAt != nil && authInfo.ExpiresAt.Before(time.Now()) { - return errors.New("Authentication expired. Please log in using 'signadot auth login'") + if authInfo.ExpiresAt != nil && authInfo.ExpiresAt.Before(time.Now()) && authInfo.Source != auth.KeyringAuthSource { + return AuthExpiredError } if authInfo.OrgName == "" { - return errors.New("No organisation found. Please log in using 'signadot auth login'") + return AuthNoOrgFoundError + } + + // Init basic settings and return + a.basicInit() + + if authInfo.Source == auth.KeyringAuthSource { + if err := a.checkKeyringAuth(authInfo); err != nil { + return err + } } a.ApiKey = authInfo.APIKey a.BearerToken = authInfo.BearerToken a.Org = authInfo.OrgName + return nil +} + +func (a *API) checkKeyringAuth(authInfo *auth.ResolvedAuth) error { + + // If the auth is expired, we need to refresh the token + if authInfo.ExpiresAt != nil && time.Now().After(*authInfo.ExpiresAt) { + if authInfo.RefreshToken == "" { + return AuthExpiredError + } + + newAuthInfo, err := a.refreshKeyringAuth(authInfo) + if err != nil { + return err + } + authInfo = newAuthInfo + return nil + } + + // If using API key, just return + if authInfo.APIKey != "" { + return nil + } - // Init basic settings and return - a.basicInit() return nil } +func (a *API) refreshKeyringAuth(authInfo *auth.ResolvedAuth) (*auth.ResolvedAuth, error) { + if err := a.InitUnauthAPIConfig(); err != nil { + return nil, err + } + + params := &sdkauth.AuthDeviceRefreshTokenParams{ + Data: &models.AuthdevicesRefreshTokenInput{ + RefreshToken: authInfo.RefreshToken, + }, + } + + resp, err := a.Client.Auth.AuthDeviceRefreshToken(params) + if err != nil { + return nil, fmt.Errorf("failed to refresh token: %w", err) + } + + expiresAt := time.Now().Add(time.Duration(resp.Payload.ExpiresIn) * time.Second) + authInfo.BearerToken = resp.Payload.AccessToken + authInfo.RefreshToken = resp.Payload.RefreshToken + authInfo.ExpiresAt = &expiresAt + + newAuthInfo := auth.Auth{ + APIKey: authInfo.APIKey, + BearerToken: authInfo.BearerToken, + RefreshToken: authInfo.RefreshToken, + OrgName: authInfo.OrgName, + ExpiresAt: &expiresAt, + } + + // Store updated auth in keyring + if err := auth.StoreAuthInKeyring(&newAuthInfo); err != nil { + return nil, fmt.Errorf("failed to store refreshed auth: %w", err) + } + + return authInfo, nil +} + func (a *API) basicInit() { if apiURL := viper.GetString("api_url"); apiURL != "" { a.APIURL = apiURL From cd6e3f8f76abcb6a4ef36cee26ac9550fb10cffd Mon Sep 17 00:00:00 2001 From: David Orozco Date: Fri, 11 Jul 2025 11:15:22 -0500 Subject: [PATCH 2/2] update sdk usage --- go.mod | 4 ++-- go.sum | 4 ++-- internal/config/api.go | 6 +----- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 40e5453..163f96c 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - golang.org/x/sync v0.15.0 // indirect + golang.org/x/sync v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect @@ -144,4 +144,4 @@ require ( // Used for local dev // replace github.com/signadot/libconnect => ../libconnect/ -//replace github.com/signadot/go-sdk => ../go-sdk +replace github.com/signadot/go-sdk => ../go-sdk diff --git a/go.sum b/go.sum index 72fa1f2..2d6baa4 100644 --- a/go.sum +++ b/go.sum @@ -527,8 +527,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/config/api.go b/internal/config/api.go index 0384fac..cfa0546 100644 --- a/internal/config/api.go +++ b/internal/config/api.go @@ -4,14 +4,12 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "time" "github.com/signadot/cli/internal/auth" "github.com/signadot/cli/internal/buildinfo" "github.com/signadot/go-sdk/client" sdkauth "github.com/signadot/go-sdk/client/auth" - "github.com/signadot/go-sdk/models" "github.com/signadot/go-sdk/transport" "github.com/spf13/viper" "gopkg.in/yaml.v2" @@ -129,9 +127,7 @@ func (a *API) refreshKeyringAuth(authInfo *auth.ResolvedAuth) (*auth.ResolvedAut } params := &sdkauth.AuthDeviceRefreshTokenParams{ - Data: &models.AuthdevicesRefreshTokenInput{ - RefreshToken: authInfo.RefreshToken, - }, + Data: authInfo.RefreshToken, } resp, err := a.Client.Auth.AuthDeviceRefreshToken(params)