From d79ed6fc21aa5f76c92e2ebac0eb690199b2aff2 Mon Sep 17 00:00:00 2001 From: Rakesh Goyal Date: Thu, 10 Mar 2016 13:16:21 +0530 Subject: [PATCH] Added Refresh Token/Expiry Date of Access Token for applicable providers --- examples/main.go | 2 ++ provider.go | 3 ++ providers/amazon/amazon.go | 18 +++++++++++ providers/amazon/session.go | 14 +++++---- providers/amazon/session_test.go | 2 +- providers/bitbucket/bitbucket.go | 30 ++++++++++++++---- providers/bitbucket/session.go | 12 ++++--- providers/bitbucket/session_test.go | 2 +- providers/box/box.go | 18 +++++++++++ providers/box/session.go | 12 ++++--- providers/box/session_test.go | 2 +- providers/digitalocean/digitalocean.go | 33 +++++++++++++++----- providers/digitalocean/session.go | 12 ++++--- providers/digitalocean/session_test.go | 2 +- providers/dropbox/dropbox.go | 23 +++++++++----- providers/facebook/facebook.go | 43 ++++++++++++++++---------- providers/facebook/session.go | 4 ++- providers/facebook/session_test.go | 2 +- providers/faux/faux.go | 12 ++++++- providers/github/github.go | 12 ++++++- providers/gplus/gplus.go | 26 ++++++++++++++-- providers/gplus/session.go | 10 ++++-- providers/gplus/session_test.go | 2 +- providers/instagram/instagram.go | 13 ++++++-- providers/lastfm/lastfm.go | 12 ++++++- providers/linkedin/linkedin.go | 20 +++++++++--- providers/linkedin/session.go | 4 ++- providers/linkedin/session_test.go | 2 +- providers/onedrive/onedrive.go | 27 ++++++++++++---- providers/onedrive/session.go | 11 ++++--- providers/onedrive/session_test.go | 2 +- providers/salesforce/salesforce.go | 17 ++++++++++ providers/salesforce/session.go | 17 ++++++++-- providers/salesforce/session_test.go | 2 +- providers/spotify/session.go | 10 ++++-- providers/spotify/session_test.go | 2 +- providers/spotify/spotify.go | 19 +++++++++++- providers/twitch/session.go | 6 +++- providers/twitch/session_test.go | 2 +- providers/twitch/twitch.go | 19 +++++++++++- providers/twitter/twitter.go | 14 ++++++++- providers/yammer/yammer.go | 11 +++++++ user.go | 7 ++++- 43 files changed, 405 insertions(+), 108 deletions(-) diff --git a/examples/main.go b/examples/main.go index d406a3daf..197eb80a9 100644 --- a/examples/main.go +++ b/examples/main.go @@ -112,4 +112,6 @@ var userTemplate = `

Description: {{.Description}}

UserID: {{.UserID}}

AccessToken: {{.AccessToken}}

+

ExpiresIn: {{.ExpiresIn}}

+

RefreshToken: {{.RefreshToken}}

` diff --git a/provider.go b/provider.go index 219816d91..5d1ffaf52 100644 --- a/provider.go +++ b/provider.go @@ -1,6 +1,7 @@ package goth import "fmt" +import "golang.org/x/oauth2" // Provider needs to be implemented for each 3rd party authentication provider // e.g. Facebook, Twitter, etc... @@ -10,6 +11,8 @@ type Provider interface { UnmarshalSession(string) (Session, error) FetchUser(Session) (User, error) Debug(bool) + RefreshToken(refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token + RefreshTokenAvailable()(bool) //Refresh token is provided by auth provider or not } // Providers is list of known/available providers. diff --git a/providers/amazon/amazon.go b/providers/amazon/amazon.go index a2fc0a7a8..6264a9df6 100644 --- a/providers/amazon/amazon.go +++ b/providers/amazon/amazon.go @@ -63,6 +63,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: sess.AccessToken, Provider: p.Name(), + RefreshToken:sess.RefreshToken, + ExpiresIn:sess.ExpiresIn, } response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken)) @@ -134,4 +136,20 @@ func userFromReader(r io.Reader, user *goth.User) error { user.UserID = u.Id user.Location=u.Location return nil +} + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err } \ No newline at end of file diff --git a/providers/amazon/session.go b/providers/amazon/session.go index d58867bc2..c2f7ab298 100644 --- a/providers/amazon/session.go +++ b/providers/amazon/session.go @@ -3,16 +3,17 @@ package amazon import ( "encoding/json" "errors" - "golang.org/x/oauth2" - + "time" "github.com/markbates/goth" ) // Session stores data during the auth process with Box. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } var _ goth.Session = &Session{} @@ -25,15 +26,16 @@ func (s Session) GetAuthURL() (string, error) { return s.AuthURL, nil } -// Authorize the session with Box and return the access token to be stored for future use. +// Authorize the session with Amazon and return the access token to be stored for future use. func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { p := provider.(*Provider) token, err := p.config.Exchange(oauth2.NoContext, params.Get("code")) if err != nil { return "", err } - s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/amazon/session_test.go b/providers/amazon/session_test.go index 93c915fed..0202d7916 100644 --- a/providers/amazon/session_test.go +++ b/providers/amazon/session_test.go @@ -35,7 +35,7 @@ func Test_ToJSON(t *testing.T) { s := &amazon.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/bitbucket/bitbucket.go b/providers/bitbucket/bitbucket.go index b551ab792..2267776ae 100644 --- a/providers/bitbucket/bitbucket.go +++ b/providers/bitbucket/bitbucket.go @@ -65,6 +65,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: sess.AccessToken, Provider: p.Name(), + RefreshToken:sess.RefreshToken, + ExpiresIn:sess.ExpiresIn, } response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken)) @@ -115,12 +117,12 @@ func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { func userFromReader(reader io.Reader, user *goth.User) error { u := struct { - ID string `json:"uuid"` - Links struct { - Avatar struct { - URL string `json:"href"` - } `json:"avatar"` - } `json:"links"` + ID string `json:"uuid"` + Links struct { + Avatar struct { + URL string `json:"href"` + } `json:"avatar"` + } `json:"links"` Email string `json:"email"` Username string `json:"username"` Name string `json:"display_name"` @@ -178,3 +180,19 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { return c } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/providers/bitbucket/session.go b/providers/bitbucket/session.go index f4b28465f..cd91204ae 100644 --- a/providers/bitbucket/session.go +++ b/providers/bitbucket/session.go @@ -3,16 +3,17 @@ package bitbucket import ( "encoding/json" "errors" - + "time" "golang.org/x/oauth2" - "github.com/markbates/goth" ) // Session stores data during the auth process with Bitbucket. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Bitbucket provider. @@ -30,8 +31,9 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, if err != nil { return "", err } - s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/bitbucket/session_test.go b/providers/bitbucket/session_test.go index 605c97a78..528866349 100644 --- a/providers/bitbucket/session_test.go +++ b/providers/bitbucket/session_test.go @@ -36,7 +36,7 @@ func Test_ToJSON(t *testing.T) { s := &bitbucket.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/box/box.go b/providers/box/box.go index f92f39a63..d6cf400de 100644 --- a/providers/box/box.go +++ b/providers/box/box.go @@ -59,6 +59,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: s.AccessToken, Provider: p.Name(), + RefreshToken:s.RefreshToken, + ExpiresIn:s.ExpiresIn, } req, err := http.NewRequest("GET", endpointProfile, nil) if err != nil { @@ -124,4 +126,20 @@ func userFromReader(r io.Reader, user *goth.User) error { user.UserID = u.Id user.Location = u.Location return nil +} + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err } \ No newline at end of file diff --git a/providers/box/session.go b/providers/box/session.go index e439c6319..6c7d90fca 100644 --- a/providers/box/session.go +++ b/providers/box/session.go @@ -3,16 +3,17 @@ package box import ( "encoding/json" "errors" - + "time" "golang.org/x/oauth2" - "github.com/markbates/goth" ) // Session stores data during the auth process with Box. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } var _ goth.Session = &Session{} @@ -32,8 +33,9 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, if err != nil { return "", err } - s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/box/session_test.go b/providers/box/session_test.go index be906e85a..913a4ec01 100644 --- a/providers/box/session_test.go +++ b/providers/box/session_test.go @@ -35,7 +35,7 @@ func Test_ToJSON(t *testing.T) { s := &box.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/digitalocean/digitalocean.go b/providers/digitalocean/digitalocean.go index 04fc1d118..6000c345e 100644 --- a/providers/digitalocean/digitalocean.go +++ b/providers/digitalocean/digitalocean.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "net/http" "strings" - "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -65,6 +64,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: sess.AccessToken, Provider: p.Name(), + RefreshToken:sess.RefreshToken, + ExpiresIn:sess.ExpiresIn, } client := &http.Client{} @@ -108,13 +109,13 @@ func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { func userFromReader(reader io.Reader, user *goth.User) error { u := struct { Account struct { - DropletLimit int `json:"droplet_limit"` - Email string `json:"email"` - UUID string `json:"uuid"` - EmailVerified bool `json:"email_verified"` - Status string `json:"status"` - StatusMessage string `json:"status_message"` - } `json:"account"` + DropletLimit int `json:"droplet_limit"` + Email string `json:"email"` + UUID string `json:"uuid"` + EmailVerified bool `json:"email_verified"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + } `json:"account"` }{} err := json.NewDecoder(reader).Decode(&u) @@ -146,3 +147,19 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { return c } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/providers/digitalocean/session.go b/providers/digitalocean/session.go index a41ea086d..f93fa1827 100644 --- a/providers/digitalocean/session.go +++ b/providers/digitalocean/session.go @@ -3,16 +3,17 @@ package digitalocean import ( "encoding/json" "errors" - + "time" "golang.org/x/oauth2" - "github.com/markbates/goth" ) // Session stores data during the auth process with DigitalOcean. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } var _ goth.Session = &Session{} @@ -32,8 +33,9 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, if err != nil { return "", err } - s.AccessToken = token.AccessToken + s.RefreshToken=token.RefreshToken + s.ExpiresIn=token.Expiry return token.AccessToken, err } diff --git a/providers/digitalocean/session_test.go b/providers/digitalocean/session_test.go index e48fcf95b..b6a6ffd5a 100644 --- a/providers/digitalocean/session_test.go +++ b/providers/digitalocean/session_test.go @@ -27,7 +27,7 @@ func Test_ToJSON(t *testing.T) { s := &digitalocean.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/dropbox/dropbox.go b/providers/dropbox/dropbox.go index 83e4eecf4..899f5ea3c 100644 --- a/providers/dropbox/dropbox.go +++ b/providers/dropbox/dropbox.go @@ -7,14 +7,13 @@ import ( "io" "net/http" "strings" - "github.com/markbates/goth" "golang.org/x/oauth2" ) const ( - authURL = "https://www.dropbox.com/1/oauth2/authorize" - tokenURL = "https://api.dropbox.com/1/oauth2/token" + authURL = "https://www.dropbox.com/1/oauth2/authorize" + tokenURL = "https://api.dropbox.com/1/oauth2/token" accountURL = "https://api.dropbox.com/1/account/info" ) @@ -138,10 +137,10 @@ func userFromReader(r io.Reader, user *goth.User) error { u := struct { Name string `json:"display_name"` NameDetails struct { - NickName string `json:"familiar_name"` - } `json:"name_details"` - Location string `json:"country"` - Email string `json:"email"` + NickName string `json:"familiar_name"` + } `json:"name_details"` + Location string `json:"country"` + Email string `json:"email"` }{} err := json.NewDecoder(r).Decode(&u) if err != nil { @@ -154,3 +153,13 @@ func userFromReader(r io.Reader, user *goth.User) error { user.Location = u.Location return nil } + +//refresh token is not provided by dropbox +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by dropbox") +} + +//refresh token is not provided by dropbox +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} \ No newline at end of file diff --git a/providers/facebook/facebook.go b/providers/facebook/facebook.go index 05866f56e..80e474b33 100644 --- a/providers/facebook/facebook.go +++ b/providers/facebook/facebook.go @@ -10,9 +10,9 @@ import ( "net/http" "net/url" "strings" - "github.com/markbates/goth" "golang.org/x/oauth2" + "errors" ) const ( @@ -63,8 +63,9 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) { func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { sess := session.(*Session) user := goth.User{ - AccessToken: sess.AccessToken, - Provider: p.Name(), + AccessToken: sess.AccessToken, + Provider: p.Name(), + ExpiresIn: sess.ExpiresIn, } response, err := http.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken)) @@ -99,19 +100,19 @@ func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { func userFromReader(reader io.Reader, user *goth.User) error { u := struct { - ID string `json:"id"` - Email string `json:"email"` - Bio string `json:"bio"` - Name string `json:"name"` - Link string `json:"link"` - Picture struct { - Data struct { - URL string `json:"url"` - } `json:"data"` - } `json:"picture"` + ID string `json:"id"` + Email string `json:"email"` + Bio string `json:"bio"` + Name string `json:"name"` + Link string `json:"link"` + Picture struct { + Data struct { + URL string `json:"url"` + } `json:"data"` + } `json:"picture"` Location struct { - Name string `json:"name"` - } `json:"location"` + Name string `json:"name"` + } `json:"location"` }{} err := json.NewDecoder(reader).Decode(&u) @@ -144,7 +145,7 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { }, } - defaultScopes := map[string]struct{}{ + defaultScopes := map[string]struct {}{ "email": {}, } @@ -156,3 +157,13 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { return c } + +//refresh token is not provided by facebook +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by facebook") +} + +//refresh token is not provided by facebook +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} diff --git a/providers/facebook/session.go b/providers/facebook/session.go index f2dac6da6..b13031f1e 100644 --- a/providers/facebook/session.go +++ b/providers/facebook/session.go @@ -3,7 +3,7 @@ package facebook import ( "encoding/json" "errors" - + "time" "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -12,6 +12,7 @@ import ( type Session struct { AuthURL string AccessToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Facebook provider. @@ -30,6 +31,7 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", err } s.AccessToken = token.AccessToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/facebook/session_test.go b/providers/facebook/session_test.go index 87b3b8fbd..d77398552 100644 --- a/providers/facebook/session_test.go +++ b/providers/facebook/session_test.go @@ -36,7 +36,7 @@ func Test_ToJSON(t *testing.T) { s := &facebook.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/faux/faux.go b/providers/faux/faux.go index 53016f826..d05b2fe4e 100644 --- a/providers/faux/faux.go +++ b/providers/faux/faux.go @@ -5,7 +5,7 @@ package faux import ( "encoding/json" "strings" - + "golang.org/x/oauth2" "github.com/markbates/goth" ) @@ -53,6 +53,16 @@ func (p *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", nil } +//RefreshTokenAvailable is used only for testing +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//RefreshToken is used only for testing +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, nil +} + // Marshal is used only for testing. func (p *Session) Marshal() string { b, _ := json.Marshal(p) diff --git a/providers/github/github.go b/providers/github/github.go index 2b6299f9b..1e4fa47a3 100644 --- a/providers/github/github.go +++ b/providers/github/github.go @@ -11,7 +11,7 @@ import ( "net/url" "strconv" "strings" - + "errors" "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -142,3 +142,13 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { return c } + +//refresh token is not provided by github +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by github") +} + +//refresh token is not provided by github +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} diff --git a/providers/gplus/gplus.go b/providers/gplus/gplus.go index 086974d0b..1a1cb1351 100644 --- a/providers/gplus/gplus.go +++ b/providers/gplus/gplus.go @@ -16,7 +16,7 @@ import ( ) const ( - authURL string = "https://accounts.google.com/o/oauth2/auth" + authURL string = "https://accounts.google.com/o/oauth2/auth?access_type=offline" tokenURL string = "https://accounts.google.com/o/oauth2/token" endpointProfile string = "https://www.googleapis.com/oauth2/v2/userinfo" ) @@ -63,8 +63,10 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) { func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { sess := session.(*Session) user := goth.User{ - AccessToken: sess.AccessToken, - Provider: p.Name(), + AccessToken: sess.AccessToken, + Provider: p.Name(), + RefreshToken: sess.RefreshToken, + ExpiresIn: sess.ExpiresIn, } response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken)) @@ -143,3 +145,21 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { } return c } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} + + diff --git a/providers/gplus/session.go b/providers/gplus/session.go index 8e89d751b..a09335dca 100644 --- a/providers/gplus/session.go +++ b/providers/gplus/session.go @@ -3,15 +3,17 @@ package gplus import ( "encoding/json" "errors" - + "time" "github.com/markbates/goth" "golang.org/x/oauth2" ) // Session stores data during the auth process with Facebook. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Google+ provider. @@ -30,6 +32,8 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", err } s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/gplus/session_test.go b/providers/gplus/session_test.go index 346737e2a..107ca8a41 100644 --- a/providers/gplus/session_test.go +++ b/providers/gplus/session_test.go @@ -35,7 +35,7 @@ func Test_ToJSON(t *testing.T) { s := &Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/instagram/instagram.go b/providers/instagram/instagram.go index a5e61068a..af784a5b9 100644 --- a/providers/instagram/instagram.go +++ b/providers/instagram/instagram.go @@ -8,9 +8,8 @@ import ( "net/http" "net/url" "strings" - + "errors" "golang.org/x/oauth2" - "github.com/markbates/goth" ) @@ -145,3 +144,13 @@ func newConfig(p *Provider, scopes []string) *oauth2.Config { return c } + +//refresh token is not provided by instagram +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by instagram") +} + +//refresh token is not provided by instagram +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} diff --git a/providers/lastfm/lastfm.go b/providers/lastfm/lastfm.go index bba51c03a..572d70759 100644 --- a/providers/lastfm/lastfm.go +++ b/providers/lastfm/lastfm.go @@ -14,7 +14,7 @@ import ( "net/url" "sort" "strings" - + "golang.org/x/oauth2" "github.com/markbates/goth" ) @@ -212,3 +212,13 @@ func signRequest(secret string, params map[string]string) string { hasher.Write([]byte(sigPlain)) return hex.EncodeToString(hasher.Sum(nil)) } + +//refresh token is not provided by lastfm +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by lastfm") +} + +//refresh token is not provided by lastfm +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} diff --git a/providers/linkedin/linkedin.go b/providers/linkedin/linkedin.go index a373389b9..561f9b63b 100644 --- a/providers/linkedin/linkedin.go +++ b/providers/linkedin/linkedin.go @@ -5,11 +5,10 @@ import ( "encoding/json" "io" "net/http" - "net/url" - "github.com/markbates/goth" "golang.org/x/oauth2" + "errors" ) //more details about linkedin fields: https://developer.linkedin.com/documents/profile-fields @@ -18,7 +17,7 @@ const ( authURL string = "https://www.linkedin.com/uas/oauth2/authorization" tokenURL string = "https://www.linkedin.com/uas/oauth2/accessToken" - //userEndpoint requires scopes "r_basicprofile", "r_emailaddress" +//userEndpoint requires scopes "r_basicprofile", "r_emailaddress" userEndpoint string = "//api.linkedin.com/v1/people/~:(id,first-name,last-name,headline,location:(name),picture-url,email-address)" ) @@ -66,6 +65,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: s.AccessToken, Provider: p.Name(), + ExpiresIn: s.ExpiresIn, } req, err := http.NewRequest("GET", "", nil) @@ -112,8 +112,8 @@ func userFromReader(reader io.Reader, user *goth.User) error { Headline string `json:"headline"` PictureURL string `json:"pictureUrl"` Location struct { - Name string `json:"name"` - } `json:"location"` + Name string `json:"name"` + } `json:"location"` }{} err := json.NewDecoder(reader).Decode(&u) @@ -150,3 +150,13 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { return c } + +//refresh token is not provided by linkedin +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by linkedin") +} + +//refresh token is not provided by linkedin +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} diff --git a/providers/linkedin/session.go b/providers/linkedin/session.go index 17f0545d6..5278b6cbb 100644 --- a/providers/linkedin/session.go +++ b/providers/linkedin/session.go @@ -3,7 +3,7 @@ package linkedin import ( "encoding/json" "errors" - + "time" "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -12,6 +12,7 @@ import ( type Session struct { AuthURL string AccessToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Linkedin provider. @@ -30,6 +31,7 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", err } s.AccessToken = token.AccessToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/linkedin/session_test.go b/providers/linkedin/session_test.go index 70761c042..d1dcefd3b 100644 --- a/providers/linkedin/session_test.go +++ b/providers/linkedin/session_test.go @@ -36,7 +36,7 @@ func Test_ToJSON(t *testing.T) { s := &linkedin.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/onedrive/onedrive.go b/providers/onedrive/onedrive.go index cf77f7c10..718a41fd0 100644 --- a/providers/onedrive/onedrive.go +++ b/providers/onedrive/onedrive.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/markbates/goth" "golang.org/x/oauth2" - "log" ) const ( @@ -61,8 +60,10 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) { func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { sess := session.(*Session) user := goth.User{ - AccessToken: sess.AccessToken, - Provider: p.Name(), + AccessToken: sess.AccessToken, + Provider: p.Name(), + RefreshToken: sess.RefreshToken, + ExpiresIn: sess.ExpiresIn, } response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken)) @@ -83,9 +84,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { if err != nil { return user, err } - err = userFromReader(bytes.NewReader(bits), &user) - log.Println(user) return user, err } // UnmarshalSession wil unmarshal a JSON string into a session. @@ -112,7 +111,7 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config { c.Scopes = append(c.Scopes, scope) } }else { - c.Scopes = append(c.Scopes, "wl.signin", "wl.emails") + c.Scopes = append(c.Scopes, "wl.signin", "wl.emails", "wl.offline_access") } return c @@ -133,4 +132,20 @@ func userFromReader(r io.Reader, user *goth.User) error { user.UserID = u.Email["account"] //onedrive doesn't provide separate user_id return nil +} + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err } \ No newline at end of file diff --git a/providers/onedrive/session.go b/providers/onedrive/session.go index 898f7f100..9432c4f79 100644 --- a/providers/onedrive/session.go +++ b/providers/onedrive/session.go @@ -3,16 +3,17 @@ package onedrive import ( "encoding/json" "errors" - + "time" "golang.org/x/oauth2" - "github.com/markbates/goth" ) // Session stores data during the auth process with Box. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } var _ goth.Session = &Session{} @@ -34,6 +35,8 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, } s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/onedrive/session_test.go b/providers/onedrive/session_test.go index fa9358fc5..ea001dd50 100644 --- a/providers/onedrive/session_test.go +++ b/providers/onedrive/session_test.go @@ -35,7 +35,7 @@ func Test_ToJSON(t *testing.T) { s := &onedrive.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } func Test_String(t *testing.T) { diff --git a/providers/salesforce/salesforce.go b/providers/salesforce/salesforce.go index f52e254fb..619a7aa53 100644 --- a/providers/salesforce/salesforce.go +++ b/providers/salesforce/salesforce.go @@ -60,6 +60,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: s.AccessToken, Provider: p.Name(), + RefreshToken:s.RefreshToken, } url, err := url.Parse(s.Id) //creating dynamic url to retrieve user information @@ -130,3 +131,19 @@ func userFromReader(r io.Reader, user *goth.User) error { user.Location = u.Location return nil } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} \ No newline at end of file diff --git a/providers/salesforce/session.go b/providers/salesforce/session.go index 9297cf7d9..5eaa3d7e0 100644 --- a/providers/salesforce/session.go +++ b/providers/salesforce/session.go @@ -8,10 +8,20 @@ import ( ) // Session stores data during the auth process with Salesforce. +// Expiry of access token is not provided by Salesforce, it is just controlled by timeout configured in auth2 settings +// by individual users +// Only way to check whether access token has expired or not is based on the response you receive if you try using +// access token and get some error +// Also, For salesforce refresh token to work follow these else remove scopes from here +//On salesforce.com, navigate to where you app is configured. (Setup > Create > Apps) +//Under Connected Apps, click on your application's name to view its settings, then click Edit. +//Under Selected OAuth Scopes, ensure that "Perform requests on your behalf at any time" is selected. You must include this even if you already chose "Full access". +//Save, then try your OAuth flow again. It make take a short while for the update to propagate. type Session struct { - AuthURL string - AccessToken string - Id string //Required to get the user info from sales force + AuthURL string + AccessToken string + RefreshToken string + Id string //Required to get the user info from sales force } var _ goth.Session = &Session{} @@ -34,6 +44,7 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", err } s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken s.Id=token.Extra("id").(string) //Required to get the user info from sales force return token.AccessToken, err } diff --git a/providers/salesforce/session_test.go b/providers/salesforce/session_test.go index c74c9947f..fb5568143 100644 --- a/providers/salesforce/session_test.go +++ b/providers/salesforce/session_test.go @@ -36,7 +36,7 @@ func Test_ToJSON(t *testing.T) { s := &salesforce.Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":"","Id":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","Id":""}`) } func Test_String(t *testing.T) { diff --git a/providers/spotify/session.go b/providers/spotify/session.go index 9e70609f5..b1f898813 100644 --- a/providers/spotify/session.go +++ b/providers/spotify/session.go @@ -3,15 +3,17 @@ package spotify import ( "encoding/json" "errors" - + "time" "github.com/markbates/goth" "golang.org/x/oauth2" ) // Session stores data during the auth process with Spotify. type Session struct { - AuthURL string - AccessToken string + AuthURL string + AccessToken string + RefreshToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on the @@ -32,6 +34,8 @@ func (s Session) Authorize(provider goth.Provider, params goth.Params) (string, return "", err } s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/spotify/session_test.go b/providers/spotify/session_test.go index 74e5bd0d6..640f63e2e 100644 --- a/providers/spotify/session_test.go +++ b/providers/spotify/session_test.go @@ -33,5 +33,5 @@ func Test_ToJSON(t *testing.T) { s := &Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } diff --git a/providers/spotify/spotify.go b/providers/spotify/spotify.go index 5c508940d..57df5a267 100644 --- a/providers/spotify/spotify.go +++ b/providers/spotify/spotify.go @@ -6,7 +6,6 @@ import ( "encoding/json" "io" "net/http" - "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -91,6 +90,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: s.AccessToken, Provider: p.Name(), + RefreshToken:s.RefreshToken, + ExpiresIn: s.ExpiresIn, } req, err := http.NewRequest("GET", userEndpoint, nil) @@ -157,3 +158,19 @@ func newConfig(p *Provider, scopes []string) *oauth2.Config { } return c } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/providers/twitch/session.go b/providers/twitch/session.go index 7cb535e73..91465a0d0 100644 --- a/providers/twitch/session.go +++ b/providers/twitch/session.go @@ -3,7 +3,7 @@ package twitch import ( "encoding/json" "errors" - + "time" "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -12,6 +12,8 @@ import ( type Session struct { AuthURL string AccessToken string + RefreshToken string + ExpiresIn time.Time } // GetAuthURL will return the URL set by calling the `BeginAuth` function on @@ -33,6 +35,8 @@ func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, } s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresIn = token.Expiry return token.AccessToken, err } diff --git a/providers/twitch/session_test.go b/providers/twitch/session_test.go index 86f96d099..5c40371fd 100644 --- a/providers/twitch/session_test.go +++ b/providers/twitch/session_test.go @@ -34,5 +34,5 @@ func Test_ToJSON(t *testing.T) { s := &Session{} data := s.Marshal() - a.Equal(data, `{"AuthURL":"","AccessToken":""}`) + a.Equal(data, `{"AuthURL":"","AccessToken":"","RefreshToken":"","ExpiresIn":"0001-01-01T00:00:00Z"}`) } diff --git a/providers/twitch/twitch.go b/providers/twitch/twitch.go index 63cfb1d5c..4b2687418 100644 --- a/providers/twitch/twitch.go +++ b/providers/twitch/twitch.go @@ -8,7 +8,6 @@ import ( "net/http" "strconv" "strings" - "github.com/markbates/goth" "golang.org/x/oauth2" ) @@ -101,6 +100,8 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user := goth.User{ AccessToken: s.AccessToken, Provider: p.Name(), + RefreshToken:s.RefreshToken, + ExpiresIn:s.ExpiresIn, } req, err := http.NewRequest("GET", userEndpoint, nil) @@ -177,3 +178,19 @@ func newConfig(p *Provider, scopes []string) *oauth2.Config { return c } + +//Refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() (bool) { + return true +} + +//Get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken:refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/providers/twitter/twitter.go b/providers/twitter/twitter.go index e378f63ec..3befbf419 100644 --- a/providers/twitter/twitter.go +++ b/providers/twitter/twitter.go @@ -7,9 +7,10 @@ import ( "encoding/json" "io/ioutil" "strings" - + "golang.org/x/oauth2" "github.com/markbates/goth" "github.com/mrjones/oauth" + "errors" ) var ( @@ -130,3 +131,14 @@ func newConsumer(provider *Provider, authURL string) *oauth.Consumer { c.Debug(provider.debug) return c } + +//refresh token is not provided by twitter +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by twitter") +} + +//refresh token is not provided by twitter +func (p *Provider) RefreshTokenAvailable() (bool) { + return false +} + diff --git a/providers/yammer/yammer.go b/providers/yammer/yammer.go index e6e2fa3a9..70541eec2 100644 --- a/providers/yammer/yammer.go +++ b/providers/yammer/yammer.go @@ -8,6 +8,7 @@ import ( "github.com/markbates/goth" "golang.org/x/oauth2" "strconv" + "errors" ) const ( @@ -103,4 +104,14 @@ func stringValue(v interface{}) string { return "" } return v.(string) +} + +//refresh token is not provided by yammer +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by yammer") +} + +//refresh token is not provided by yammer +func (p *Provider) RefreshTokenAvailable() (bool) { + return false } \ No newline at end of file diff --git a/user.go b/user.go index 137c6cae4..501776c08 100644 --- a/user.go +++ b/user.go @@ -1,6 +1,9 @@ package goth -import "encoding/gob" +import ( + "encoding/gob" + "time" +) func init() { gob.Register(User{}) @@ -20,4 +23,6 @@ type User struct { Location string AccessToken string AccessTokenSecret string + RefreshToken string + ExpiresIn time.Time }