bouncer/internal/proxy/director/layer/authn/layer.go
William Petit 781bfcab19
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
Cadoles/bouncer/pipeline/pr-develop This commit looks good
feat: add authn-basic layer
2024-05-21 12:10:52 +02:00

167 lines
4.4 KiB
Go

package authn
import (
"html/template"
"net/http"
"path/filepath"
"forge.cadoles.com/Cadoles/go-proxy"
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
"forge.cadoles.com/cadoles/bouncer/internal/store"
"github.com/Masterminds/sprig/v3"
"github.com/pkg/errors"
"gitlab.com/wpetit/goweb/logger"
)
type Layer struct {
layerType store.LayerType
auth Authenticator
templateDir string
}
func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
options, err := fromStoreOptions(layer.Options)
if err != nil {
logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if preAuth, ok := l.auth.(PreAuthentication); ok {
if err := preAuth.PreAuthentication(w, r, layer); err != nil {
if errors.Is(err, ErrSkipRequest) {
return
}
logger.Error(ctx, "could not execute pre-auth hook", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
matches := wildcard.MatchAny(r.URL.String(), options.MatchURLs...)
if !matches {
next.ServeHTTP(w, r)
return
}
user, err := l.auth.Authenticate(w, r, layer)
if err != nil {
if errors.Is(err, ErrSkipRequest) {
return
}
if errors.Is(err, ErrForbidden) {
l.renderForbiddenPage(w, r, layer, options, user)
return
}
logger.Error(ctx, "could not authenticate user", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if err := l.applyRules(r, options, user); err != nil {
if errors.Is(err, ErrForbidden) {
l.renderForbiddenPage(w, r, layer, options, user)
return
}
logger.Error(ctx, "could not apply rules", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if postAuth, ok := l.auth.(PostAuthentication); ok {
if err := postAuth.PostAuthentication(w, r, layer, user); err != nil {
if errors.Is(err, ErrSkipRequest) {
return
}
if errors.Is(err, ErrForbidden) {
l.renderForbiddenPage(w, r, layer, options, user)
return
}
logger.Error(ctx, "could not execute post-auth hook", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
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)
}
func (l *Layer) renderPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, page string, block string, user *User) {
ctx := r.Context()
pattern := filepath.Join(l.templateDir, page+".gohtml")
logger.Info(ctx, "loading authn templates", logger.F("pattern", pattern))
tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern)
if err != nil {
logger.Error(ctx, "could not load authn templates", logger.E(errors.WithStack(err)))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
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)
return
}
}
// LayerType implements director.MiddlewareLayer
func (l *Layer) LayerType() store.LayerType {
return l.layerType
}
func NewLayer(layerType store.LayerType, auth Authenticator, funcs ...OptionFunc) *Layer {
opts := NewOptions(funcs...)
return &Layer{
layerType: layerType,
auth: auth,
templateDir: opts.TemplateDir,
}
}
var _ director.MiddlewareLayer = &Layer{}