Compare commits
2 commits
master
...
feature/lo
Author | SHA1 | Date | |
---|---|---|---|
|
9b3558f598 | ||
|
8807ea42bb |
8 changed files with 105 additions and 15 deletions
|
@ -24,10 +24,10 @@ func getBaseCookie(options *options.Options) *http.Cookie {
|
|||
}
|
||||
}
|
||||
|
||||
func (fw *ForwardAuth) MakeCSRFCookie(w http.ResponseWriter, r *http.Request, options *options.Options, state string) *http.Cookie {
|
||||
func (fw *ForwardAuth) MakeCSRFCookie(w http.ResponseWriter, r *http.Request, options *options.Options, redirect string, state string) *http.Cookie {
|
||||
cookie := getBaseCookie(options)
|
||||
cookie.Name = "__auth_csrf"
|
||||
cookie.Value = fmt.Sprintf("%s|%s", fw.GetReturnUri(r), state)
|
||||
cookie.Value = fmt.Sprintf("%s|%s", redirect, state)
|
||||
cookie.Expires = time.Now().Local().Add(time.Hour)
|
||||
|
||||
return cookie
|
||||
|
|
|
@ -19,6 +19,12 @@ type ForwardAuth struct {
|
|||
OidcProvider *oidc.Provider
|
||||
OAuth2Config oauth2.Config
|
||||
OidcVefifier *oidc.IDTokenVerifier
|
||||
|
||||
OidcProviderClaims *OidcProviderClaims
|
||||
}
|
||||
|
||||
type OidcProviderClaims struct {
|
||||
EndSessionURL string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
// Claims represents the claims struct which we get from the identity provider
|
||||
|
@ -49,8 +55,16 @@ func Create(ctx context.Context, options *options.Options) (*ForwardAuth, error)
|
|||
ClientID: options.ClientID,
|
||||
})
|
||||
|
||||
providerClaims := OidcProviderClaims{}
|
||||
if err = provider.Claims(&providerClaims); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ForwardAuth{
|
||||
OidcProvider: provider,
|
||||
OidcProvider: provider,
|
||||
OidcVefifier: verifier,
|
||||
OidcProviderClaims: &providerClaims,
|
||||
|
||||
OAuth2Config: oauth2.Config{
|
||||
ClientID: options.ClientID,
|
||||
ClientSecret: options.ClientSecret,
|
||||
|
@ -62,6 +76,5 @@ func Create(ctx context.Context, options *options.Options) (*ForwardAuth, error)
|
|||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
},
|
||||
OidcVefifier: verifier,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package forwardauth
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (fw *ForwardAuth) GetReturnUri(r *http.Request) string {
|
||||
|
@ -16,3 +17,19 @@ func (fw *ForwardAuth) GetReturnUri(r *http.Request) string {
|
|||
|
||||
return fmt.Sprintf("%s://%s%s", proto, host, path)
|
||||
}
|
||||
|
||||
func (fw *ForwardAuth) GetLogoutUri(redirectURL string, state string) string {
|
||||
logoutURL, err := url.Parse(fw.OidcProviderClaims.EndSessionURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
query := logoutURL.Query()
|
||||
if redirectURL != "" {
|
||||
query.Set("post_logout_redirect_uri", redirectURL)
|
||||
}
|
||||
if state != "" {
|
||||
query.Set("state", state)
|
||||
}
|
||||
logoutURL.RawQuery = query.Encode()
|
||||
return logoutURL.String()
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@ import (
|
|||
)
|
||||
|
||||
// CallbackHandler returns a handler function which handles the callback from oidc provider
|
||||
func (root *HttpHandler) callbackHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
|
||||
func (root *HttpHandler) authCallbackHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
||||
"Path": forwardedURI.Path,
|
||||
"Path": "/auth/resp",
|
||||
})
|
||||
|
||||
// check for the csrf cookie
|
|
@ -5,6 +5,7 @@ This code is licensed under MIT license (see LICENSE for details)
|
|||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
|
@ -13,11 +14,12 @@ import (
|
|||
)
|
||||
|
||||
// RootHandler returns a handler function which handles all requests to the root
|
||||
func (root *HttpHandler) rootHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
|
||||
func (root *HttpHandler) authRootHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
|
||||
redirect := fmt.Sprintf("%s://%s%s", r.Header.Get("X-Forwarded-Proto"), r.Header.Get("X-Forwarded-Host"), r.Header.Get("X-Forwarded-Uri"))
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
||||
"RequestTarget": root.forwardAuth.GetReturnUri(r),
|
||||
"Path": forwardedURI.Path,
|
||||
"RequestTarget": redirect,
|
||||
"Path": "/",
|
||||
})
|
||||
|
||||
claims, err := root.forwardAuth.IsAuthenticated(r.Context(), logger, w, r, root.options)
|
||||
|
@ -29,11 +31,11 @@ func (root *HttpHandler) rootHandler(w http.ResponseWriter, r *http.Request, for
|
|||
//http.SetCookie(w, root.forwardAuth.ClearRefreshAuthCookie(root.options))
|
||||
|
||||
state := uuid.New().String()
|
||||
http.SetCookie(w, root.forwardAuth.MakeCSRFCookie(w, r, root.options, state))
|
||||
http.SetCookie(w, root.forwardAuth.MakeCSRFCookie(w, r, root.options, redirect, state))
|
||||
http.Redirect(w, r, root.forwardAuth.OAuth2Config.AuthCodeURL(state), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Forwarded-User", claims.Email)
|
||||
w.WriteHeader(200)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
|
@ -5,6 +5,7 @@ This code is licensed under MIT license (see LICENSE for details)
|
|||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
|
@ -24,7 +25,7 @@ func Create(fw *forwardauth.ForwardAuth, options *options.Options) *HttpHandler
|
|||
}
|
||||
}
|
||||
|
||||
func (h *HttpHandler) Entrypoint() func(http.ResponseWriter, *http.Request) {
|
||||
func (root *HttpHandler) Entrypoint() func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
uri, err := url.Parse(r.Header.Get("X-Forwarded-Uri"))
|
||||
switch {
|
||||
|
@ -32,12 +33,19 @@ func (h *HttpHandler) Entrypoint() func(http.ResponseWriter, *http.Request) {
|
|||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
case uri.Path == h.options.RedirectURL:
|
||||
h.callbackHandler(w, r, uri)
|
||||
case uri.Path == root.options.RedirectURL:
|
||||
root.authCallbackHandler(w, r, uri)
|
||||
return
|
||||
|
||||
case uri.Path == root.options.LogoutUrl:
|
||||
root.logoutHandler(w, r, uri)
|
||||
return
|
||||
|
||||
case uri.Path == fmt.Sprintf("%s/resp", root.options.LogoutUrl):
|
||||
return
|
||||
|
||||
default:
|
||||
h.rootHandler(w, r, uri)
|
||||
root.authRootHandler(w, r, uri)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
49
pkg/httphandler/logout_root.go
Normal file
49
pkg/httphandler/logout_root.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright (c) 2020 Stefan Kürzeder <info@stivik.de>
|
||||
This code is licensed under MIT license (see LICENSE for details)
|
||||
*/
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// RootHandler returns a handler function which handles all requests to the root
|
||||
func (root *HttpHandler) logoutHandler(w http.ResponseWriter, r *http.Request, forwardedURI *url.URL) {
|
||||
logger := logrus.WithFields(logrus.Fields{
|
||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
||||
"RequestTarget": root.forwardAuth.GetReturnUri(r),
|
||||
"Path": root.options.LogoutUrl,
|
||||
})
|
||||
|
||||
// check for the csrf cookie
|
||||
state, redirect, err := root.forwardAuth.ValidateCSRFCookie(r)
|
||||
if err != nil {
|
||||
state := uuid.New().String()
|
||||
redirect := fmt.Sprintf("%s://%s", r.Header.Get("X-Forwarded-Proto"), r.Header.Get("X-Forwarded-Host"))
|
||||
|
||||
http.SetCookie(w, root.forwardAuth.MakeCSRFCookie(w, r, root.options, redirect, state))
|
||||
|
||||
responseURL := fmt.Sprintf("https://%s%s/resp", root.options.AuthDomain, root.options.LogoutUrl)
|
||||
http.Redirect(w, r, root.forwardAuth.GetLogoutUri(responseURL, state), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
// verify the state
|
||||
if forwardedURI.Query().Get("state") != state {
|
||||
http.Error(w, "state did not match", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Clear the auth information
|
||||
logger.Info("Destroying auth cookie.")
|
||||
http.SetCookie(w, root.forwardAuth.ClearAuthCookie(root.options))
|
||||
|
||||
// Redirect to the base
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
}
|
|
@ -18,6 +18,7 @@ type Options struct {
|
|||
CookieDomain string `env:"COOKIE_DOMAIN"`
|
||||
Port int `env:"PORT" envDefault:"4181"`
|
||||
RedirectURL string `env:"REDIRECT_URL" envDefault:"/auth/resp"`
|
||||
LogoutUrl string `env:"LOGOUT_URL" envDefault:"/auth/logout"`
|
||||
}
|
||||
|
||||
// LoadOptions parses the environment vars and the options
|
||||
|
|
Loading…
Reference in a new issue