Skip to content
This repository has been archived by the owner on May 29, 2018. It is now read-only.

Commit

Permalink
Merge pull request #2 from hiendv/add-oauth
Browse files Browse the repository at this point in the history
Add OAuth driver
  • Loading branch information
hiendv committed Nov 22, 2017
2 parents 24345d3 + c75c837 commit ab9ae3d
Show file tree
Hide file tree
Showing 169 changed files with 32,524 additions and 15 deletions.
32 changes: 31 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@
[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.1.0"

[[constraint]]
branch = "master"
name = "golang.org/x/oauth2"
35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

### Supported authentication drivers
- Password-based authentication
- OAuth2 (coming soon)
- OAuth2

### Installation
```bash
Expand All @@ -25,24 +25,47 @@ go get github.com/hiendv/gate
### Usage
Quick example to get a taste of Gate
```go

var auth gate.Auth
var user gate.User
var err error

// some codes go here
// some construction codes go here

// Login using password-based authentication & Issue the JWT
// Login using password-based authentication
user, err = auth.Login(map[string]string{"email": "email", "password": "password"})
if err != nil {
log.Fatal("oops")
}

// Login using OAuth
// Redirect users to the authentication code URL
url, err := auth.LoginURL("state")

// Receive the code and exchange it
user, err = auth.Login(map[string]string{"code": "received-code"})
if err != nil {
log.Fatal("oops")
}

// Issue the JWT for the user
jwt, err := auth.IssueJWT(user)
if err != nil {
log.Fatal("oops")
}

// Authenticate with a given JWT
// Send the JWT to the user and let them use it to authenticate
// Authenticate a user using JWT
user, err = auth.Authenticate("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiaWQiLCJ1c2VybmFtZSI6InVzZXJuYW1lIiwicm9sZXMiOlsicm9sZSJdfSwiZXhwIjoxNjA1MDUyODAwLCJqdGkiOiJjbGFpbXMtaWQiLCJpYXQiOjE2MDUwNDkyMDB9.b0gxC2uZRek-SPwHSqyLOoW_DjSYroSivLqJG96Zxl0")
if err != nil {
log.Fatal("oops")
}

err = auth.Authorize(user, "action", "object")
```

You may want to check these examples and tests:
- Password-based authentication [examples](https://godoc.org/github.com/hiendv/gate/password#pkg-examples) & [tests](password/password_test.go)
- Password-based authentication [examples](https://godoc.org/github.com/hiendv/gate/password#pkg-examples), [unit tests](password/password_test.go) & [integration tests](password/password_integration_test.go)
- OAuth2 authentication [examples](https://godoc.org/github.com/hiendv/gate/oauth#pkg-examples), [unit tests](oauth/oauth_test.go) & [integration tests](oauth/oauth_integration_test.go)

## Development & Testing
Please check the [Contributing Guidelines](https://github.com/hiendv/gate/blob/master/CONTRIBUTING.md).
Expand Down
1 change: 1 addition & 0 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Auth interface {
type UserService interface {
FindOneByID(string) (User, error)
FindOrCreateOneByEmail(string) (User, error)
FindOneByEmail(string) (User, error)
}

// RoleService is the contract which offers queries on the role entity
Expand Down
10 changes: 10 additions & 0 deletions internal/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package internal

import (
"net/http"
)

// HTTPClient is the interface for the common HTTP client
type HTTPClient interface {
Get(url string) (resp *http.Response, err error)
}
108 changes: 108 additions & 0 deletions internal/test/fixtures/oauth_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package fixtures

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"

"github.com/hiendv/gate"
"github.com/hiendv/gate/internal"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)

type contextKey string

const noResponseKey contextKey = "no-response"
const malformedResponseKey contextKey = "malformed-response"

// OAuthClient is the mocking HTTP client for OAuth driver
type OAuthClient struct {
ctx context.Context
token *oauth2.Token
responses map[string]gate.HasEmail
}

// Get makes a GET request with the given URL
func (client OAuthClient) Get(url string) (resp *http.Response, err error) {
if noResponse, ok := client.ctx.Value(noResponseKey).(bool); ok && noResponse {
return nil, nil
}

if client.token == nil || client.token.AccessToken == "" {
err = errors.New("invalid token")
return
}

if malformedResponse, ok := client.ctx.Value(malformedResponseKey).(bool); ok && malformedResponse {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString("malformed")),
}, nil
}

user := client.responses[client.token.AccessToken]

result, err := json.Marshal(user)
if err != nil {
return
}

return &http.Response{
Body: ioutil.NopCloser(bytes.NewBuffer(result)),
}, nil
}

// OAuthProvider is the mocking provider for OAuth driver
type OAuthProvider struct {
Responses map[string]gate.HasEmail
}

// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
func (config OAuthProvider) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
return ""
}

// Exchange converts an authorization code into a token
func (config OAuthProvider) Exchange(ctx context.Context, code string) (*oauth2.Token, error) {
if code == "" {
return nil, nil
}

token := &oauth2.Token{}
token.AccessToken = fmt.Sprintf("%s-token", code)

return token, nil
}

// Client returns an HTTP client using the provided token
func (config OAuthProvider) Client(ctx context.Context, token *oauth2.Token) internal.HTTPClient {
return OAuthClient{ctx, token, config.Responses}
}

// BadOAuthProvider is the mocking provider with no client for OAuth driver
type BadOAuthProvider struct {
NoClient bool
NoResponse bool
MalformedResponse bool
OAuthProvider
}

// Client returns an HTTP client using the provided token
func (config BadOAuthProvider) Client(ctx context.Context, token *oauth2.Token) internal.HTTPClient {
if config.NoClient {
return nil
}

if config.NoResponse {
ctx = context.WithValue(ctx, noResponseKey, true)
}

if config.MalformedResponse {
ctx = context.WithValue(ctx, malformedResponseKey, true)
}

return config.OAuthProvider.Client(ctx, token)
}
21 changes: 13 additions & 8 deletions internal/test/fixtures/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,18 @@ func (service MyUserService) FindOneByEmail(email string) (u gate.User, err erro
return
}

// FindOrCreateOneByEmail fetches the user with the given email or create a new one if the user doesn't exist
// CreateOneByEmail creates the user with the given email
func (service *MyUserService) CreateOneByEmail(email string) (u gate.User, err error) {
record := User{
ID: service.GenerateMyUserID(),
Email: email,
}
service.records = append(service.records, record)
u = record
return
}

// FindOrCreateOneByEmail fetches the user with the given email or creates a new one if the user doesn't exist
func (service *MyUserService) FindOrCreateOneByEmail(email string) (u gate.User, err error) {
u, err = service.FindOneByEmail(email)
if err == nil {
Expand All @@ -83,12 +94,6 @@ func (service *MyUserService) FindOrCreateOneByEmail(email string) (u gate.User,
return
}

err = nil
record := User{
ID: service.GenerateMyUserID(),
Email: email,
}
service.records = append(service.records, record)
u = record
u, err = service.CreateOneByEmail(email)
return
}
45 changes: 45 additions & 0 deletions oauth/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package oauth

import (
"github.com/hiendv/gate"
"golang.org/x/oauth2"
"golang.org/x/oauth2/facebook"
"golang.org/x/oauth2/google"
)

// Config is the configuration for OAuth authentication
type Config struct {
gate.Config
ClientID string
ClientSecret string
Scopes []string
Endpoint oauth2.Endpoint
RedirectURI string
UserAPI string
}

// NewGoogleConfig is the constructor for OAuth configuration using Google API
func NewGoogleConfig(base gate.Config, id, secret, redirectURI string) Config {
return Config{
base,
id,
secret,
[]string{"https://www.googleapis.com/auth/userinfo.email"},
google.Endpoint,
redirectURI,
"https://www.googleapis.com/oauth2/v3/userinfo",
}
}

// NewFacebookConfig is the constructor for OAuth configuration using Facebook API
func NewFacebookConfig(base gate.Config, id, secret, redirectURI string) Config {
return Config{
base,
id,
secret,
[]string{"email"},
facebook.Endpoint,
redirectURI,
"https://graph.facebook.com/v2.11/me?fields=id,name,email",
}
}
2 changes: 2 additions & 0 deletions oauth/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package oauth is the OAuth2 authentication driver for github.com/hiendv/gate. It uses client implementations, not OAuth servers e.g. Google, Facebook, etc.
package oauth

0 comments on commit ab9ae3d

Please sign in to comment.