Update markbates/goth library (#3533)

Signed-off-by: Lauris Bukšis-Haberkorns <lauris@nix.lv>
This commit is contained in:
Lauris BH 2018-02-19 07:10:51 +02:00 committed by GitHub
parent 6f751409b4
commit 7b297808ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 284 additions and 160 deletions

View file

@ -8,10 +8,18 @@ See https://github.com/markbates/goth/examples/main.go to see this in action.
package gothic
import (
"bytes"
"compress/gzip"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
@ -27,15 +35,21 @@ var defaultStore sessions.Store
var keySet = false
var gothicRand *rand.Rand
func init() {
key := []byte(os.Getenv("SESSION_SECRET"))
keySet = len(key) != 0
Store = sessions.NewCookieStore([]byte(key))
cookieStore := sessions.NewCookieStore([]byte(key))
cookieStore.Options.HttpOnly = true
Store = cookieStore
defaultStore = Store
gothicRand = rand.New(rand.NewSource(time.Now().UnixNano()))
}
/*
BeginAuthHandler is a convienence handler for starting the authentication process.
BeginAuthHandler is a convenience handler for starting the authentication process.
It expects to be able to get the name of the provider from the query parameters
as either "provider" or ":provider".
@ -65,8 +79,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.
@ -87,7 +109,6 @@ I would recommend using the BeginAuthHandler instead of doing all of these steps
yourself, but that's entirely up to you.
*/
func GetAuthURL(res http.ResponseWriter, req *http.Request) (string, error) {
if !keySet && defaultStore == Store {
fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.")
}
@ -130,7 +151,7 @@ as either "provider" or ":provider".
See https://github.com/markbates/goth/examples/main.go to see this in action.
*/
var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
defer Logout(res, req)
if !keySet && defaultStore == Store {
fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.")
}
@ -155,6 +176,11 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
return goth.User{}, err
}
err = validateState(req, sess)
if err != nil {
return goth.User{}, err
}
user, err := provider.FetchUser(sess)
if err == nil {
// user can be found with existing session data
@ -173,7 +199,43 @@ 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
// AuthURL matches the one included in the current (callback) request.
func validateState(req *http.Request, sess goth.Session) error {
rawAuthURL, err := sess.GetAuthURL()
if err != nil {
return err
}
authURL, err := url.Parse(rawAuthURL)
if err != nil {
return err
}
originalState := authURL.Query().Get("state")
if originalState != "" && (originalState != req.URL.Query().Get("state")) {
return errors.New("state token mismatch")
}
return nil
}
// Logout invalidates a user session.
func Logout(res http.ResponseWriter, req *http.Request) error {
session, err := Store.Get(req, SessionName)
if err != nil {
return err
}
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
}
// GetProviderName is a function used to get the name of a provider
@ -184,36 +246,96 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us
var GetProviderName = getProviderName
func getProviderName(req *http.Request) (string, error) {
provider := req.URL.Query().Get("provider")
if provider == "" {
if p, ok := mux.Vars(req)["provider"]; ok {
// 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
}
}
if provider == "" {
provider = req.URL.Query().Get(":provider")
// try to get it from the url param "provider"
if p := req.URL.Query().Get("provider"); p != "" {
return p, nil
}
if provider == "" {
return provider, errors.New("you must select a provider")
// try to get it from the url param ":provider"
if p := req.URL.Query().Get(":provider"); p != "" {
return p, nil
}
return provider, nil
// try to get it from the context's value of "provider" key
if p, ok := mux.Vars(req)["provider"]; ok {
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")
}
func storeInSession(key string, value string, req *http.Request, res http.ResponseWriter) error {
session, _ := Store.Get(req, key + SessionName)
session, _ := Store.Get(req, SessionName)
session.Values[key] = value
if err := updateSessionValue(session, key, value); err != nil {
return err
}
return session.Save(req, res)
}
func getFromSession(key string, req *http.Request) (string, error) {
session, _ := Store.Get(req, key + SessionName)
value := session.Values[key]
if value == nil {
session, _ := Store.Get(req, SessionName)
value, err := getSessionValue(session, key)
if err != nil {
return "", errors.New("could not find a matching session for this request")
}
return value.(string), nil
}
return value, nil
}
func getSessionValue(session *sessions.Session, key string) (string, error) {
value := session.Values[key]
if value == nil {
return "", fmt.Errorf("could not find a matching session for this request")
}
rdata := strings.NewReader(value.(string))
r, err := gzip.NewReader(rdata)
if err != nil {
return "", err
}
s, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
return string(s), nil
}
func updateSessionValue(session *sessions.Session, key, value string) error {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write([]byte(value)); err != nil {
return err
}
if err := gz.Flush(); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}
session.Values[key] = b.String()
return nil
}