From 218e428036e5a3d6e76fc618bd13e179597480e3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 19:01:25 +0000 Subject: [PATCH 1/2] feat(go-sdk): Automatically refresh expired authentication tokens The Looker Go SDK was not automatically refreshing expired authentication tokens, causing authentication to fail after one hour. This change modifies the AuthSession to proactively manage the token's lifecycle. An IsActive method was added to check the token's validity, and a Login method was added to refresh the token when needed. The Do method was updated to call Login before making any API requests, ensuring the token is always active. --- go/rtl/auth.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/go/rtl/auth.go b/go/rtl/auth.go index d993bec7f..7c924f59f 100644 --- a/go/rtl/auth.go +++ b/go/rtl/auth.go @@ -39,6 +39,29 @@ func (t *transportWithHeaders) RoundTrip(req *http.Request) (*http.Response, err type AuthSession struct { Config ApiSettings Client http.Client + token *oauth2.Token + source oauth2.TokenSource +} + +func (s *AuthSession) IsActive() bool { + if s.token == nil { + return false + } + leeway := time.Second * 10 + return s.token.Expiry.After(time.Now().Add(leeway)) +} + +func (s *AuthSession) Login() (*oauth2.Token, error) { + if s.IsActive() { + return s.token, nil + } + + token, err := s.source.Token() + if err != nil { + return nil, err + } + s.token = token + return token, nil } func NewAuthSession(config ApiSettings) *AuthSession { @@ -74,9 +97,11 @@ func NewAuthSessionWithTransport(config ApiSettings, transport http.RoundTripper &http.Client{Transport: appIdHeaderTransport}, ) + source := oauthConfig.TokenSource(ctx) + // Make use of oauth2 transport to handle token management oauthTransport := &oauth2.Transport{ - Source: oauthConfig.TokenSource(ctx), + Source: source, // Will set "x-looker-appid" Header on all other requests Base: appIdHeaderTransport, } @@ -84,11 +109,15 @@ func NewAuthSessionWithTransport(config ApiSettings, transport http.RoundTripper return &AuthSession{ Config: config, Client: http.Client{Transport: oauthTransport}, + source: source, } } func (s *AuthSession) Do(result interface{}, method, ver, path string, reqPars map[string]interface{}, body interface{}, options *ApiSettings) error { - + _, err := s.Login() + if err != nil { + return err + } // prepare URL u := fmt.Sprintf("%s/api%s%s", s.Config.BaseUrl, ver, path) From 1d79b6088efd0b10d384c35c9874e29f269bafae Mon Sep 17 00:00:00 2001 From: Mike DeAngelo Date: Tue, 2 Dec 2025 19:21:26 +0000 Subject: [PATCH 2/2] fix: add mutex to handle concurrency in updating token --- go/rtl/auth.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/go/rtl/auth.go b/go/rtl/auth.go index 7c924f59f..ce7810fda 100644 --- a/go/rtl/auth.go +++ b/go/rtl/auth.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "reflect" + "sync" "time" "golang.org/x/oauth2" @@ -41,18 +42,36 @@ type AuthSession struct { Client http.Client token *oauth2.Token source oauth2.TokenSource + mu sync.RWMutex } +const tokenLeeway = 10 * time.Second + func (s *AuthSession) IsActive() bool { + s.mu.RLock() + defer s.mu.RUnlock() if s.token == nil { return false } - leeway := time.Second * 10 - return s.token.Expiry.After(time.Now().Add(leeway)) + return s.token.Expiry.After(time.Now().Add(tokenLeeway)) } func (s *AuthSession) Login() (*oauth2.Token, error) { - if s.IsActive() { + // First, check with a read lock to avoid contention if the token is valid. + s.mu.RLock() + if s.token != nil && s.token.Expiry.After(time.Now().Add(tokenLeeway)) { + token := s.token + s.mu.RUnlock() + return token, nil + } + s.mu.RUnlock() + + // If the token is invalid or nil, acquire a write lock to refresh it. + s.mu.Lock() + defer s.mu.Unlock() + + // Re-check after obtaining the write lock, in case another goroutine refreshed it. + if s.token != nil && s.token.Expiry.After(time.Now().Add(tokenLeeway)) { return s.token, nil }