feat(authn): add templatized error page
Cadoles/bouncer/pipeline/head Something is wrong with the build of this commit
Details
Cadoles/bouncer/pipeline/head Something is wrong with the build of this commit
Details
ref CNOUS/mse#3907
This commit is contained in:
parent
c7ac331b10
commit
d5fed4c2ac
|
@ -31,6 +31,7 @@ type QueueLayerConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthnLayerConfig struct {
|
type AuthnLayerConfig struct {
|
||||||
|
Debug InterpolatedBool `yaml:"debug"`
|
||||||
TemplateDir InterpolatedString `yaml:"templateDir"`
|
TemplateDir InterpolatedString `yaml:"templateDir"`
|
||||||
OIDC AuthnOIDCLayerConfig `yaml:"oidc"`
|
OIDC AuthnOIDCLayerConfig `yaml:"oidc"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
type Layer struct {
|
type Layer struct {
|
||||||
layerType store.LayerType
|
layerType store.LayerType
|
||||||
auth Authenticator
|
auth Authenticator
|
||||||
|
debug bool
|
||||||
|
|
||||||
templateDir string
|
templateDir string
|
||||||
}
|
}
|
||||||
|
@ -40,8 +41,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "could not execute pre-auth hook", logger.E(errors.WithStack(err)))
|
err = errors.WithStack(err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
logger.Error(ctx, "could not execute pre-auth hook", logger.E(err))
|
||||||
|
l.renderErrorPage(w, r, layer, options, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -65,8 +67,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "could not authenticate user", logger.E(errors.WithStack(err)))
|
err = errors.WithStack(err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
logger.Error(ctx, "could not authenticate user", logger.E(err))
|
||||||
|
l.renderErrorPage(w, r, layer, options, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -77,8 +80,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "could not apply rules", logger.E(errors.WithStack(err)))
|
err = errors.WithStack(err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
logger.Error(ctx, "could not apply rules", logger.E(err))
|
||||||
|
l.renderErrorPage(w, r, layer, options, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -94,8 +98,9 @@ func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Error(ctx, "could not execute post-auth hook", logger.E(errors.WithStack(err)))
|
err = errors.WithStack(err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
logger.Error(ctx, "could not execute post-auth hook", logger.E(err))
|
||||||
|
l.renderErrorPage(w, r, layer, options, err)
|
||||||
|
|
||||||
return
|
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) {
|
type baseTemplateData struct {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
Layer *store.Layer
|
||||||
l.renderPage(w, r, layer, "forbidden", options.Templates.Forbidden.Block, user)
|
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()
|
ctx := r.Context()
|
||||||
|
|
||||||
pattern := filepath.Join(l.templateDir, page+".gohtml")
|
pattern := filepath.Join(l.templateDir, page+".gohtml")
|
||||||
|
@ -128,18 +168,8 @@ func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, layer *store.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData := struct {
|
|
||||||
Layer *store.Layer
|
|
||||||
User *User
|
|
||||||
}{
|
|
||||||
Layer: layer,
|
|
||||||
User: user,
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Cache-Control", "no-cache")
|
w.Header().Add("Cache-Control", "no-cache")
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
if err := tmpl.ExecuteTemplate(w, block, templateData); err != nil {
|
if err := tmpl.ExecuteTemplate(w, block, templateData); err != nil {
|
||||||
logger.Error(ctx, "could not render authn page", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "could not render authn page", logger.E(errors.WithStack(err)))
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -160,6 +190,7 @@ func NewLayer(layerType store.LayerType, auth Authenticator, funcs ...OptionFunc
|
||||||
layerType: layerType,
|
layerType: layerType,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
templateDir: opts.TemplateDir,
|
templateDir: opts.TemplateDir,
|
||||||
|
debug: opts.Debug,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ type LayerOptions struct {
|
||||||
|
|
||||||
type TemplatesOptions struct {
|
type TemplatesOptions struct {
|
||||||
Forbidden TemplateOptions `mapstructure:"forbidden"`
|
Forbidden TemplateOptions `mapstructure:"forbidden"`
|
||||||
|
Error TemplateOptions `mapstructure:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateOptions struct {
|
type TemplateOptions struct {
|
||||||
|
@ -43,6 +44,9 @@ func DefaultLayerOptions() LayerOptions {
|
||||||
Forbidden: TemplateOptions{
|
Forbidden: TemplateOptions{
|
||||||
Block: "default",
|
Block: "default",
|
||||||
},
|
},
|
||||||
|
Error: TemplateOptions{
|
||||||
|
Block: "default",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,5 @@ func NewLayer(store sessions.Store, funcs ...OptionFunc) *authn.Layer {
|
||||||
httpTransport: opts.HTTPTransport,
|
httpTransport: opts.HTTPTransport,
|
||||||
httpClientTimeout: opts.HTTPClientTimeout,
|
httpClientTimeout: opts.HTTPClientTimeout,
|
||||||
store: store,
|
store: store,
|
||||||
})
|
}, opts.AuthnOptions...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@ package oidc
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
HTTPTransport *http.Transport
|
HTTPTransport *http.Transport
|
||||||
HTTPClientTimeout time.Duration
|
HTTPClientTimeout time.Duration
|
||||||
|
AuthnOptions []authn.OptionFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionFunc func(opts *Options)
|
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 {
|
func NewOptions(funcs ...OptionFunc) *Options {
|
||||||
opts := &Options{
|
opts := &Options{
|
||||||
HTTPTransport: http.DefaultTransport.(*http.Transport),
|
HTTPTransport: http.DefaultTransport.(*http.Transport),
|
||||||
HTTPClientTimeout: 30 * time.Second,
|
HTTPClientTimeout: 30 * time.Second,
|
||||||
|
AuthnOptions: make([]authn.OptionFunc, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fn := range funcs {
|
for _, fn := range funcs {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package authn
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
TemplateDir string
|
TemplateDir string
|
||||||
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionFunc func(*Options)
|
type OptionFunc func(*Options)
|
||||||
|
@ -9,6 +10,7 @@ type OptionFunc func(*Options)
|
||||||
func NewOptions(funcs ...OptionFunc) *Options {
|
func NewOptions(funcs ...OptionFunc) *Options {
|
||||||
opts := &Options{
|
opts := &Options{
|
||||||
TemplateDir: "./templates",
|
TemplateDir: "./templates",
|
||||||
|
Debug: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fn := range funcs {
|
for _, fn := range funcs {
|
||||||
|
@ -23,3 +25,9 @@ func WithTemplateDir(templateDir string) OptionFunc {
|
||||||
o.TemplateDir = templateDir
|
o.TemplateDir = templateDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithDebug(debug bool) OptionFunc {
|
||||||
|
return func(o *Options) {
|
||||||
|
o.Debug = debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ func init() {
|
||||||
func setupAuthnBasicLayer(conf *config.Config) (director.Layer, error) {
|
func setupAuthnBasicLayer(conf *config.Config) (director.Layer, error) {
|
||||||
options := []authn.OptionFunc{
|
options := []authn.OptionFunc{
|
||||||
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
|
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
|
||||||
|
authn.WithDebug(bool(conf.Layers.Authn.Debug)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return basic.NewLayer(options...), nil
|
return basic.NewLayer(options...), nil
|
||||||
|
|
|
@ -21,6 +21,7 @@ func init() {
|
||||||
func setupAuthnNetworkLayer(conf *config.Config) (director.Layer, error) {
|
func setupAuthnNetworkLayer(conf *config.Config) (director.Layer, error) {
|
||||||
options := []authn.OptionFunc{
|
options := []authn.OptionFunc{
|
||||||
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
|
authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)),
|
||||||
|
authn.WithDebug(bool(conf.Layers.Authn.Debug)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return network.NewLayer(options...), nil
|
return network.NewLayer(options...), nil
|
||||||
|
|
|
@ -33,5 +33,9 @@ func setupAuthnOIDCLayer(conf *config.Config) (director.Layer, error) {
|
||||||
store,
|
store,
|
||||||
oidc.WithHTTPTransport(transport),
|
oidc.WithHTTPTransport(transport),
|
||||||
oidc.WithHTTPClientTimeout(time.Duration(*conf.Layers.Authn.OIDC.HTTPClient.Timeout)),
|
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
|
), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
{{ define "default" }}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Erreur - {{ .Layer.Name }}</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: #ffe4e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card {
|
||||||
|
padding: 1.5em 1em;
|
||||||
|
border: 1px solid #d90202;
|
||||||
|
background-color: #feb0b0;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 2px 2px #cccccc1c;
|
||||||
|
color: #810000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<div id="card">
|
||||||
|
<h2 class="title">Une erreur est survenue !</h2>
|
||||||
|
{{ if .Debug }}
|
||||||
|
<pre>{{ .Err }}</pre>
|
||||||
|
{{ end }}
|
||||||
|
{{/* if a public base url is provided, show navigation link */}}
|
||||||
|
{{ $oidc := ( index .Layer.Options "oidc" ) }}
|
||||||
|
{{ $publicBaseURL := ( index $oidc "publicBaseURL" ) }}
|
||||||
|
{{ if $publicBaseURL }}
|
||||||
|
<div class="back-link">
|
||||||
|
<a href="{{ $publicBaseURL }}" title="Retourner sur l'application"
|
||||||
|
>Retourner sur l'application</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<p class="footer">
|
||||||
|
Propulsé par
|
||||||
|
<a href="https://forge.cadoles.com/Cadoles/bouncer">Bouncer</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
Loading…
Reference in New Issue