Skip to content

Commit

Permalink
web/handlers: revamp error localization
Browse files Browse the repository at this point in the history
fixes #66
  • Loading branch information
cryptix committed Apr 7, 2021
1 parent 77b1f52 commit b246b3a
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 73 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
go.cryptoscope.co/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59
go.cryptoscope.co/netwrap v0.1.1
go.cryptoscope.co/secretstream v1.2.2
go.mindeco.de v1.9.0
go.mindeco.de v1.10.0
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.mindeco.de v1.9.0 h1:/xli02DkzpIUZxp/rp1nj8z/OZ9MHvkMIr9TfDVcmBg=
go.mindeco.de v1.9.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
go.mindeco.de v1.10.0 h1:H/bhL+dIgZZnUgBEDlKUJBisTszNiHDONeGZtGdiJJ0=
go.mindeco.de v1.10.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870 h1:TCI3AefMAaOYECvppn30+CfEB0Fn8IES1SKvvacc3/c=
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870/go.mod h1:OnBnV02ux4lLsZ39LID6yYLqSDp+dqTHb/3miYPkQFs=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
Expand Down
8 changes: 8 additions & 0 deletions web/errors/badrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ func (f ErrForbidden) Error() string {
}

var ErrNotAuthorized = errors.New("rooms/web: not authorized")

type PageNotFound struct {
Path string
}

func (e PageNotFound) Error() string {
return fmt.Sprintf("rooms/web: page not found: %s", e.Path)
}
93 changes: 93 additions & 0 deletions web/errors/errhandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package errors

import (
"errors"
"html/template"
"net/http"

"github.com/go-kit/kit/log/level"
"go.mindeco.de/http/auth"
"go.mindeco.de/http/render"
"go.mindeco.de/logging"

"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
)

type ErrorHandler struct {
locHelper *i18n.Helper
render *render.Renderer
}

func NewErrorHandler(locHelper *i18n.Helper) *ErrorHandler {
return &ErrorHandler{
locHelper: locHelper,
}
}

// SetRenderer needs to update the rendere later since we need to pass ErrorHandler into render.New (ie. befor 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) {
var ih = i18n.LocalizerFromRequest(eh.locHelper, req)

// default, unlocalized message
msg := err.Error()

// localize some specific error messages
var (
aa roomdb.ErrAlreadyAdded
pnf PageNotFound
br ErrBadRequest
)

switch {
case err == auth.ErrBadLogin:
msg = ih.LocalizeSimple("ErrorAuthBadLogin")

case errors.Is(err, roomdb.ErrNotFound):
msg = ih.LocalizeSimple("ErrorNotFound")

case errors.As(err, &aa):
msg = ih.LocalizeWithData("ErrorAlreadyAdded", map[string]string{
"Feed": aa.Ref.Ref(),
})

case errors.As(err, &pnf):
msg = ih.LocalizeWithData("ErrorPageNotFound", map[string]string{
"Path": pnf.Path,
})

case errors.As(err, &br):
// TODO: we could localize all the "Where:" as labels, too
// buttt it feels like overkill right now
msg = ih.LocalizeWithData("ErrorBadRequest", map[string]string{
"Where": br.Where,
"Details": br.Details.Error(),
})
}

data := errorTemplateData{
Err: template.HTML(msg),
// TODO: localize status codes? might be fine with a few
Status: http.StatusText(code),
StatusCode: code,
}

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
}
101 changes: 36 additions & 65 deletions web/handlers/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package handlers

import (
"errors"
"fmt"
"html/template"
"net/http"
Expand All @@ -25,6 +24,7 @@ import (
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
"github.com/ssb-ngi-pointer/go-ssb-room/web"
weberrs "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
"github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/admin"
roomsAuth "github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/auth"
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
Expand Down Expand Up @@ -78,16 +78,23 @@ func New(
return nil, err
}

eh := weberrs.NewErrorHandler(locHelper)

allTheTemplates := concatTemplates(
HTMLTemplates,
roomsAuth.HTMLTemplates,
admin.HTMLTemplates,
)
allTheTemplates = append(allTheTemplates, "error.tmpl")

r, err := render.New(web.Templates,
render.SetLogger(logger),
render.BaseTemplates("base.tmpl", "menu.tmpl"),
render.AddTemplates(concatTemplates(
HTMLTemplates,
roomsAuth.HTMLTemplates,
admin.HTMLTemplates,
)...),
render.ErrorTemplate("error.tmpl"),
render.AddTemplates(allTheTemplates...),
// render.ErrorTemplate(),
render.SetErrorHandler(eh.Handle),
render.FuncMap(web.TemplateFuncs(m)),

// TODO: move these to the i18n helper pkg
render.InjectTemplateFunc("i18npl", func(r *http.Request) interface{} {
loc := i18n.LocalizerFromRequest(locHelper, r)
Expand All @@ -97,6 +104,7 @@ func New(
loc := i18n.LocalizerFromRequest(locHelper, r)
return loc.LocalizeSimple
}),

render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
return func(routeName string) bool {
route := m.Get(routeName)
Expand All @@ -110,6 +118,7 @@ func New(
return r.RequestURI == url.Path
}
}),

render.InjectTemplateFunc("urlToNotice", func(r *http.Request) interface{} {
return func(name string) *url.URL {
noticeName := roomdb.PinnedNoticeName(name)
Expand All @@ -135,11 +144,13 @@ func New(
return u
}
}),

render.InjectTemplateFunc("is_logged_in", members.TemplateHelper()),
)
if err != nil {
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
}
eh.SetRenderer(r)

cookieCodec, err := web.LoadOrCreateCookieSecrets(repo)
if err != nil {
Expand All @@ -154,52 +165,14 @@ func New(
},
}

// TODO: this is just the error handler for http/auth, not render
authErrH := func(rw http.ResponseWriter, req *http.Request, err error, code int) {
var ih = i18n.LocalizerFromRequest(locHelper, req)

// default, unlocalized message
msg := err.Error()

// localize some specific error messages
var (
aa roomdb.ErrAlreadyAdded
)
switch {
case err == auth.ErrBadLogin:
msg = ih.LocalizeSimple("AuthErrorBadLogin")

case errors.Is(err, roomdb.ErrNotFound):
msg = ih.LocalizeSimple("ErrorNotFound")

case errors.As(err, &aa):
msg = ih.LocalizeSimple("ErrorAlreadyAdded")
}

r.HTML("error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
return errorTemplateData{
Err: msg,
// TODO: localize?
Status: http.StatusText(code),
StatusCode: code,
}, nil
}).ServeHTTP(rw, req)
}

notAuthorizedH := r.HTML("error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
statusCode := http.StatusUnauthorized
rw.WriteHeader(statusCode)
return errorTemplateData{
statusCode,
"Unauthorized",
"you are not authorized to access the requested site",
}, nil
})

authWithPassword, err := auth.NewHandler(dbs.AuthFallback,
auth.SetStore(cookieStore),
auth.SetErrorHandler(authErrH),
auth.SetNotAuthorizedHandler(notAuthorizedH),
auth.SetErrorHandler(func(rw http.ResponseWriter, req *http.Request, err error, code int) {
eh.Handle(rw, req, code, err)
}),
auth.SetNotAuthorizedHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
eh.Handle(rw, req, http.StatusForbidden, weberrs.ErrNotAuthorized)
})),
auth.SetLifetime(2*time.Hour), // TODO: configure
)
if err != nil {
Expand Down Expand Up @@ -237,16 +210,14 @@ func New(
bridge,
)

// auth routes
m.Get(router.AuthLogin).Handler(r.StaticHTML("auth/decide_method.tmpl"))

m.Get(router.AuthFallbackFinalize).HandlerFunc(authWithPassword.Authorize)

m.Get(router.AuthFallbackLogin).Handler(r.HTML("auth/fallback_sign_in.tmpl", func(w http.ResponseWriter, req *http.Request) (interface{}, error) {
return map[string]interface{}{
csrf.TemplateTag: csrf.TemplateField(req),
}, nil
}))

m.Get(router.AuthLogout).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
err = authWithSSB.Logout(w, req)
if err != nil {
Expand All @@ -255,6 +226,7 @@ func New(
authWithPassword.Logout(w, req)
})

// all the admin routes
adminHandler := admin.Handler(
netInfo.Domain,
r,
Expand All @@ -271,7 +243,10 @@ func New(
)
mainMux.Handle("/admin/", members.AuthenticateFromContext(r)(adminHandler))

// landing page
m.Get(router.CompleteIndex).Handler(r.HTML("landing/index.tmpl", func(w http.ResponseWriter, req *http.Request) (interface{}, error) {
// TODO: try websocket upgrade (issue #)

notice, err := dbs.PinnedNotices.Get(req.Context(), roomdb.NoticeDescription, "en-GB")
if err != nil {
return nil, fmt.Errorf("failed to find description: %w", err)
Expand All @@ -286,13 +261,15 @@ func New(
}))
m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl"))

// notices (the mini-CMS)
var nh = noticeHandler{
notices: dbs.Notices,
pinned: dbs.PinnedNotices,
}
m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list))
m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show))

// public aliases
var ah = aliasHandler{
r: r,

Expand All @@ -303,6 +280,7 @@ func New(
}
m.Get(router.CompleteAliasResolve).HandlerFunc(ah.resolve)

//public invites
var ih = inviteHandler{
render: r,

Expand All @@ -317,14 +295,14 @@ func New(
m.Get(router.CompleteInviteInsertID).Handler(r.HTML("invite/insert-id.tmpl", ih.presentInsert))
m.Get(router.CompleteInviteConsume).HandlerFunc(ih.consume)

// statuc assets
m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets)))

m.NotFoundHandler = r.HTML("error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
rw.WriteHeader(http.StatusNotFound)
msg := i18n.LocalizerFromRequest(locHelper, req).LocalizeSimple("PageNotFound")
return errorTemplateData{http.StatusNotFound, "Not Found", msg}, nil
m.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
eh.Handle(rw, req, http.StatusNotFound, weberrs.PageNotFound{Path: req.URL.Path})
})

// hook up main stdlib mux to the gorrilla/mux with named routes
mainMux.Handle("/", m)

urlTo := web.NewURLTo(m)
Expand Down Expand Up @@ -363,13 +341,6 @@ func New(
}

// utils

type errorTemplateData struct {
StatusCode int
Status string
Err string
}

func concatTemplates(lst ...[]string) []string {
var catted []string

Expand Down
Loading

0 comments on commit b246b3a

Please sign in to comment.