Skip to content

Commit

Permalink
Fixed the Oauth token expiring issue (#2)
Browse files Browse the repository at this point in the history
* Fixed the Oauth token expiring issue
Added the logic for refreshing the token manually when there's only a minute left till expiration
Added a mutex lock for the code of fetching the token from database to handle concurrency issues

* Removed some useless code and fixed a comment

* Review fixes

* Update server/plugin.go

Co-authored-by: shivamjosh <85667960+shivamjosh@users.noreply.github.com>

Co-authored-by: shivamjosh <85667960+shivamjosh@users.noreply.github.com>
  • Loading branch information
2 people authored and mickmister committed Jun 8, 2022
1 parent 5f463d2 commit 41df0ae
Showing 1 changed file with 37 additions and 0 deletions.
37 changes: 37 additions & 0 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"strings"
"sync"
"time"

"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/plugin"
Expand Down Expand Up @@ -47,6 +48,7 @@ type Plugin struct {

// configurationLock synchronizes access to the configuration.
configurationLock sync.RWMutex
tokenLock sync.Mutex

// configuration is the active plugin configuration. Consult getConfiguration and
// setConfiguration for usage.
Expand Down Expand Up @@ -196,6 +198,8 @@ func (p *Plugin) getGitlabUserInfoByMattermostID(userID string) (*gitlab.UserInf

var userInfo gitlab.UserInfo

p.tokenLock.Lock()
defer p.tokenLock.Unlock()
if infoBytes, err := p.API.KVGet(userID + GitlabTokenKey); err != nil || infoBytes == nil {
return nil, &APIErrorResponse{ID: APIErrorIDNotConnected, Message: "Must connect user account to GitLab first.", StatusCode: http.StatusBadRequest}
} else if err := json.Unmarshal(infoBytes, &userInfo); err != nil {
Expand All @@ -209,6 +213,19 @@ func (p *Plugin) getGitlabUserInfoByMattermostID(userID string) (*gitlab.UserInf
}

userInfo.Token.AccessToken = unencryptedToken
newToken, err := p.checkAndRefreshToken(userInfo.Token)
if err != nil {
return nil, &APIErrorResponse{ID: "", Message: err.Error(), StatusCode: http.StatusInternalServerError}
}

if newToken != nil {
userInfo.Token = newToken
unencryptedToken = newToken.AccessToken // needed because the storeGitlabUserInfo method changes its value to an encrypted value
if err := p.storeGitlabUserInfo(&userInfo); err != nil {
return nil, &APIErrorResponse{ID: "", Message: fmt.Sprintf("Unable to store user info. Error: %s", err.Error()), StatusCode: http.StatusInternalServerError}
}
userInfo.Token.AccessToken = unencryptedToken
}

return &userInfo, nil
}
Expand Down Expand Up @@ -491,3 +508,23 @@ func (p *Plugin) HasGroupHook(user *gitlab.UserInfo, namespace string) (bool, er

return found, err
}

func (p *Plugin) checkAndRefreshToken(token *oauth2.Token) (*oauth2.Token, error) {
// If there is only one minute left for the token to expire, we are refreshing the token.
// The detailed reason for this can be found here: https://github.com/golang/oauth2/issues/84#issuecomment-831492464
// We don't want the token to expire between the time when we decide that the old token is valid
// and the time at which we create the request. We are handling that by not letting the token expire.
if time.Until(token.Expiry) <= 1*time.Minute {
conf := p.getOAuthConfig()
src := conf.TokenSource(context.Background(), token)
newToken, err := src.Token() // this actually goes and renews the tokens
if err != nil {
return nil, errors.Wrap(err, "unable to get the new refreshed token")
}
if newToken.AccessToken != token.AccessToken {
return newToken, nil
}
}

return nil, nil
}

0 comments on commit 41df0ae

Please sign in to comment.