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 := getBaseCookie(options)
|
||||||
cookie.Name = "__auth_csrf"
|
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)
|
cookie.Expires = time.Now().Local().Add(time.Hour)
|
||||||
|
|
||||||
return cookie
|
return cookie
|
||||||
|
|
|
@ -19,6 +19,12 @@ type ForwardAuth struct {
|
||||||
OidcProvider *oidc.Provider
|
OidcProvider *oidc.Provider
|
||||||
OAuth2Config oauth2.Config
|
OAuth2Config oauth2.Config
|
||||||
OidcVefifier *oidc.IDTokenVerifier
|
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
|
// 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,
|
ClientID: options.ClientID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
providerClaims := OidcProviderClaims{}
|
||||||
|
if err = provider.Claims(&providerClaims); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &ForwardAuth{
|
return &ForwardAuth{
|
||||||
OidcProvider: provider,
|
OidcProvider: provider,
|
||||||
|
OidcVefifier: verifier,
|
||||||
|
OidcProviderClaims: &providerClaims,
|
||||||
|
|
||||||
OAuth2Config: oauth2.Config{
|
OAuth2Config: oauth2.Config{
|
||||||
ClientID: options.ClientID,
|
ClientID: options.ClientID,
|
||||||
ClientSecret: options.ClientSecret,
|
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.
|
// "openid" is a required scope for OpenID Connect flows.
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
},
|
},
|
||||||
OidcVefifier: verifier,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package forwardauth
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (fw *ForwardAuth) GetReturnUri(r *http.Request) string {
|
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)
|
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
|
// 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{
|
logger := logrus.WithFields(logrus.Fields{
|
||||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
||||||
"Path": forwardedURI.Path,
|
"Path": "/auth/resp",
|
||||||
})
|
})
|
||||||
|
|
||||||
// check for the csrf cookie
|
// check for the csrf cookie
|
|
@ -5,6 +5,7 @@ This code is licensed under MIT license (see LICENSE for details)
|
||||||
package httphandler
|
package httphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
@ -13,11 +14,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// RootHandler returns a handler function which handles all requests to the root
|
// 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{
|
logger := logrus.WithFields(logrus.Fields{
|
||||||
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
"SourceIP": r.Header.Get("X-Forwarded-For"),
|
||||||
"RequestTarget": root.forwardAuth.GetReturnUri(r),
|
"RequestTarget": redirect,
|
||||||
"Path": forwardedURI.Path,
|
"Path": "/",
|
||||||
})
|
})
|
||||||
|
|
||||||
claims, err := root.forwardAuth.IsAuthenticated(r.Context(), logger, w, r, root.options)
|
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))
|
//http.SetCookie(w, root.forwardAuth.ClearRefreshAuthCookie(root.options))
|
||||||
|
|
||||||
state := uuid.New().String()
|
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)
|
http.Redirect(w, r, root.forwardAuth.OAuth2Config.AuthCodeURL(state), http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("X-Forwarded-User", claims.Email)
|
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
|
package httphandler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"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) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
uri, err := url.Parse(r.Header.Get("X-Forwarded-Uri"))
|
uri, err := url.Parse(r.Header.Get("X-Forwarded-Uri"))
|
||||||
switch {
|
switch {
|
||||||
|
@ -32,12 +33,19 @@ func (h *HttpHandler) Entrypoint() func(http.ResponseWriter, *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
||||||
case uri.Path == h.options.RedirectURL:
|
case uri.Path == root.options.RedirectURL:
|
||||||
h.callbackHandler(w, r, uri)
|
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
|
return
|
||||||
|
|
||||||
default:
|
default:
|
||||||
h.rootHandler(w, r, uri)
|
root.authRootHandler(w, r, uri)
|
||||||
return
|
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"`
|
CookieDomain string `env:"COOKIE_DOMAIN"`
|
||||||
Port int `env:"PORT" envDefault:"4181"`
|
Port int `env:"PORT" envDefault:"4181"`
|
||||||
RedirectURL string `env:"REDIRECT_URL" envDefault:"/auth/resp"`
|
RedirectURL string `env:"REDIRECT_URL" envDefault:"/auth/resp"`
|
||||||
|
LogoutUrl string `env:"LOGOUT_URL" envDefault:"/auth/logout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadOptions parses the environment vars and the options
|
// LoadOptions parses the environment vars and the options
|
||||||
|
|
Loading…
Reference in a new issue