Skip to content
Snippets Groups Projects
Commit bd1e6a57 authored by Niall Sheridan's avatar Niall Sheridan
Browse files

Add github oauth provider.

parent 5a57870d
Branches
Tags
No related merge requests found
package github
import (
"net/http"
"github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/config"
githubapi "github.com/google/go-github/github"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
)
const (
// revokeURL = "https://accounts.google.com/o/oauth2/revoke?token=%s"
name = "github"
)
// Config is an implementation of `auth.Provider` for authenticating using a
// Github account.
type Config struct {
config *oauth2.Config
organization string
}
// New creates a new Github provider from a configuration.
func New(c *config.Auth) auth.Provider {
return &Config{
config: &oauth2.Config{
ClientID: c.OauthClientID,
ClientSecret: c.OauthClientSecret,
RedirectURL: c.OauthCallbackURL,
Endpoint: github.Endpoint,
Scopes: []string{
string(githubapi.ScopeUser),
string(githubapi.ScopeReadOrg),
},
},
organization: c.ProviderOpts["organization"],
}
}
// A new oauth2 http client.
func (c *Config) newClient(token *oauth2.Token) *http.Client {
return c.config.Client(oauth2.NoContext, token)
}
// Name returns the name of the provider.
func (c *Config) Name() string {
return name
}
// Valid validates the oauth token.
func (c *Config) Valid(token *oauth2.Token) bool {
if !token.Valid() {
return false
}
if c.organization == "" {
return true
}
client := githubapi.NewClient(c.newClient(token))
member, _, err := client.Organizations.IsMember(c.organization, c.Username(token))
if err != nil {
return false
}
return member
}
// Revoke disables the access token.
func (c *Config) Revoke(token *oauth2.Token) error {
return nil
}
// StartSession retrieves an authentication endpoint from Github.
func (c *Config) StartSession(state string) *auth.Session {
return &auth.Session{
AuthURL: c.config.AuthCodeURL(state),
State: state,
}
}
// Exchange authorizes the session and returns an access token.
func (c *Config) Exchange(code string) (*oauth2.Token, error) {
return c.config.Exchange(oauth2.NoContext, code)
}
// Username retrieves the username portion of the user's email address.
func (c *Config) Username(token *oauth2.Token) string {
client := githubapi.NewClient(c.newClient(token))
u, _, err := client.Users.Get("")
if err != nil {
return ""
}
return *u.Login
}
package github
import (
"fmt"
"testing"
"github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/config"
"github.com/stretchr/testify/assert"
)
var (
oauthClientID = "id"
oauthClientSecret = "secret"
oauthCallbackURL = "url"
organization = "exampleorg"
)
func TestNew(t *testing.T) {
a := assert.New(t)
p := newGithub()
g := p.(*Config)
a.Equal(g.config.ClientID, oauthClientID)
a.Equal(g.config.ClientSecret, oauthClientSecret)
a.Equal(g.config.RedirectURL, oauthCallbackURL)
a.Equal(g.organization, organization)
}
func TestStartSession(t *testing.T) {
a := assert.New(t)
p := newGithub()
s := p.StartSession("test_state")
a.Equal(s.State, "test_state")
a.Contains(s.AuthURL, "github.com/login/oauth/authorize")
a.Contains(s.AuthURL, "state=test_state")
a.Contains(s.AuthURL, fmt.Sprintf("client_id=%s", oauthClientID))
}
func newGithub() auth.Provider {
c := &config.Auth{
OauthClientID: oauthClientID,
OauthClientSecret: oauthClientSecret,
OauthCallbackURL: oauthCallbackURL,
ProviderOpts: map[string]string{"organization": organization},
}
return New(c)
}
......@@ -21,6 +21,7 @@ import (
"github.com/gorilla/sessions"
"github.com/nsheridan/cashier/lib"
"github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/auth/github"
"github.com/nsheridan/cashier/server/auth/google"
"github.com/nsheridan/cashier/server/config"
"github.com/nsheridan/cashier/server/signer"
......@@ -51,7 +52,7 @@ func (a *appContext) getAuthCookie(r *http.Request) *oauth2.Token {
if err := json.Unmarshal(t.([]byte), &tok); err != nil {
return nil
}
if !a.authprovider.Valid(&tok) {
if !tok.Valid() {
return nil
}
return &tok
......@@ -136,6 +137,12 @@ func callbackHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int
if err := a.authsession.Authorize(a.authprovider, code); err != nil {
return http.StatusInternalServerError, err
}
// Github tokens don't have an expiry. Set one so that the session expires
// after a period.
if a.authsession.Token.Expiry.Unix() <= 0 {
a.authsession.Token.Expiry = time.Now().Add(1 * time.Hour)
}
fmt.Println(a.authsession.Token)
a.setAuthCookie(w, r, a.authsession.Token)
http.Redirect(w, r, "/", http.StatusFound)
return http.StatusFound, nil
......@@ -148,6 +155,9 @@ func rootHandler(a *appContext, w http.ResponseWriter, r *http.Request) (int, er
http.Redirect(w, r, "/auth/login", http.StatusSeeOther)
return http.StatusSeeOther, nil
}
if !a.authprovider.Valid(tok) {
return http.StatusUnauthorized, errors.New(http.StatusText(http.StatusUnauthorized))
}
j := jwt.New(jwt.SigningMethodHS256)
j.Claims["token"] = tok.AccessToken
j.Claims["exp"] = tok.Expiry.Unix()
......@@ -203,7 +213,17 @@ func main() {
if err != nil {
log.Fatal(err)
}
authprovider := google.New(&config.Auth)
var authprovider auth.Provider
switch config.Auth.Provider {
case "google":
authprovider = google.New(&config.Auth)
case "github":
authprovider = github.New(&config.Auth)
default:
log.Fatalln("Unknown provider %s", config.Auth.Provider)
}
ctx := &appContext{
cookiestore: sessions.NewCookieStore([]byte(config.Server.CookieSecret)),
authprovider: authprovider,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment