From d5fed4c2ac9e4ccf02330939dd2b5f03f318ed62 Mon Sep 17 00:00:00 2001 From: William Petit Date: Wed, 5 Jun 2024 15:46:59 +0200 Subject: [PATCH] feat(authn): add templatized error page ref https://forge.cadoles.com/CNOUS/mse/issues/3907 --- internal/config/layers.go | 1 + internal/proxy/director/layer/authn/layer.go | 75 +++++++++---- .../director/layer/authn/layer_options.go | 4 + .../proxy/director/layer/authn/oidc/layer.go | 2 +- .../director/layer/authn/oidc/options.go | 10 ++ .../proxy/director/layer/authn/options.go | 8 ++ internal/setup/authn_basic_layer.go | 1 + internal/setup/authn_network_layer.go | 1 + internal/setup/authn_oidc_layer.go | 4 + layers/authn/templates/error.gohtml | 104 ++++++++++++++++++ 10 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 layers/authn/templates/error.gohtml diff --git a/internal/config/layers.go b/internal/config/layers.go index b909aae..a2d9384 100644 --- a/internal/config/layers.go +++ b/internal/config/layers.go @@ -31,6 +31,7 @@ type QueueLayerConfig struct { } type AuthnLayerConfig struct { + Debug InterpolatedBool `yaml:"debug"` TemplateDir InterpolatedString `yaml:"templateDir"` OIDC AuthnOIDCLayerConfig `yaml:"oidc"` } diff --git a/internal/proxy/director/layer/authn/layer.go b/internal/proxy/director/layer/authn/layer.go index 90fc336..dd15a32 100644 --- a/internal/proxy/director/layer/authn/layer.go +++ b/internal/proxy/director/layer/authn/layer.go @@ -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, } } diff --git a/internal/proxy/director/layer/authn/layer_options.go b/internal/proxy/director/layer/authn/layer_options.go index c22aae9..8afe4be 100644 --- a/internal/proxy/director/layer/authn/layer_options.go +++ b/internal/proxy/director/layer/authn/layer_options.go @@ -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", + }, }, } diff --git a/internal/proxy/director/layer/authn/oidc/layer.go b/internal/proxy/director/layer/authn/oidc/layer.go index 2c242b2..9509361 100644 --- a/internal/proxy/director/layer/authn/oidc/layer.go +++ b/internal/proxy/director/layer/authn/oidc/layer.go @@ -14,5 +14,5 @@ func NewLayer(store sessions.Store, funcs ...OptionFunc) *authn.Layer { httpTransport: opts.HTTPTransport, httpClientTimeout: opts.HTTPClientTimeout, store: store, - }) + }, opts.AuthnOptions...) } diff --git a/internal/proxy/director/layer/authn/oidc/options.go b/internal/proxy/director/layer/authn/oidc/options.go index 9ce69e1..a2f0c14 100644 --- a/internal/proxy/director/layer/authn/oidc/options.go +++ b/internal/proxy/director/layer/authn/oidc/options.go @@ -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 { diff --git a/internal/proxy/director/layer/authn/options.go b/internal/proxy/director/layer/authn/options.go index 2f365c5..50e8a2e 100644 --- a/internal/proxy/director/layer/authn/options.go +++ b/internal/proxy/director/layer/authn/options.go @@ -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 + } +} diff --git a/internal/setup/authn_basic_layer.go b/internal/setup/authn_basic_layer.go index 21a2ac5..e07ebef 100644 --- a/internal/setup/authn_basic_layer.go +++ b/internal/setup/authn_basic_layer.go @@ -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 diff --git a/internal/setup/authn_network_layer.go b/internal/setup/authn_network_layer.go index 8a0775d..bd0d9dd 100644 --- a/internal/setup/authn_network_layer.go +++ b/internal/setup/authn_network_layer.go @@ -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 diff --git a/internal/setup/authn_oidc_layer.go b/internal/setup/authn_oidc_layer.go index be2cf6b..6ecbecf 100644 --- a/internal/setup/authn_oidc_layer.go +++ b/internal/setup/authn_oidc_layer.go @@ -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 } diff --git a/layers/authn/templates/error.gohtml b/layers/authn/templates/error.gohtml new file mode 100644 index 0000000..1b8f17d --- /dev/null +++ b/layers/authn/templates/error.gohtml @@ -0,0 +1,104 @@ +{{ define "default" }} + + + + + + Erreur - {{ .Layer.Name }} + + + +
+
+

Une erreur est survenue !

+ {{ if .Debug }} +
{{ .Err }}
+ {{ end }} + {{/* if a public base url is provided, show navigation link */}} + {{ $oidc := ( index .Layer.Options "oidc" ) }} + {{ $publicBaseURL := ( index $oidc "publicBaseURL" ) }} + {{ if $publicBaseURL }} + + {{ end }} + +
+
+ + +{{ end }}