parent
f9652c6423
commit
81bd94344e
2
go.mod
2
go.mod
@ -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
|
||||
|
2
go.sum
2
go.sum
@ -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=
|
||||
|
@ -8,9 +8,12 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrNotFound struct {
|
||||
What string
|
||||
}
|
||||
var (
|
||||
ErrNotAuthorized = errors.New("rooms/web: not authorized")
|
||||
ErrDenied = errors.New("rooms: this key has been banned")
|
||||
)
|
||||
|
||||
type ErrNotFound struct{ What string }
|
||||
|
||||
func (nf ErrNotFound) Error() string {
|
||||
return fmt.Sprintf("rooms/web: item not found: %s", nf.What)
|
||||
@ -25,14 +28,20 @@ func (br ErrBadRequest) Error() string {
|
||||
return fmt.Sprintf("rooms/web: bad request error: %s", br.Details)
|
||||
}
|
||||
|
||||
type ErrForbidden struct {
|
||||
Details error
|
||||
}
|
||||
type ErrForbidden struct{ Details error }
|
||||
|
||||
func (f ErrForbidden) Error() string {
|
||||
return fmt.Sprintf("rooms/web: access denied: %s", f.Details)
|
||||
}
|
||||
|
||||
var ErrNotAuthorized = errors.New("rooms/web: not authorized")
|
||||
type PageNotFound struct{ Path string }
|
||||
|
||||
var ErrDenied = errors.New("rooms: this key has been banned")
|
||||
func (e PageNotFound) Error() string {
|
||||
return fmt.Sprintf("rooms/web: page not found: %s", e.Path)
|
||||
}
|
||||
|
||||
type DatabaseError struct{ Reason error }
|
||||
|
||||
func (e DatabaseError) Error() string {
|
||||
return fmt.Sprintf("rooms/web: database failed to complete query: %s", e.Reason.Error())
|
||||
}
|
||||
|
107
web/errors/errhandler.go
Normal file
107
web/errors/errhandler.go
Normal file
@ -0,0 +1,107 @@
|
||||
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
|
||||
f ErrForbidden
|
||||
)
|
||||
|
||||
switch {
|
||||
case err == ErrNotAuthorized:
|
||||
code = http.StatusForbidden
|
||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
||||
|
||||
case err == auth.ErrBadLogin:
|
||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
||||
|
||||
case errors.Is(err, roomdb.ErrNotFound):
|
||||
code = http.StatusNotFound
|
||||
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):
|
||||
code = http.StatusNotFound
|
||||
msg = ih.LocalizeWithData("ErrorPageNotFound", map[string]string{
|
||||
"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", map[string]string{
|
||||
"Where": br.Where,
|
||||
"Details": br.Details.Error(),
|
||||
})
|
||||
|
||||
case errors.As(err, &f):
|
||||
code = http.StatusForbidden
|
||||
msg = ih.LocalizeWithData("ErrorForbidden", map[string]string{
|
||||
"Details": f.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
|
||||
}
|
@ -69,11 +69,13 @@ func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
||||
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
||||
if err != nil {
|
||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||
|
||||
// TODO: flash error
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status = http.StatusNotFound
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(rw, req, redirectToAliases, status)
|
||||
|
@ -8,12 +8,12 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"go.mindeco.de/http/render"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
||||
"github.com/gorilla/csrf"
|
||||
"go.mindeco.de/http/render"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
||||
type deniedKeysHandler struct {
|
||||
@ -26,21 +26,21 @@ const redirectToDeniedKeys = "/admin/denied"
|
||||
|
||||
func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request"))
|
||||
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
newEntry := req.Form.Get("pub_key")
|
||||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||
if err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -53,11 +53,7 @@ func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||
var aa roomdb.ErrAlreadyAdded
|
||||
if errors.As(err, &aa) {
|
||||
code = http.StatusBadRequest
|
||||
// TODO: localized error pages
|
||||
// h.r.Error(w, req, http.StatusBadRequest, weberrors.Localize())
|
||||
// return
|
||||
}
|
||||
|
||||
h.r.Error(w, req, code, err)
|
||||
return
|
||||
}
|
||||
@ -98,6 +94,7 @@ func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Reque
|
||||
entry, err := h.db.GetByID(req.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
||||
return nil, ErrRedirected
|
||||
}
|
||||
|
@ -47,17 +47,15 @@ func (h invitesHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
||||
|
||||
func (h invitesHandler) create(w http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
return nil, fmt.Errorf("bad request")
|
||||
return nil, weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||
}
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
return nil, fmt.Errorf("bad request: %w", err)
|
||||
return nil, weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
}
|
||||
|
||||
member := members.FromContext(req.Context())
|
||||
if member == nil {
|
||||
return nil, fmt.Errorf("warning: no user session for elevated access request")
|
||||
return nil, weberrors.ErrNotAuthorized
|
||||
}
|
||||
pm, err := h.config.GetPrivacyMode(req.Context())
|
||||
if err != nil {
|
||||
|
@ -29,22 +29,22 @@ const redirectToMembers = "/admin/members"
|
||||
|
||||
func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request"))
|
||||
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
newEntry := req.Form.Get("pub_key")
|
||||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||
if err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad public key: %w", err))
|
||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -54,11 +54,7 @@ func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||
var aa roomdb.ErrAlreadyAdded
|
||||
if errors.As(err, &aa) {
|
||||
code = http.StatusBadRequest
|
||||
// TODO: localized error pages
|
||||
// h.r.Error(w, req, http.StatusBadRequest, weberrors.Localize())
|
||||
// return
|
||||
}
|
||||
|
||||
h.r.Error(w, req, code, err)
|
||||
return
|
||||
}
|
||||
@ -68,41 +64,42 @@ func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "POST" {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request"))
|
||||
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
currentMember := members.FromContext(req.Context())
|
||||
if currentMember == nil || currentMember.Role != roomdb.RoleAdmin {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusForbidden, fmt.Errorf("not an admin"))
|
||||
err := weberrors.ErrForbidden{Details: fmt.Errorf("not an admin")}
|
||||
h.r.Error(w, req, http.StatusForbidden, err)
|
||||
return
|
||||
}
|
||||
|
||||
memberID, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad member id: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusBadRequest, fmt.Errorf("bad request: %w", err))
|
||||
err = weberrors.ErrBadRequest{Where: "id", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
var role roomdb.Role
|
||||
if err := role.UnmarshalText([]byte(req.Form.Get("role"))); err != nil {
|
||||
// TODO: proper error type
|
||||
err = weberrors.ErrBadRequest{Where: "role", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.SetRole(req.Context(), memberID, role); err != nil {
|
||||
// TODO: proper error type
|
||||
h.r.Error(w, req, http.StatusInternalServerError, fmt.Errorf("failed to change member role: %w", err))
|
||||
err = weberrors.DatabaseError{Reason: err}
|
||||
// TODO: not found error
|
||||
h.r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
@ -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"
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -237,6 +210,7 @@ func New(
|
||||
bridge,
|
||||
)
|
||||
|
||||
// auth routes
|
||||
m.Get(router.AuthLogin).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if label := req.URL.Query().Get("ssb-http-auth"); label != "" {
|
||||
authWithSSB.DecideMethod(w, req)
|
||||
@ -246,13 +220,11 @@ func New(
|
||||
})
|
||||
|
||||
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 {
|
||||
@ -261,6 +233,7 @@ func New(
|
||||
authWithPassword.Logout(w, req)
|
||||
})
|
||||
|
||||
// all the admin routes
|
||||
adminHandler := admin.Handler(
|
||||
netInfo.Domain,
|
||||
r,
|
||||
@ -277,7 +250,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)
|
||||
@ -292,6 +268,7 @@ 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,
|
||||
@ -299,6 +276,7 @@ func New(
|
||||
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,
|
||||
|
||||
@ -309,6 +287,7 @@ func New(
|
||||
}
|
||||
m.Get(router.CompleteAliasResolve).HandlerFunc(ah.resolve)
|
||||
|
||||
//public invites
|
||||
var ih = inviteHandler{
|
||||
render: r,
|
||||
|
||||
@ -324,14 +303,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)
|
||||
@ -370,13 +349,6 @@ func New(
|
||||
}
|
||||
|
||||
// utils
|
||||
|
||||
type errorTemplateData struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Err string
|
||||
}
|
||||
|
||||
func concatTemplates(lst ...[]string) []string {
|
||||
var catted []string
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
# default localiztion file for english
|
||||
|
||||
# generic terms
|
||||
GenericConfirm = "Yes"
|
||||
GenericGoBack = "Back"
|
||||
GenericSave = "Save"
|
||||
@ -11,32 +14,77 @@ PageNotFound = "The requested page was not found."
|
||||
|
||||
PubKeyRefPlaceholder = "@ .ed25519"
|
||||
|
||||
# roles
|
||||
RoleMember = "Member"
|
||||
RoleModerator = "Moderator"
|
||||
RoleAdmin = "Admin"
|
||||
|
||||
# navigation labels (should be single words or as short as possible)
|
||||
NavAdminLanding = "Home"
|
||||
NavAdminDashboard = "Dashboard"
|
||||
NavAdminInvites = "Invites"
|
||||
NavAdminNotices = "Notices"
|
||||
|
||||
# Error messages
|
||||
ErrorAuthBadLogin = "The supplied authentication credentials seem wrong."
|
||||
ErrorNotFound = "The database couldn't find the item in question."
|
||||
ErrorAlreadyAdded = "The public key <strong>{{.Key}}</strong> already is on the list"
|
||||
ErrorPageNotFound = "The requested page <strong>({{.Path}})</strong> is not there."
|
||||
ErrorNotAuthorized = "You are not authorized to access this page."
|
||||
ErrorForbidden = "The request could not be executed because of lacking privileges ({{.Details}})"
|
||||
ErrorBadRequest = "There was a problem with your Request: {{.Where}} ({{.Details}}"
|
||||
|
||||
# TODO: might be obsolete with notices
|
||||
LandingTitle = "ohai my room"
|
||||
LandingWelcome = "Landing welcome here"
|
||||
|
||||
AuthSignIn = "Sign in"
|
||||
AuthSignOut = "Sign out"
|
||||
# authentication
|
||||
################
|
||||
|
||||
AuthTitle = "Member Authentication"
|
||||
AuthWelcome = "If you are a member of this room, you can access the internal dashboard. Click on your preferred sign-in method below:"
|
||||
|
||||
AuthSignIn = "Sign in"
|
||||
AuthSignOut = "Sign out"
|
||||
|
||||
# auth with ssb
|
||||
AuthWithSSBTitle = "Sign in with SSB"
|
||||
AuthWithSSBInstruct = "Easy and secure method, if your SSB app supports it."
|
||||
AuthWithSSBWelcome = "To sign-in with your SSB identity stored on this device, press the button below which will open a compatible SSB app, if it's installed."
|
||||
AuthWithSSBInstructQR = "If your SSB app is on another device, you can scan the following QR code to sign-in with that device's SSB identity."
|
||||
AuthWithSSBError = "Sign-in failed. Please make sure you use an SSB app that supports this method of login, and click the button above within a minute after this page was opened."
|
||||
|
||||
# auth with password
|
||||
AuthFallbackTitle = "Password sign-in"
|
||||
AuthFallbackWelcome = "Signing in with username and password is only possible if the administrator has given you one, because we do not support user registration."
|
||||
AuthFallbackInstruct = "This method is an acceptable fallback, if you have a username and password."
|
||||
|
||||
# general dashboard stuff
|
||||
#########################
|
||||
|
||||
AdminDashboardTitle = "Dashboard"
|
||||
AdminDashboardWelcome = "Welcome to your dashboard"
|
||||
|
||||
# privacy modes
|
||||
###############
|
||||
|
||||
ModeOpen = "Open"
|
||||
ModeCommunity = "Community"
|
||||
ModeRestricted = "Restricted"
|
||||
|
||||
SetPrivacyModeTitle = "Set Privacy Mode"
|
||||
PrivacyModesTitle = "Privacy Modes"
|
||||
RoomsSpecification = "rooms 2 specification"
|
||||
ExplanationPrivacyModes = "The privacy mode of this room determines who can create invites and who can connect to the room. For more information, see the"
|
||||
ExplanationOpen = "Open invite codes, anyone may connect"
|
||||
ExplanationCommunity = "Members can create invites, anyone may connect"
|
||||
ExplanationRestricted = "Only admins/mods can create invites, only members may connect"
|
||||
|
||||
Settings = "Settings"
|
||||
|
||||
# banned dashboard
|
||||
##################
|
||||
|
||||
AdminDeniedKeysTitle = "Banned"
|
||||
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
|
||||
AdminDeniedKeysAdd = "Add"
|
||||
@ -46,6 +94,9 @@ AdminDeniedKeysCommentDescription = "The person who added this ban, added the fo
|
||||
AdminDeniedKeysRemoveConfirmWelcome = "Are you sure you want to remove this ban? They will will be able to access the room again."
|
||||
AdminDeniedKeysRemoveConfirmTitle = "Confirm member removal"
|
||||
|
||||
# members dashboard
|
||||
###################
|
||||
|
||||
AdminMembersTitle = "Members"
|
||||
AdminMembersWelcome = "Here you can see all the members of the room and ways to add new ones (by their SSB ID) or remove exising ones."
|
||||
AdminMembersAdd = "Add"
|
||||
@ -61,6 +112,9 @@ AdminMemberDetailsAliasRevoke = "Revoke"
|
||||
AdminMemberDetailsExclusion = "Exclusion from this room"
|
||||
AdminMemberDetailsRemove = "Remove member"
|
||||
|
||||
# invite dashboard
|
||||
##################
|
||||
|
||||
AdminInvitesTitle = "Invites"
|
||||
AdminInvitesWelcome = "Create invite tokens for people who are not yet members of this room. On this page you can also see previously created invites that are still not unclaimed by new members."
|
||||
AdminInvitesCreate = "Create new invite"
|
||||
@ -80,10 +134,8 @@ AdminInviteSuggestedAliasIsShort = "Alias:"
|
||||
AdminInviteCreatedTitle = "Invite created successfully!"
|
||||
AdminInviteCreatedInstruct = "Now, copy the link below and paste it to a friend who you want to invite to this room."
|
||||
|
||||
NavAdminLanding = "Home"
|
||||
NavAdminDashboard = "Dashboard"
|
||||
NavAdminInvites = "Invites"
|
||||
NavAdminNotices = "Notices"
|
||||
# public invites
|
||||
################
|
||||
|
||||
InviteFacade = "Join Room"
|
||||
InviteFacadeTitle = "Join Room"
|
||||
@ -101,6 +153,9 @@ InviteInsertWelcome = "You can claim your invite by inserting your SSB ID below.
|
||||
InviteConsumedTitle = "Invite accepted!"
|
||||
InviteConsumedWelcome = "You are now a member of this room. If you need a multiserver address to connect to the room, you can copy-paste the one below:"
|
||||
|
||||
# notices (mini-CMS)
|
||||
####################
|
||||
|
||||
NoticeEditTitle = "Edit Notice"
|
||||
NoticeList = "Notices"
|
||||
NoticeListWelcome = "Here you can manage the contents of the landing page and other important documents such as code of conduct and privacy policy."
|
||||
@ -111,19 +166,12 @@ NoticeNews = "News"
|
||||
NoticeDescription = "Description"
|
||||
NoticePrivacyPolicy = "Privacy Policy"
|
||||
|
||||
ModeOpen = "Open"
|
||||
ModeCommunity = "Community"
|
||||
ModeRestricted = "Restricted"
|
||||
|
||||
SetPrivacyModeTitle = "Set Privacy Mode"
|
||||
PrivacyModesTitle = "Privacy Modes"
|
||||
RoomsSpecification = "rooms 2 specification"
|
||||
ExplanationPrivacyModes = "The privacy mode of this room determines who can create invites and who can connect to the room. For more information, see the"
|
||||
ExplanationOpen = "Open invite codes, anyone may connect"
|
||||
ExplanationCommunity = "Members can create invites, anyone may connect"
|
||||
ExplanationRestricted = "Only admins/mods can create invites, only members may connect"
|
||||
|
||||
Settings = "Settings"
|
||||
# Plurals
|
||||
#########
|
||||
# These need to use this form and get {{.Count}}
|
||||
# [Label]
|
||||
# one = "singular"
|
||||
# other = "{{.Count}} things"
|
||||
|
||||
[MemberCount]
|
||||
description = "Number of members"
|
||||
|
@ -139,6 +139,18 @@ func (l Localizer) LocalizeSimple(messageID string) string {
|
||||
panic(fmt.Sprintf("i18n/error: failed to localize label %s: %s", messageID, err))
|
||||
}
|
||||
|
||||
func (l Localizer) LocalizeWithData(messageID string, tplData map[string]string) string {
|
||||
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
TemplateData: tplData,
|
||||
})
|
||||
if err == nil {
|
||||
return msg
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("i18n/error: failed to localize label %s: %s", messageID, err))
|
||||
}
|
||||
|
||||
func (l Localizer) LocalizePlurals(messageID string, pluralCount int) string {
|
||||
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
|
Loading…
Reference in New Issue
Block a user