From 41df0ae498b7abddf552704b8211ff7404883a03 Mon Sep 17 00:00:00 2001 From: Manoj Malik Date: Thu, 26 May 2022 13:40:08 +0530 Subject: [PATCH] Fixed the Oauth token expiring issue (#2) * 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> --- server/plugin.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/server/plugin.go b/server/plugin.go index 777bd1f9..81649dec 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -11,6 +11,7 @@ import ( "path/filepath" "strings" "sync" + "time" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" @@ -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. @@ -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 { @@ -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 } @@ -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 +}