feat(authn): add templatized error page
Some checks reported errors
Cadoles/bouncer/pipeline/head Something is wrong with the build of this commit

ref CNOUS/mse#3907
This commit is contained in:
2024-06-05 15:46:59 +02:00
parent c7ac331b10
commit d5fed4c2ac
10 changed files with 187 additions and 23 deletions

View File

@ -31,6 +31,7 @@ type QueueLayerConfig struct {
}
type AuthnLayerConfig struct {
Debug InterpolatedBool `yaml:"debug"`
TemplateDir InterpolatedString `yaml:"templateDir"`
OIDC AuthnOIDCLayerConfig `yaml:"oidc"`
}

View File

@ -17,6 +17,7 @@ import (
type Layer struct {
layerType store.LayerType
auth Authenticator
debug bool
templateDir string
}
@ -40,8 +41,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return
}
logger.Error(ctx, "could not execute pre-auth hook", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
err = errors.WithStack(err)
logger.Error(ctx, "could not execute pre-auth hook", logger.E(err))
l.renderErrorPage(w, r, layer, options, err)
return
}
@ -65,8 +67,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return
}
logger.Error(ctx, "could not authenticate user", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
err = errors.WithStack(err)
logger.Error(ctx, "could not authenticate user", logger.E(err))
l.renderErrorPage(w, r, layer, options, err)
return
}
@ -77,8 +80,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return
}
logger.Error(ctx, "could not apply rules", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
err = errors.WithStack(err)
logger.Error(ctx, "could not apply rules", logger.E(err))
l.renderErrorPage(w, r, layer, options, err)
return
}
@ -94,8 +98,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return
}
logger.Error(ctx, "could not execute post-auth hook", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
err = errors.WithStack(err)
logger.Error(ctx, "could not execute post-auth hook", logger.E(err))
l.renderErrorPage(w, r, layer, options, err)
return
}
@ -108,12 +113,47 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
}
}
func (l *Layer) renderForbiddenPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, options *LayerOptions, user *User) {
w.WriteHeader(http.StatusForbidden)
l.renderPage(w, r, layer, "forbidden", options.Templates.Forbidden.Block, user)
type baseTemplateData struct {
Layer *store.Layer
Debug bool
Request *http.Request
}
func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, page string, block string, user *User) {
func (l *Layer) renderForbiddenPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, options *LayerOptions, user *User) {
templateData := struct {
baseTemplateData
User *User
}{
baseTemplateData: baseTemplateData{
Layer: layer,
Debug: l.debug,
Request: r,
},
User: user,
}
w.WriteHeader(http.StatusForbidden)
l.renderPage(w, r, "forbidden", options.Templates.Forbidden.Block, templateData)
}
func (l *Layer) renderErrorPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, options *LayerOptions, err error) {
templateData := struct {
baseTemplateData
Err error
}{
baseTemplateData: baseTemplateData{
Layer: layer,
Debug: l.debug,
Request: r,
},
Err: err,
}
w.WriteHeader(http.StatusInternalServerError)
l.renderPage(w, r, "error", options.Templates.Error.Block, templateData)
}
func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, page string, block string, templateData any) {
ctx := r.Context()
pattern := filepath.Join(l.templateDir, page+".gohtml")
@ -128,18 +168,8 @@ func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, layer *store.
return
}
templateData := struct {
Layer *store.Layer
User *User
}{
Layer: layer,
User: user,
}
w.Header().Add("Cache-Control", "no-cache")
w.WriteHeader(http.StatusOK)
if err := tmpl.ExecuteTemplate(w, block, templateData); err != nil {
logger.Error(ctx, "could not render authn page", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -160,6 +190,7 @@ func NewLayer(layerType store.LayerType, auth Authenticator, funcs ...OptionFunc
layerType: layerType,
auth: auth,
templateDir: opts.TemplateDir,
debug: opts.Debug,
}
}

View File

@ -17,6 +17,7 @@ type LayerOptions struct {
type TemplatesOptions struct {
Forbidden TemplateOptions `mapstructure:"forbidden"`
Error TemplateOptions `mapstructure:"error"`
}
type TemplateOptions struct {
@ -43,6 +44,9 @@ func DefaultLayerOptions() LayerOptions {
Forbidden: TemplateOptions{
Block: "default",
},
Error: TemplateOptions{
Block: "default",
},
},
}

View File

@ -14,5 +14,5 @@ func NewLayer(store sessions.Store, funcs ...OptionFunc) *authn.Layer {
httpTransport: opts.HTTPTransport,
httpClientTimeout: opts.HTTPClientTimeout,
store: store,
})
}, opts.AuthnOptions...)
}

View File

@ -3,11 +3,14 @@ package oidc
import (
"net/http"
"time"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
)
type Options struct {
HTTPTransport *http.Transport
HTTPClientTimeout time.Duration
AuthnOptions []authn.OptionFunc
}
type OptionFunc func(opts *Options)
@ -24,10 +27,17 @@ func WithHTTPClientTimeout(timeout time.Duration) OptionFunc {
}
}
func WithAuthnOptions(funcs ...authn.OptionFunc) OptionFunc {
return func(opts *Options) {
opts.AuthnOptions = funcs
}
}
func NewOptions(funcs ...OptionFunc) *Options {
opts := &Options{
HTTPTransport: http.DefaultTransport.(*http.Transport),
HTTPClientTimeout: 30 * time.Second,
AuthnOptions: make([]authn.OptionFunc, 0),
}
for _, fn := range funcs {

View File

@ -2,6 +2,7 @@ package authn
type Options struct {
TemplateDir string
Debug bool
}
type OptionFunc func(*Options)
@ -9,6 +10,7 @@ type OptionFunc func(*Options)
func NewOptions(funcs ...OptionFunc) *Options {
opts := &Options{
TemplateDir: "./templates",
Debug: false,
}
for _, fn := range funcs {
@ -23,3 +25,9 @@ func WithTemplateDir(templateDir string) OptionFunc {
o.TemplateDir = templateDir
}
}
func WithDebug(debug bool) OptionFunc {
return func(o *Options) {
o.Debug = debug
}
}

View File

@ -21,6 +21,7 @@ func init() {
func setupAuthnBasicLayer(conf *config.Config) (director.Layer, error) {
options := []authn.OptionFunc{
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
authn.WithDebug(bool(conf.Layers.Authn.Debug)),
}
return basic.NewLayer(options...), nil

View File

@ -21,6 +21,7 @@ func init() {
func setupAuthnNetworkLayer(conf *config.Config) (director.Layer, error) {
options := []authn.OptionFunc{
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
authn.WithDebug(bool(conf.Layers.Authn.Debug)),
}
return network.NewLayer(options...), nil

View File

@ -33,5 +33,9 @@ func setupAuthnOIDCLayer(conf *config.Config) (director.Layer, error) {
store,
oidc.WithHTTPTransport(transport),
oidc.WithHTTPClientTimeout(time.Duration(*conf.Layers.Authn.OIDC.HTTPClient.Timeout)),
oidc.WithAuthnOptions(
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
authn.WithDebug(bool(conf.Layers.Authn.Debug)),
),
), nil
}