Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality to revoke tokens #150

Open
wants to merge 53 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
5d84577
Add functionality to revoke tokens
Feb 26, 2017
712df6d
Merge branch 'master' into revoke
May 19, 2017
72184ec
Add revoke functionality for twitch
Jun 16, 2017
2c2dc22
Add comment about unsupported revoke in amazon
Jun 16, 2017
d072d9c
Merge branch 'master' into revoke
Jun 16, 2017
305dac0
Add empty remote method for xero provider
Jun 16, 2017
a8f446e
Add dropbox revoke call
Jun 17, 2017
2342b9f
Ad revoke calls for slack
Jun 17, 2017
ddf5134
Add comment about missing instagram revoke support
Jul 28, 2017
96f2d3d
Add comment about missing lastfm revoke support
Jul 28, 2017
9e6300d
Add comment about missing bitbucket revoke support
Jul 28, 2017
f2b6526
Fix httpmock call in twitch test
Jul 28, 2017
b748448
Add revoke functionality for box provider
Jul 28, 2017
216cf58
Merge branch 'master' into revoke
Jul 28, 2017
ed94a9b
Merge master and update battlenet provider
Jul 28, 2017
86331bb
Add comment about missing deezer revoke functionality
Jul 28, 2017
457eb06
Add comment about missing battlenet revoke sup
Jul 28, 2017
5271ded
Fix typo
Jul 28, 2017
d46c790
Added support to get provider name from request URL's go context in f…
SuperSarkar Aug 22, 2017
6e6ce86
string conversion in p,ok style to prevent panic if value of returned…
SuperSarkar Aug 22, 2017
3a18c23
updated .travis.yml to include Go 1.7, 1.8 and tip
SuperSarkar Aug 22, 2017
acf4f54
Added support to get provider name from request URL's go context in f…
SuperSarkar Aug 22, 2017
65ebf2a
string conversion in p,ok style to prevent panic if value of returned…
SuperSarkar Aug 22, 2017
1eade6c
updated .travis.yml to include Go 1.7, 1.8 and tip
SuperSarkar Aug 22, 2017
a7cba59
added support to get provider name from session
SuperSarkar Aug 23, 2017
c130f2f
Merge pull request #173 from KunalSarkar/provider-name-from-session
markbates Aug 23, 2017
e714cee
Added Eve Online as an Auth Provider
Sep 2, 2017
b98c959
Forgot the Refresh token is available
Sep 2, 2017
162dadd
Updated Readme
Sep 3, 2017
f80c7b0
Add default random state nonce to prevent CSRF attacks in Gothic
andyhaskell Sep 10, 2017
1d9143d
Update test coverage for nonce state in Gothic
andyhaskell Sep 10, 2017
9885e53
fixed issue with gothic not handling multiple logins correctly
markbates Sep 21, 2017
6c3a31e
Merge branch 'master' of https://github.com/markbates/goth
markbates Sep 21, 2017
86ca064
Moved deferring Logout until it's determined there's actually a sessi…
terev Oct 8, 2017
3453e97
Add functionality to revoke tokens
Feb 26, 2017
bb53194
Add revoke functionality for twitch
Jun 16, 2017
d1e75ee
Add comment about unsupported revoke in amazon
Jun 16, 2017
12a91bd
Add empty remote method for xero provider
Jun 16, 2017
0914ed6
Add dropbox revoke call
Jun 17, 2017
8e2b8aa
Ad revoke calls for slack
Jun 17, 2017
0ecc93b
Add comment about missing instagram revoke support
Jul 28, 2017
e34427c
Add comment about missing lastfm revoke support
Jul 28, 2017
0acd6e8
Add comment about missing bitbucket revoke support
Jul 28, 2017
12363ea
Fix httpmock call in twitch test
Jul 28, 2017
995ee87
Add revoke functionality for box provider
Jul 28, 2017
c6bd5b8
Merge master and update battlenet provider
Jul 28, 2017
faa24a1
Add comment about missing deezer revoke functionality
Jul 28, 2017
836822a
Add comment about missing battlenet revoke sup
Jul 28, 2017
4d39b6a
Fix typo
Jul 28, 2017
a750a97
Update comments
neumayer Nov 10, 2017
2dfc4c6
Merge branch 'revoke' of github.com:neumayer/goth into revoke
neumayer Nov 10, 2017
3a19803
Add Revoke method to eveonline provider
neumayer Nov 10, 2017
5bf9b4e
Only call revoke in case there is a session
neumayer Nov 10, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ language: go
sudo: false

go:
- 1.5
- 1.6
- 1.7
- 1.8
- tip

matrix:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ $ go get github.com/markbates/goth
* Digital Ocean
* Discord
* Dropbox
* Eve Online
* Facebook
* Fitbit
* GitHub
Expand Down
9 changes: 8 additions & 1 deletion examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/markbates/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox"
"github.com/markbates/goth/providers/eveonline"
"github.com/markbates/goth/providers/facebook"
"github.com/markbates/goth/providers/fitbit"
"github.com/markbates/goth/providers/github"
Expand Down Expand Up @@ -92,6 +93,7 @@ func main() {
yammer.New(os.Getenv("YAMMER_KEY"), os.Getenv("YAMMER_SECRET"), "http://localhost:3000/auth/yammer/callback"),
onedrive.New(os.Getenv("ONEDRIVE_KEY"), os.Getenv("ONEDRIVE_SECRET"), "http://localhost:3000/auth/onedrive/callback"),
battlenet.New(os.Getenv("BATTLENET_KEY"), os.Getenv("BATTLENET_SECRET"), "http://localhost:3000/auth/battlenet/callback"),
eveonline.New(os.Getenv("EVEONLINE_KEY"), os.Getenv("EVEONLINE_SECRET"), "http://localhost:3000/auth/eveonline/callback"),

//Pointed localhost.com to http://localhost:3000/auth/yahoo/callback through proxy as yahoo
// does not allow to put custom ports in redirection uri
Expand Down Expand Up @@ -134,6 +136,7 @@ func main() {
m["digitalocean"] = "Digital Ocean"
m["discord"] = "Discord"
m["dropbox"] = "Dropbox"
m["eveonline"] = "Eve Online"
m["facebook"] = "Facebook"
m["fitbit"] = "Fitbit"
m["github"] = "Github"
Expand Down Expand Up @@ -185,7 +188,11 @@ func main() {
})

p.Get("/logout/{provider}", func(res http.ResponseWriter, req *http.Request) {
gothic.Logout(res, req)
err := gothic.Logout(res, req)
if err != nil {
fmt.Fprintln(res, "Error occured during logout: ", err)
}

res.Header().Set("Location", "/")
res.WriteHeader(http.StatusTemporaryRedirect)
})
Expand Down
67 changes: 64 additions & 3 deletions gothic/gothic.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ See https://github.com/markbates/goth/examples/main.go to see this in action.
package gothic

import (
"encoding/base64"
"errors"
"fmt"
"math/rand"
"net/http"
"net/url"
"os"
"time"

"github.com/gorilla/mux"
"github.com/gorilla/sessions"
Expand All @@ -28,6 +31,8 @@ var defaultStore sessions.Store

var keySet = false

var gothicRand *rand.Rand

func init() {
key := []byte(os.Getenv("SESSION_SECRET"))
keySet = len(key) != 0
Expand All @@ -36,6 +41,7 @@ func init() {
cookieStore.Options.HttpOnly = true
Store = cookieStore
defaultStore = Store
gothicRand = rand.New(rand.NewSource(time.Now().UnixNano()))
}

/*
Expand Down Expand Up @@ -69,8 +75,16 @@ var SetState = func(req *http.Request) string {
return state
}

return "state"

// If a state query param is not passed in, generate a random
// base64-encoded nonce so that the state on the auth URL
// is unguessable, preventing CSRF attacks, as described in
//
// https://auth0.com/docs/protocols/oauth2/oauth-state#keep-reading
nonceBytes := make([]byte, 64)
for i := 0; i < 64; i++ {
nonceBytes[i] = byte(gothicRand.Int63() % 256)
}
return base64.URLEncoding.EncodeToString(nonceBytes)
}

// GetState gets the state returned by the provider during the callback.
Expand Down Expand Up @@ -152,6 +166,8 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err
}

defer Logout(res, req)

sess, err := provider.UnmarshalSession(value)
if err != nil {
return goth.User{}, err
Expand Down Expand Up @@ -180,7 +196,8 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err
}

return provider.FetchUser(sess)
gu, err := provider.FetchUser(sess)
return gu, err
}

// validateState ensures that the state token param from the original
Expand Down Expand Up @@ -214,12 +231,37 @@ func Logout(res http.ResponseWriter, req *http.Request) error {
if err != nil {
return err
}

// do revoke call only if we have a session
if len(session.Values) != 0 {
provider, err := goth.GetProvider(providerName)
if err != nil {
return err
}

value, err := getFromSession(providerName, req)
if err != nil {
return err
}
sess, err := provider.UnmarshalSession(value)
if err != nil {
return err
}

err = session.Save(req, res)

err = provider.Revoke(sess)
if err != nil {
return errors.New("Could not revoke token")
}
}
session.Options.MaxAge = -1
session.Values = make(map[interface{}]interface{})
err = session.Save(req, res)
if err != nil {
return errors.New("Could not delete user session ")
}

return nil
}

Expand All @@ -231,6 +273,20 @@ func Logout(res http.ResponseWriter, req *http.Request) error {
var GetProviderName = getProviderName

func getProviderName(req *http.Request) (string, error) {

// get all the used providers
providers := goth.GetProviders()

// loop over the used providers, if we already have a valid session for any provider (ie. user is already logged-in with a provider), then return that provider name
for _, provider := range providers {
p := provider.Name()
session, _ := Store.Get(req, p+SessionName)
value := session.Values[p]
if _, ok := value.(string); ok {
return p, nil
}
}

// try to get it from the url param "provider"
if p := req.URL.Query().Get("provider"); p != "" {
return p, nil
Expand All @@ -246,6 +302,11 @@ func getProviderName(req *http.Request) (string, error) {
return p, nil
}

// try to get it from the go-context's value of "provider" key
if p, ok := req.Context().Value("provider").(string); ok {
return p, nil
}

// if not found then return an empty string with the corresponding error
return "", errors.New("you must select a provider")
}
Expand Down
48 changes: 43 additions & 5 deletions gothic/gothic_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gothic_test

import (
"fmt"
"html"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/gorilla/sessions"
Expand Down Expand Up @@ -44,9 +47,12 @@ func (p ProviderStore) Save(r *http.Request, w http.ResponseWriter, s *sessions.
return nil
}

var fauxProvider goth.Provider

func init() {
Store = NewProviderStore()
goth.UseProviders(&faux.Provider{})
fauxProvider = &faux.Provider{}
goth.UseProviders(fauxProvider)
}

func Test_BeginAuthHandler(t *testing.T) {
Expand All @@ -58,9 +64,24 @@ func Test_BeginAuthHandler(t *testing.T) {

BeginAuthHandler(res, req)

sess, err := Store.Get(req, "faux"+SessionName)
if err != nil {
t.Fatalf("error getting faux Gothic session: %v", err)
}
sessStr, ok := sess.Values["faux"].(string)
if !ok {
t.Fatalf("Gothic session not stored as marshalled string; was %T (value %v)",
sess.Values["faux"], sess.Values["faux"])
}
gothSession, err := fauxProvider.UnmarshalSession(sessStr)
if err != nil {
t.Fatalf("error unmarshalling faux Gothic session: %v", err)
}
au, _ := gothSession.GetAuthURL()

a.Equal(http.StatusTemporaryRedirect, res.Code)
a.Contains(res.Body.String(),
`<a href="http://example.com/auth?client_id=&amp;response_type=code&amp;state=state">Temporary Redirect</a>`)
fmt.Sprintf(`<a href="%s">Temporary Redirect</a>`, html.EscapeString(au)))
}

func Test_GetAuthURL(t *testing.T) {
Expand All @@ -70,11 +91,28 @@ func Test_GetAuthURL(t *testing.T) {
req, err := http.NewRequest("GET", "/auth?provider=faux", nil)
a.NoError(err)

url, err := GetAuthURL(res, req)

u, err := GetAuthURL(res, req)
a.NoError(err)

a.Equal("http://example.com/auth?client_id=&response_type=code&state=state", url)
// Check that we get the correct auth URL with a state parameter
parsed, err := url.Parse(u)
a.NoError(err)
a.Equal("http", parsed.Scheme)
a.Equal("example.com", parsed.Host)
q := parsed.Query()
a.Contains(q, "client_id")
a.Equal("code", q.Get("response_type"))
a.NotZero(q, "state")

// Check that if we run GetAuthURL on another request, that request's
// auth URL has a different state from the previous one.
req2, err := http.NewRequest("GET", "/auth?provider=faux", nil)
a.NoError(err)
url2, err := GetAuthURL(httptest.NewRecorder(), req2)
a.NoError(err)
parsed2, err := url.Parse(url2)
a.NoError(err)
a.NotEqual(parsed.Query().Get("state"), parsed2.Query().Get("state"))
}

func Test_CompleteUserAuth(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Provider interface {
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
Revoke(Session) error
}

const NoAuthUrlErrorMessage = "an AuthURL has not been set"
Expand Down
16 changes: 11 additions & 5 deletions providers/amazon/amazon.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
"net/http"
"net/url"

"fmt"

"github.com/markbates/goth"
"golang.org/x/oauth2"
"fmt"
)

const (
Expand All @@ -36,10 +37,10 @@ type Provider struct {
// create one manually.
func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
p := &Provider{
ClientKey: clientKey,
Secret: secret,
CallbackURL: callbackURL,
providerName: "amazon",
ClientKey: clientKey,
Secret: secret,
CallbackURL: callbackURL,
providerName: "amazon",
}
p.config = newConfig(p, scopes)
return p
Expand Down Expand Up @@ -165,3 +166,8 @@ func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
}
return newToken, err
}

// Revoke is not supported by the amazon oauth api
func (p *Provider) Revoke(session goth.Session) error {
return nil
}
17 changes: 11 additions & 6 deletions providers/auth0/auth0.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
"io"
"net/http"

"fmt"

"github.com/markbates/goth"
"golang.org/x/oauth2"
"fmt"
)

const (
Expand Down Expand Up @@ -44,11 +45,11 @@ type auth0UserResp struct {
// create one manually.
func New(clientKey, secret, callbackURL string, auth0Domain string, scopes ...string) *Provider {
p := &Provider{
ClientKey: clientKey,
Secret: secret,
CallbackURL: callbackURL,
Domain: auth0Domain,
providerName: "auth0",
ClientKey: clientKey,
Secret: secret,
CallbackURL: callbackURL,
Domain: auth0Domain,
providerName: "auth0",
}
p.config = newConfig(p, scopes)
return p
Expand Down Expand Up @@ -181,3 +182,7 @@ func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
}
return newToken, err
}

func (p *Provider) Revoke(session goth.Session) error {
return nil
}
5 changes: 5 additions & 0 deletions providers/battlenet/battlenet.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,8 @@ func (p *Provider) RefreshTokenAvailable() bool {
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
return nil, nil
}

// Revoke is not supported by the amazon oauth api
func (p *Provider) Revoke(session goth.Session) error {
return nil
}