Skip to content

Commit

Permalink
Merge pull request #109 from inovex/fix-oauth-refresh-token-2
Browse files Browse the repository at this point in the history
fix: refresh token using tokensource
  • Loading branch information
MichaelEischer authored Jan 13, 2024
2 parents f1257cc + 64af53d commit 3dcf787
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 38 deletions.
2 changes: 1 addition & 1 deletion internal/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Configurable interface {
}

type OAuth2Adapter interface {
SetupOauth2(credentials auth.Credentials, storage auth.Storage, bindPort uint) error
SetupOauth2(ctx context.Context, credentials auth.Credentials, storage auth.Storage, bindPort uint) error
}

// ConfigReader provides an interface for adapters to load their own configuration map.
Expand Down
3 changes: 1 addition & 2 deletions internal/adapter/google/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type CalendarAPI struct {
storage auth.Storage
}

func (c *CalendarAPI) SetupOauth2(credentials auth.Credentials, storage auth.Storage, bindPort uint) error {
func (c *CalendarAPI) SetupOauth2(ctx context.Context, credentials auth.Credentials, storage auth.Storage, bindPort uint) error {
// Google Adapter does not need the tenantId
switch {
case credentials.Client.Id == "":
Expand Down Expand Up @@ -132,7 +132,6 @@ func (c *CalendarAPI) Initialize(ctx context.Context, config map[string]interfac
}

c.pageMaxResults = defaultPageMaxResults
// TODO: this does not seem right
c.gcalClient = &GCalClient{oauthClient: c.oAuthHandler.Configuration().Client(ctx, c.oAuthToken)}
err := c.gcalClient.InitGoogleCalendarClient(c.calendarID, c.logger)
if err != nil {
Expand Down
88 changes: 55 additions & 33 deletions internal/adapter/outlook_http/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package outlook_http
import (
"context"
"fmt"
"net/http"
"strings"
"time"

"github.com/charmbracelet/log"
Expand Down Expand Up @@ -43,7 +41,7 @@ type CalendarAPI struct {
storage auth.Storage
}

func (c *CalendarAPI) SetupOauth2(credentials auth.Credentials, storage auth.Storage, bindPort uint) error {
func (c *CalendarAPI) SetupOauth2(ctx context.Context, credentials auth.Credentials, storage auth.Storage, bindPort uint) error {
// Outlook Adapter does not need the clientKey
switch {
case credentials.Client.Id == "":
Expand All @@ -62,17 +60,20 @@ func (c *CalendarAPI) SetupOauth2(credentials auth.Credentials, storage auth.Sto
AuthStyle: oauth2.AuthStyleInParams,
}

oAuthListener, err := auth.NewOAuthHandler(oauth2.Config{
oAuthConfig := oauth2.Config{
ClientID: credentials.Client.Id,
Endpoint: endpoint,
Scopes: []string{"Calendars.ReadWrite", "offline_access"}, // You need to request offline_access in order to retrieve a refresh token
}, bindPort)
}

oAuthListener, err := auth.NewOAuthHandler(oAuthConfig, bindPort)
if err != nil {
return err
}

c.oAuthHandler = oAuthListener
c.storage = storage
c.oAuthConfig = &oAuthConfig

storedAuth, err := c.storage.ReadCalendarAuth(credentials.CalendarId)
if err != nil {
Expand All @@ -84,21 +85,61 @@ func (c *CalendarAPI) SetupOauth2(credentials auth.Credentials, storage auth.Sto
return err
}

// this only checks the expiry field, which is the expiration time of the access token which was granted
// even if the refresh token is still valid
// TODO: unfortunately, without this part - the token will get assigned below and this triggers a panic
// TODO: in the oauth2 package. I'm not aware of the culprit yet.
now := time.Now()
if now.After(expiry) {
c.logger.Info("saved credentials expired, we need to reauthenticate..")
c.authenticated = false
err := c.storage.RemoveCalendarAuth(c.calendarID)
c.logger.Debugf("expiry time of stored token: %s", expiry.String())
src := c.oAuthConfig.TokenSource(ctx, &oauth2.Token{
AccessToken: storedAuth.OAuth2.AccessToken,
RefreshToken: storedAuth.OAuth2.RefreshToken,
Expiry: expiry,
TokenType: storedAuth.OAuth2.TokenType,
})

// refresh tokens as the access token is expired
// there is a lot of confusion here and i still didn't understood all of the implications, but it works
// for more info: https://github.com/golang/oauth2/issues/84
newToken, err := src.Token()
if err != nil {
return fmt.Errorf("failed to remove authentication for calendar %s: %w", c.calendarID, err)
// most probably the refresh token is now also expired
c.logger.Info("saved credentials expired, we need to reauthenticate..", "error", err)
c.authenticated = false
err := c.storage.RemoveCalendarAuth(c.calendarID)
if err != nil {
return fmt.Errorf("failed to remove authentication for calendar %s: %w", c.calendarID, err)
}
return nil
}
// give our CalendarAPI the new token
c.oAuthToken = &oauth2.Token{
AccessToken: newToken.AccessToken,
RefreshToken: newToken.RefreshToken,
Expiry: newToken.Expiry,
TokenType: newToken.TokenType,
}

c.authenticated = true
c.logger.Debug("Refreshed oauth credentials using the refresh token")
c.logger.Debugf("expiry time of new token: %s", newToken.Expiry.String())

// save the updated token to disk for the next use
_, err = c.storage.WriteCalendarAuth(auth.CalendarAuth{
CalendarID: c.calendarID,
OAuth2: auth.OAuth2Object{
AccessToken: c.oAuthToken.AccessToken,
RefreshToken: c.oAuthToken.RefreshToken,
Expiry: c.oAuthToken.Expiry.Format(time.RFC3339),
TokenType: c.oAuthToken.TokenType,
},
})
if err != nil {
return err
}

c.logger.Debug("saved new token to disk")
return nil
}

// if the token isn't expired, load from disk and use it
c.oAuthToken = &oauth2.Token{
AccessToken: storedAuth.OAuth2.AccessToken,
RefreshToken: storedAuth.OAuth2.RefreshToken,
Expand All @@ -108,6 +149,7 @@ func (c *CalendarAPI) SetupOauth2(credentials auth.Credentials, storage auth.Sto

c.authenticated = true
c.logger.Info("using stored credentials")
c.logger.Debugf("expiry time of stored token: %s", expiry.String())
}

return nil
Expand Down Expand Up @@ -144,26 +186,6 @@ func (c *CalendarAPI) Initialize(ctx context.Context, config map[string]interfac

client := c.oAuthConfig.Client(ctx, c.oAuthToken)

resp, err := client.Get(baseUrl + "/me/calendars/" + c.calendarID)
if err != nil {
if strings.Contains(err.Error(), "token_expired") {
c.logger.Info("the refresh token expired, initiating reauthentication...")
err := c.storage.RemoveCalendarAuth(c.calendarID)
if err != nil {
return fmt.Errorf("failed to remove authentication for calendar %s: %w", c.calendarID, err)
}
c.authenticated = false
err = c.Initialize(ctx, config)
if err != nil {
return fmt.Errorf("couldn't reinitialize calendar after expired refresh token: %w", err)
}
return nil
}
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status code %d", resp.StatusCode)
}
c.outlookClient = &OutlookClient{Client: client, CalendarID: c.calendarID}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion internal/adapter/sink_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func NewSinkAdapterFromConfig(ctx context.Context, bindPort uint, config ConfigR
}

if c, ok := client.(OAuth2Adapter); ok {
if err := c.SetupOauth2(
if err := c.SetupOauth2(ctx,
auth.Credentials{
Client: auth.Client{
Id: config.Adapter().OAuth.ClientID,
Expand Down
2 changes: 1 addition & 1 deletion internal/adapter/source_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewSourceAdapterFromConfig(ctx context.Context, bindPort uint, config Confi
}

if c, ok := client.(OAuth2Adapter); ok {
if err := c.SetupOauth2(
if err := c.SetupOauth2(ctx,
auth.Credentials{
Client: auth.Client{
Id: config.Adapter().OAuth.ClientID,
Expand Down

0 comments on commit 3dcf787

Please sign in to comment.