forked from forgejo/forgejo
Oauth2 consumer (#679)
* initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
This commit is contained in:
parent
fd941db246
commit
01d957677f
76 changed files with 7275 additions and 137 deletions
219
vendor/github.com/markbates/goth/gothic/gothic.go
generated
vendored
Normal file
219
vendor/github.com/markbates/goth/gothic/gothic.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue