Skip to content

Commit

Permalink
proposal: app engine support
Browse files Browse the repository at this point in the history
App Engine unfortunately can't make outbound requests without using
the appengine urlfetch library and a request-specific context.

I don't see a way around this other than to change the signature of
the Provider and Session interfaces.

This really has a huge amount of downsides obviously, but before I
go and fix stuff for providers besides Facebook, G+, and Twitter
(Twitter support requires mrjones/oauth#60
to get merged), I figured I'd open a pull request and discuss the
tradeoffs.

Unfortunately I think we need to do something like this to support
App Engine. Perhaps this is an opportunity to version goth using
gopkg.in or something?
  • Loading branch information
jtolio committed Dec 30, 2016
1 parent a185bfa commit 7b9fe7a
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 46 deletions.
15 changes: 15 additions & 0 deletions client.go
@@ -0,0 +1,15 @@
// +build !appengine

package goth

import (
"net/http"

"golang.org/x/net/context"
)

// Provider implementations should use this method for making outbound HTTP
// requests.
var HTTPClient = func(ctx context.Context) (*http.Client, error) {
return http.DefaultClient, nil
}
16 changes: 16 additions & 0 deletions client_appengine.go
@@ -0,0 +1,16 @@
// +build appengine

package goth

import (
"net/http"

"golang.org/x/net/context"
"google.golang.org/appengine/urlfetch"
)

// Provider implementations should use this method for making outbound HTTP
// requests.
var HTTPClient = func(ctx context.Context) (*http.Client, error) {
return urlfetch.Client(ctx), nil
}
28 changes: 22 additions & 6 deletions provider.go
@@ -1,18 +1,34 @@
package goth

import "fmt"
import "golang.org/x/oauth2"
import (
"fmt"

"golang.org/x/net/context"
"golang.org/x/oauth2"
)

// Provider needs to be implemented for each 3rd party authentication provider
// e.g. Facebook, Twitter, etc...
type Provider interface {
// When implementing a provider, these methods should not make outbound
// requests.
Name() string
BeginAuth(state string) (Session, error)
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
// Refresh token is provided by auth provider or not
RefreshTokenAvailable() bool

// These three methods are deprecated. See the appropriate *Ctx replacement.
BeginAuth(state string) (Session, error)
FetchUser(Session) (User, error)
RefreshToken(refreshToken string) (*oauth2.Token, error)

// These methods are now preferred.
BeginAuthCtx(ctx context.Context, state string) (Session, error)
FetchUserCtx(context.Context, Session) (User, error)
// Get new access token based on the refresh token.
// Only works if RefreshTokenAvailable() is true
RefreshTokenCtx(ctx context.Context, refreshToken string) (*oauth2.Token, error)
}

// Providers is list of known/available providers.
Expand Down
30 changes: 24 additions & 6 deletions providers/facebook/facebook.go
Expand Up @@ -8,10 +8,10 @@ import (
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"

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

Expand Down Expand Up @@ -50,25 +50,39 @@ func (p *Provider) Name() string {
// Debug is a no-op for the facebook package.
func (p *Provider) Debug(debug bool) {}

// BeginAuth asks Facebook for an authentication end-point.
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
return p.BeginAuthCtx(context.TODO(), state)
}

// BeginAuthCtx asks Facebook for an authentication end-point.
func (p *Provider) BeginAuthCtx(ctx context.Context, state string) (goth.Session, error) {
url := p.config.AuthCodeURL(state)
session := &Session{
AuthURL: url,
}
return session, nil
}

// FetchUser will go to Facebook and access basic information about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
return p.FetchUserCtx(context.TODO(), session)
}

// FetchUserCtx will go to Facebook and access basic information about the user.
func (p *Provider) FetchUserCtx(ctx context.Context, session goth.Session) (
goth.User, error) {
sess := session.(*Session)
user := goth.User{
AccessToken: sess.AccessToken,
Provider: p.Name(),
ExpiresAt: sess.ExpiresAt,
}

response, err := http.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
client, err := goth.HTTPClient(ctx)
if err != nil {
return user, err
}

response, err := client.Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken))
if err != nil {
return user, err
}
Expand Down Expand Up @@ -152,12 +166,16 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config {
return c
}

//RefreshToken refresh token is not provided by facebook
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
return p.RefreshTokenCtx(context.TODO(), refreshToken)
}

// RefreshTokenCtx refresh token is not provided by facebook
func (p *Provider) RefreshTokenCtx(ctx context.Context, refreshToken string) (*oauth2.Token, error) {
return nil, errors.New("Refresh token is not provided by facebook")
}

//RefreshTokenAvailable refresh token is not provided by facebook
// RefreshTokenAvailable refresh token is not provided by facebook
func (p *Provider) RefreshTokenAvailable() bool {
return false
}
13 changes: 9 additions & 4 deletions providers/facebook/session.go
Expand Up @@ -3,10 +3,11 @@ package facebook
import (
"encoding/json"
"errors"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"strings"
"time"

"github.com/markbates/goth"
"golang.org/x/net/context"
)

// Session stores data during the auth process with Facebook.
Expand All @@ -24,10 +25,14 @@ func (s Session) GetAuthURL() (string, error) {
return s.AuthURL, nil
}

// Authorize the session with Facebook and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
return s.AuthorizeCtx(context.TODO(), provider, params)
}

// AuthorizeCtx the session with Facebook and return the access token to be stored for future use.
func (s *Session) AuthorizeCtx(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
token, err := p.config.Exchange(ctx, params.Get("code"))
if err != nil {
return "", err
}
Expand Down
39 changes: 30 additions & 9 deletions providers/gplus/gplus.go
Expand Up @@ -7,11 +7,11 @@ import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"

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

Expand Down Expand Up @@ -51,8 +51,12 @@ func (p *Provider) Name() string {
// Debug is a no-op for the gplus package.
func (p *Provider) Debug(debug bool) {}

// BeginAuth asks Google+ for an authentication end-point.
func (p *Provider) BeginAuth(state string) (goth.Session, error) {
return p.BeginAuthCtx(context.TODO(), state)
}

// BeginAuthCtx asks Google+ for an authentication end-point.
func (p *Provider) BeginAuthCtx(ctx context.Context, state string) (goth.Session, error) {
var opts []oauth2.AuthCodeOption
if p.prompt != nil {
opts = append(opts, p.prompt)
Expand All @@ -64,8 +68,14 @@ func (p *Provider) BeginAuth(state string) (goth.Session, error) {
return session, nil
}

// FetchUser will go to Google+ and access basic information about the user.
func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
func (p *Provider) FetchUser(session goth.Session) (
goth.User, error) {
return p.FetchUserCtx(context.TODO(), session)
}

// FetchUserCtx will go to Google+ and access basic information about the user.
func (p *Provider) FetchUserCtx(ctx context.Context, session goth.Session) (
goth.User, error) {
sess := session.(*Session)
user := goth.User{
AccessToken: sess.AccessToken,
Expand All @@ -74,7 +84,12 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
ExpiresAt: sess.ExpiresAt,
}

response, err := http.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
client, err := goth.HTTPClient(ctx)
if err != nil {
return user, err
}

response, err := client.Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
if err != nil {
return user, err
}
Expand Down Expand Up @@ -145,15 +160,21 @@ func newConfig(provider *Provider, scopes []string) *oauth2.Config {
return c
}

//RefreshTokenAvailable refresh token is provided by auth provider or not
// RefreshTokenAvailable refresh token is provided by auth provider or not
func (p *Provider) RefreshTokenAvailable() bool {
return true
}

//RefreshToken get new access token based on the refresh token
func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
func (p *Provider) RefreshToken(refreshToken string) (
*oauth2.Token, error) {
return p.RefreshTokenCtx(context.TODO(), refreshToken)
}

// RefreshTokenCtx get new access token based on the refresh token
func (p *Provider) RefreshTokenCtx(ctx context.Context, refreshToken string) (
*oauth2.Token, error) {
token := &oauth2.Token{RefreshToken: refreshToken}
ts := p.config.TokenSource(oauth2.NoContext, token)
ts := p.config.TokenSource(ctx, token)
newToken, err := ts.Token()
if err != nil {
return nil, err
Expand Down
13 changes: 9 additions & 4 deletions providers/gplus/session.go
Expand Up @@ -3,10 +3,11 @@ package gplus
import (
"encoding/json"
"errors"
"github.com/markbates/goth"
"golang.org/x/oauth2"
"strings"
"time"

"github.com/markbates/goth"
"golang.org/x/net/context"
)

// Session stores data during the auth process with Facebook.
Expand All @@ -25,10 +26,14 @@ func (s Session) GetAuthURL() (string, error) {
return s.AuthURL, nil
}

// Authorize the session with Google+ and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
return s.AuthorizeCtx(context.TODO(), provider, params)
}

// AuthorizeCtx the session with Google+ and return the access token to be stored for future use.
func (s *Session) AuthorizeCtx(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
token, err := p.config.Exchange(oauth2.NoContext, params.Get("code"))
token, err := p.config.Exchange(ctx, params.Get("code"))
if err != nil {
return "", err
}
Expand Down
13 changes: 10 additions & 3 deletions providers/twitter/session.go
Expand Up @@ -3,9 +3,11 @@ package twitter
import (
"encoding/json"
"errors"
"strings"

"github.com/markbates/goth"
"github.com/mrjones/oauth"
"strings"
"golang.org/x/net/context"
)

// Session stores data during the auth process with Twitter.
Expand All @@ -23,10 +25,15 @@ func (s Session) GetAuthURL() (string, error) {
return s.AuthURL, nil
}

// Authorize the session with Twitter and return the access token to be stored for future use.
func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) {
return s.AuthorizeCtx(context.TODO(), provider, params)
}

// AuthorizeCtx the session with Twitter and return the access token to be stored for future use.
func (s *Session) AuthorizeCtx(ctx context.Context, provider goth.Provider, params goth.Params) (string, error) {
p := provider.(*Provider)
accessToken, err := p.consumer.AuthorizeToken(s.RequestToken, params.Get("oauth_verifier"))
accessToken, err := p.consumer.AuthorizeTokenWithParamsCtx(
ctx, s.RequestToken, params.Get("oauth_verifier"), p.consumer.AdditionalParams)
if err != nil {
return "", err
}
Expand Down

0 comments on commit 7b9fe7a

Please sign in to comment.