go-ssb-room/web/errors/errhandler.go

142 lines
3.4 KiB
Go

// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
//
// SPDX-License-Identifier: MIT
package errors
import (
"errors"
"html/template"
"net/http"
"go.mindeco.de/http/auth"
"go.mindeco.de/http/render"
"go.mindeco.de/log/level"
"go.mindeco.de/logging"
"github.com/ssbc/go-ssb-room/v2/roomdb"
"github.com/ssbc/go-ssb-room/v2/web/i18n"
)
type ErrorHandler struct {
locHelper *i18n.Helper
render *render.Renderer
flashes *FlashHelper
}
func NewErrorHandler(locHelper *i18n.Helper, flashes *FlashHelper) *ErrorHandler {
return &ErrorHandler{
locHelper: locHelper,
flashes: flashes,
}
}
// SetRenderer needs to update the renderer later since we need to pass ErrorHandler into render.New (i.e. before we get the pointer for r)
func (eh *ErrorHandler) SetRenderer(r *render.Renderer) {
eh.render = r
}
func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) {
log := logging.FromContext(req.Context())
level.Error(log).Log("event", "handling error", "path", req.URL.Path, "err", err)
var redirectErr ErrRedirect
if errors.As(err, &redirectErr) {
if redirectErr.Reason != nil {
eh.flashes.AddError(rw, req, redirectErr.Reason)
}
http.Redirect(rw, req, redirectErr.Path, http.StatusSeeOther)
return
}
var ih = eh.locHelper.FromRequest(req)
code, msg := localizeError(ih, err)
data := errorTemplateData{
Err: template.HTML(msg),
// TODO: localize status codes? might be fine with a few
Status: http.StatusText(code),
StatusCode: code,
BackURL: req.URL.Path,
}
if code == http.StatusNotFound {
data.BackURL = "/"
referer := req.Header.Get("Referer")
if referer != "" {
data.BackURL = referer
}
}
renderErr := eh.render.Render(rw, req, "error.tmpl", code, data)
if renderErr != nil {
logger := logging.FromContext(req.Context())
level.Error(logger).Log("event", "error template renderfailed",
"orig-err", err,
"render-err", renderErr,
)
}
}
type errorTemplateData struct {
StatusCode int
Status string
Err template.HTML
BackURL string
}
func localizeError(ih *i18n.Localizer, err error) (int, template.HTML) {
// default, unlocalized message
msg := template.HTML(err.Error())
// localize some specific error messages
var (
aa roomdb.ErrAlreadyAdded
pnf PageNotFound
br ErrBadRequest
f ErrForbidden
gl ErrGenericLocalized
)
code := http.StatusInternalServerError
switch {
case err == ErrNotAuthorized:
code = http.StatusForbidden
msg = ih.LocalizeSimple("ErrorNotAuthorized")
case err == auth.ErrBadLogin:
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
case errors.As(err, &gl):
msg = ih.LocalizeSimple(gl.Label)
case errors.Is(err, roomdb.ErrNotFound):
code = http.StatusNotFound
msg = ih.LocalizeSimple("ErrorNotFound")
case errors.As(err, &aa):
msg = ih.LocalizeWithData("ErrorAlreadyAdded", "Feed", aa.Ref.String())
case errors.As(err, &pnf):
code = http.StatusNotFound
msg = ih.LocalizeWithData("ErrorPageNotFound", "Path", pnf.Path)
case errors.As(err, &br):
code = http.StatusBadRequest
// TODO: we could localize all the "Where:" as labels, too
// buttt it feels like overkill right now
msg = ih.LocalizeWithData("ErrorBadRequest", "Where", br.Where, "Details", br.Details.Error())
case errors.As(err, &f):
code = http.StatusForbidden
msg = ih.LocalizeWithData("ErrorForbidden", "Details", f.Details.Error())
}
return code, msg
}