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

Netlify Provider #210

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion api/external.go
Expand Up @@ -10,10 +10,10 @@ import (
"time"

jwt "github.com/dgrijalva/jwt-go"
"github.com/gobuffalo/uuid"
"github.com/netlify/gotrue/api/provider"
"github.com/netlify/gotrue/models"
"github.com/netlify/gotrue/storage"
"github.com/gobuffalo/uuid"
"github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -173,10 +173,15 @@ func (a *API) internalExternalProviderCallback(w http.ResponseWriter, r *http.Re
}
}

if userData.AppMetadata != nil {
user.UpdateAppMetaData(tx, userData.AppMetadata)
}

token, terr = a.issueRefreshToken(ctx, tx, user)
if terr != nil {
return oauthError("server_error", terr.Error())
}

return nil
})
if err != nil {
Expand Down Expand Up @@ -276,6 +281,8 @@ func (a *API) Provider(ctx context.Context, name string) (provider.Provider, err
return provider.NewGoogleProvider(config.External.Google)
case "facebook":
return provider.NewFacebookProvider(config.External.Facebook)
case "netlify":
return provider.NewNetlifyProvider(config.External.Netlify)
case "saml":
return provider.NewSamlProvider(config.External.Saml, a.db, getInstanceID(ctx))
default:
Expand Down
52 changes: 52 additions & 0 deletions api/external_oauth.go
Expand Up @@ -2,10 +2,19 @@ package api

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"net/http"

"github.com/netlify/gotrue/api/provider"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)

// loadOAuthState parses the `state` query parameter as a JWS payload,
Expand Down Expand Up @@ -54,9 +63,52 @@ func (a *API) oAuthCallback(ctx context.Context, r *http.Request, providerType s
return nil, internalServerError("Error getting user email from external provider").WithInternalError(err)
}

config := a.getConfig(ctx)
if config.External.TokenEncryptionKey != "" {
cipher, err := encryptToken(config.External.TokenEncryptionKey, tok)
if err != nil {
log.WithError(err).Warn("Unable to encrypt oauth token for JWT payload")
} else {
if userData.AppMetadata == nil {
userData.AppMetadata = make(map[string]interface{})
}
key := fmt.Sprintf("%s_token", providerType)
userData.AppMetadata[key] = cipher
}
}

return userData, nil
}

func encryptToken(pemKey string, token *oauth2.Token) (string, error) {
// read pubkey
block, _ := pem.Decode([]byte(pemKey))
if block == nil || block.Type != "PUBLIC KEY" {
return "", errors.New("failed to decode PEM block containing public key")
}

parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", err
}

pubKey, ok := parsedKey.(*rsa.PublicKey)
if !ok {
return "", errors.New("Unable to parse RSA public key, generating a temp one")
}

secretMessage := []byte(token.AccessToken)
label := []byte("token")

ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, secretMessage, label)
if err != nil {
return "", err
}

base64Cipher := base64.StdEncoding.EncodeToString(ciphertext)
return base64Cipher, nil
}

func (a *API) OAuthProvider(ctx context.Context, name string) (provider.OAuthProvider, error) {
providerCandidate, err := a.Provider(ctx, name)
if err != nil {
Expand Down
69 changes: 69 additions & 0 deletions api/provider/netlify.go
@@ -0,0 +1,69 @@
package provider

import (
"context"
"errors"

"github.com/netlify/gotrue/conf"
"golang.org/x/oauth2"
)

var netlifyOauthEndpoint = oauth2.Endpoint{
AuthURL: "https://app.netlify.com/authorize",
TokenURL: "https://api.netlify.com/oauth/token",
}

const netlifyUserEndpoint = "https://api.netlify.com/api/v1/user"

type netlifyProvider struct {
*oauth2.Config
}

type netlifyUser struct {
AvatarURL string `json:"avatar_url,omitempty"`
Email string `json:"email,omitempty"`
FullName string `json:"full_name,omitempty"`
ID string `json:"id,omitempty"`
}

// NewNetlifyProvider provides a configured provider for logging in with Netlify
func NewNetlifyProvider(ext conf.OAuthProviderConfiguration) (OAuthProvider, error) {
if err := ext.Validate(); err != nil {
return nil, err
}

return &netlifyProvider{
&oauth2.Config{
ClientID: ext.ClientID,
ClientSecret: ext.Secret,
Endpoint: netlifyOauthEndpoint,
RedirectURL: ext.RedirectURI,
},
}, nil
}

func (n netlifyProvider) GetOAuthToken(code string) (*oauth2.Token, error) {
return n.Exchange(oauth2.NoContext, code)
}

func (n netlifyProvider) GetUserData(ctx context.Context, tok *oauth2.Token) (*UserProvidedData, error) {
var u netlifyUser
if err := makeRequest(ctx, tok, n.Config, netlifyUserEndpoint, &u); err != nil {
return nil, err
}

data := &UserProvidedData{
Email: u.Email,
Verified: true,
Metadata: map[string]string{
nameKey: u.FullName,
avatarURLKey: u.AvatarURL,
},
}

if data.Email == "" {
return nil, errors.New("Unable to find email with Netlify provider")
}

return data, nil
}
7 changes: 4 additions & 3 deletions api/provider/provider.go
Expand Up @@ -14,9 +14,10 @@ const (
)

type UserProvidedData struct {
Email string
Verified bool
Metadata map[string]string
Email string
Verified bool
Metadata map[string]string
AppMetadata map[string]interface{}
}

// Provider is an interface for interacting with external account providers
Expand Down
2 changes: 2 additions & 0 deletions api/settings.go
Expand Up @@ -8,6 +8,7 @@ type ProviderSettings struct {
GitLab bool `json:"gitlab"`
Google bool `json:"google"`
Facebook bool `json:"facebook"`
Netlify bool `json:"netlify"`
Email bool `json:"email"`
SAML bool `json:"saml"`
}
Expand All @@ -33,6 +34,7 @@ func (a *API) Settings(w http.ResponseWriter, r *http.Request) error {
GitLab: config.External.Gitlab.Enabled,
Google: config.External.Google.Enabled,
Facebook: config.External.Facebook.Enabled,
Netlify: config.External.Netlify.Enabled,
Email: !config.External.Email.Disabled,
SAML: config.External.Saml.Enabled,
},
Expand Down
3 changes: 3 additions & 0 deletions conf/configuration.go
Expand Up @@ -81,9 +81,12 @@ type ProviderConfiguration struct {
Gitlab OAuthProviderConfiguration `json:"gitlab"`
Google OAuthProviderConfiguration `json:"google"`
Facebook OAuthProviderConfiguration `json:"facebook"`
Netlify OAuthProviderConfiguration `json:"netlify"`
Email EmailProviderConfiguration `json:"email"`
Saml SamlProviderConfiguration `json:"saml"`
RedirectURL string `json:"redirect_url"`

TokenEncryptionKey string `json:"token_encryption_key" split_words:"true"`
}

type SMTPConfiguration struct {
Expand Down
36 changes: 8 additions & 28 deletions go.mod 100755 → 100644
Expand Up @@ -12,76 +12,56 @@ require (
github.com/bugsnag/bugsnag-go v1.3.0 // indirect
github.com/bugsnag/panicwrap v0.0.0-20170829152406-dd8df9a3778a // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dropbox/godropbox v0.0.0-20180512210157-31879d3884b9 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4 // indirect
github.com/go-chi/chi v3.1.4+incompatible
github.com/go-kit/kit v0.8.0 // indirect
github.com/go-logfmt/logfmt v0.4.0 // indirect
github.com/go-sql-driver/mysql v1.4.1
github.com/go-stack/stack v1.8.0 // indirect
github.com/gobuffalo/buffalo-plugins v1.9.4 // indirect
github.com/gobuffalo/gogen v0.1.1 // indirect
github.com/gobuffalo/nulls v0.0.0-20190305142546-85f3c9250d87 // indirect
github.com/gobuffalo/plushgen v0.0.0-20190104222512-177cd2b872b3 // indirect
github.com/gobuffalo/plush v3.7.32+incompatible // indirect
github.com/gobuffalo/pop v4.11.0+incompatible
github.com/gobuffalo/tags v2.0.15+incompatible // indirect
github.com/gobuffalo/uuid v2.0.5+incompatible
github.com/gobuffalo/x v0.0.0-20181110221217-14085ca3e1a9 // indirect
github.com/gogo/protobuf v0.0.0-20171109181519-616a82ed12d7 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-msgpack v0.0.0-20150518234257-fa3f63826f7c // indirect
github.com/hashicorp/raft v1.0.0 // indirect
github.com/imdario/mergo v0.0.0-20160216103600-3e95a51e0639
github.com/joho/godotenv v1.3.0
github.com/jonboulle/clockwork v0.0.0-20180716110948-e7c6d408fd5c // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect
github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
github.com/kelseyhightower/envconfig v1.3.0
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 // indirect
github.com/microcosm-cc/bluemonday v1.0.2 // indirect
github.com/nats-io/gnatsd v1.3.0 // indirect
github.com/nats-io/go-nats v1.3.0 // indirect
github.com/nats-io/go-nats-streaming v0.3.4 // indirect
github.com/nats-io/nats-streaming-server v0.11.2 // indirect
github.com/nats-io/nuid v0.0.0-20170303150224-3cf34f9fca4e // indirect
github.com/netlify/mailme v0.0.0-20170821082834-c4a76ce443c1
github.com/netlify/netlify-commons v0.7.7
github.com/netlify/netlify-commons v0.14.0
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
github.com/pelletier/go-buffruneio v0.1.0 // indirect
github.com/pkg/errors v0.8.1
github.com/pkg/sftp v0.0.0-20160908100035-8197a2e58073 // indirect
github.com/rs/cors v0.0.0-20170608165155-8dd4211afb5d
github.com/russellhaering/gosaml2 v0.3.1
github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35
github.com/shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d // indirect
github.com/signalfx/com_signalfx_metrics_protobuf v0.0.0-20170330202426-93e507b42f43 // indirect
github.com/signalfx/gohistogram v0.0.0-20160107210732-1ccfd2ff5083 // indirect
github.com/signalfx/golib v1.0.0 // indirect
github.com/sirupsen/logrus v1.4.1
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/spf13/afero v1.2.0 // indirect
github.com/spf13/cobra v0.0.3
github.com/streadway/amqp v0.0.0-20170707203015-2cbfe40c9341 // indirect
github.com/spf13/viper v1.3.1 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 // indirect
golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
golang.org/x/oauth2 v0.0.0-20170807180024-9a379c6b3e95
golang.org/x/sys v0.0.0-20190515190549-87c872767d25 // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190515235946-4f9510c6a12d // indirect
google.golang.org/api v0.0.0-20170821230356-dd6bdadc5852 // indirect
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/logfmt.v0 v0.3.0 // indirect
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/stack.v1 v1.6.0 // indirect
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 // indirect
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
)