You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							219 lines
						
					
					
						
							5.9 KiB
						
					
					
				
			
		
		
	
	
							219 lines
						
					
					
						
							5.9 KiB
						
					
					
				| /*
 | |
| Package gothic wraps common behaviour when using Goth. This makes it quick, and easy, to get up
 | |
| and running with Goth. Of course, if you want complete control over how things flow, in regards
 | |
| to the authentication process, feel free and use Goth directly.
 | |
| 
 | |
| See https://github.com/markbates/goth/examples/main.go to see this in action.
 | |
| */
 | |
| package gothic
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 
 | |
| 	"github.com/gorilla/mux"
 | |
| 	"github.com/gorilla/sessions"
 | |
| 	"github.com/markbates/goth"
 | |
| )
 | |
| 
 | |
| // SessionName is the key used to access the session store.
 | |
| const SessionName = "_gothic_session"
 | |
| 
 | |
| // Store can/should be set by applications using gothic. The default is a cookie store.
 | |
| var Store sessions.Store
 | |
| var defaultStore sessions.Store
 | |
| 
 | |
| var keySet = false
 | |
| 
 | |
| func init() {
 | |
| 	key := []byte(os.Getenv("SESSION_SECRET"))
 | |
| 	keySet = len(key) != 0
 | |
| 	Store = sessions.NewCookieStore([]byte(key))
 | |
| 	defaultStore = Store
 | |
| }
 | |
| 
 | |
| /*
 | |
| BeginAuthHandler is a convienence 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".
 | |
| 
 | |
| BeginAuthHandler will redirect the user to the appropriate authentication end-point
 | |
| for the requested provider.
 | |
| 
 | |
| See https://github.com/markbates/goth/examples/main.go to see this in action.
 | |
| */
 | |
| func BeginAuthHandler(res http.ResponseWriter, req *http.Request) {
 | |
| 	url, err := GetAuthURL(res, req)
 | |
| 	if err != nil {
 | |
| 		res.WriteHeader(http.StatusBadRequest)
 | |
| 		fmt.Fprintln(res, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	http.Redirect(res, req, url, http.StatusTemporaryRedirect)
 | |
| }
 | |
| 
 | |
| // SetState sets the state string associated with the given request.
 | |
| // If no state string is associated with the request, one will be generated.
 | |
| // This state is sent to the provider and can be retrieved during the
 | |
| // callback.
 | |
| var SetState = func(req *http.Request) string {
 | |
| 	state := req.URL.Query().Get("state")
 | |
| 	if len(state) > 0 {
 | |
| 		return state
 | |
| 	}
 | |
| 
 | |
| 	return "state"
 | |
| 
 | |
| }
 | |
| 
 | |
| // GetState gets the state returned by the provider during the callback.
 | |
| // This is used to prevent CSRF attacks, see
 | |
| // http://tools.ietf.org/html/rfc6749#section-10.12
 | |
| var GetState = func(req *http.Request) string {
 | |
| 	return req.URL.Query().Get("state")
 | |
| }
 | |
| 
 | |
| /*
 | |
| GetAuthURL starts the authentication process with the requested provided.
 | |
| It will return a URL that should be used to send users to.
 | |
| 
 | |
| It expects to be able to get the name of the provider from the query parameters
 | |
| as either "provider" or ":provider".
 | |
| 
 | |
| 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.")
 | |
| 	}
 | |
| 
 | |
| 	providerName, err := GetProviderName(req)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	provider, err := goth.GetProvider(providerName)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	sess, err := provider.BeginAuth(SetState(req))
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	url, err := sess.GetAuthURL()
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	err = storeInSession(providerName, sess.Marshal(), req, res)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	return url, err
 | |
| }
 | |
| 
 | |
| /*
 | |
| CompleteUserAuth does what it says on the tin. It completes the authentication
 | |
| process and fetches all of the basic information about the user from the provider.
 | |
| 
 | |
| It expects to be able to get the name of the provider from the query parameters
 | |
| 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) {
 | |
| 
 | |
| 	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.")
 | |
| 	}
 | |
| 
 | |
| 	providerName, err := GetProviderName(req)
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	provider, err := goth.GetProvider(providerName)
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	value, err := getFromSession(providerName, req)
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	sess, err := provider.UnmarshalSession(value)
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	user, err := provider.FetchUser(sess)
 | |
| 	if err == nil {
 | |
| 		// user can be found with existing session data
 | |
| 		return user, err
 | |
| 	}
 | |
| 
 | |
| 	// get new token and retry fetch
 | |
| 	_, err = sess.Authorize(provider, req.URL.Query())
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	err = storeInSession(providerName, sess.Marshal(), req, res)
 | |
| 
 | |
| 	if err != nil {
 | |
| 		return goth.User{}, err
 | |
| 	}
 | |
| 
 | |
| 	return provider.FetchUser(sess)
 | |
| }
 | |
| 
 | |
| // GetProviderName is a function used to get the name of a provider
 | |
| // for a given request. By default, this provider is fetched from
 | |
| // the URL query string. If you provide it in a different way,
 | |
| // assign your own function to this variable that returns the provider
 | |
| // name for your request.
 | |
| 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 {
 | |
| 			return p, nil
 | |
| 		}
 | |
| 	}
 | |
| 	if provider == "" {
 | |
| 		provider = req.URL.Query().Get(":provider")
 | |
| 	}
 | |
| 	if provider == "" {
 | |
| 		return provider, errors.New("you must select a provider")
 | |
| 	}
 | |
| 	return provider, nil
 | |
| }
 | |
| 
 | |
| func storeInSession(key string, value string, req *http.Request, res http.ResponseWriter) error {
 | |
| 	session, _ := Store.Get(req, key + SessionName)
 | |
| 
 | |
| 	session.Values[key] = value
 | |
| 
 | |
| 	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 {
 | |
| 		return "", errors.New("could not find a matching session for this request")
 | |
| 	}
 | |
| 
 | |
| 	return value.(string), nil
 | |
| } |