add flash message helper
This commit is contained in:
parent
da62b1eecc
commit
cec7bc0e44
|
@ -9,6 +9,7 @@ cmd/insert-user/insert-user
|
||||||
muxrpc/test/go/testrun
|
muxrpc/test/go/testrun
|
||||||
muxrpc/test/nodejs/testrun
|
muxrpc/test/nodejs/testrun
|
||||||
web/handlers/testrun
|
web/handlers/testrun
|
||||||
|
web/handlers/admin/testrun
|
||||||
roomdb/sqlite/testrun
|
roomdb/sqlite/testrun
|
||||||
|
|
||||||
# build artifacts from node.js project web/styles
|
# build artifacts from node.js project web/styles
|
||||||
|
|
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/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59
|
||||||
go.cryptoscope.co/netwrap v0.1.1
|
go.cryptoscope.co/netwrap v0.1.1
|
||||||
go.cryptoscope.co/secretstream v1.2.2
|
go.cryptoscope.co/secretstream v1.2.2
|
||||||
go.mindeco.de v1.10.0
|
go.mindeco.de v1.11.0
|
||||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870
|
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -499,6 +499,8 @@ 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.9.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
|
||||||
go.mindeco.de v1.10.0 h1:H/bhL+dIgZZnUgBEDlKUJBisTszNiHDONeGZtGdiJJ0=
|
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 v1.10.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
|
||||||
|
go.mindeco.de v1.11.0 h1:zrNJFjRr7l+eoRywKxBetB1sgQwnrJ9NA65H/xeusTs=
|
||||||
|
go.mindeco.de v1.11.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 h1:TCI3AefMAaOYECvppn30+CfEB0Fn8IES1SKvvacc3/c=
|
||||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870/go.mod h1:OnBnV02ux4lLsZ39LID6yYLqSDp+dqTHb/3miYPkQFs=
|
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=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
|
|
@ -34,6 +34,13 @@ func (f ErrForbidden) Error() string {
|
||||||
return fmt.Sprintf("rooms/web: access denied: %s", f.Details)
|
return fmt.Sprintf("rooms/web: access denied: %s", f.Details)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrRedirect decide to not render a page during the controller
|
||||||
|
type ErrRedirect struct{ Path string }
|
||||||
|
|
||||||
|
func (err ErrRedirect) Error() string {
|
||||||
|
return fmt.Sprintf("rooms/web: redirecting to: %s", err.Path)
|
||||||
|
}
|
||||||
|
|
||||||
type PageNotFound struct{ Path string }
|
type PageNotFound struct{ Path string }
|
||||||
|
|
||||||
func (e PageNotFound) Error() string {
|
func (e PageNotFound) Error() string {
|
||||||
|
|
|
@ -31,7 +31,40 @@ func (eh *ErrorHandler) SetRenderer(r *render.Renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) {
|
func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) {
|
||||||
var ih = i18n.LocalizerFromRequest(eh.locHelper, req)
|
var redirectErr ErrRedirect
|
||||||
|
if errors.As(err, &redirectErr) {
|
||||||
|
http.Redirect(rw, req, redirectErr.Path, http.StatusTemporaryRedirect)
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func localizeError(ih *i18n.Localizer, err error) (int, string) {
|
||||||
|
|
||||||
// default, unlocalized message
|
// default, unlocalized message
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
|
@ -44,10 +77,13 @@ func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code i
|
||||||
f ErrForbidden
|
f ErrForbidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
code := http.StatusInternalServerError
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
case err == ErrNotAuthorized:
|
case err == ErrNotAuthorized:
|
||||||
code = http.StatusForbidden
|
code = http.StatusForbidden
|
||||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
msg = ih.LocalizeSimple("ErrorNotAuthorized")
|
||||||
|
|
||||||
case err == auth.ErrBadLogin:
|
case err == auth.ErrBadLogin:
|
||||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
||||||
|
@ -83,25 +119,5 @@ func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code i
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
data := errorTemplateData{
|
return code, msg
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlashHelper struct {
|
||||||
|
store sessions.Store
|
||||||
|
|
||||||
|
locHelper *i18n.Helper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlashHelper(s sessions.Store, loc *i18n.Helper) *FlashHelper {
|
||||||
|
gob.Register(FlashMessage{})
|
||||||
|
|
||||||
|
return &FlashHelper{
|
||||||
|
store: s,
|
||||||
|
locHelper: loc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const flashSession = "go-ssb-room-flash-messages"
|
||||||
|
|
||||||
|
type FlashKind uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ FlashKind = iota
|
||||||
|
// FlashError signals that a problem occured
|
||||||
|
FlashError
|
||||||
|
// FlashNotification represents a normal message (like "xyz added/updated successfull")
|
||||||
|
FlashNotification
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlashMessage struct {
|
||||||
|
Kind FlashKind
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: rethink error return - maybe panic() / maybe render package?
|
||||||
|
|
||||||
|
// AddMessage expects a i18n label, translates it and adds it as a FlashNotification
|
||||||
|
func (fh FlashHelper) AddMessage(rw http.ResponseWriter, req *http.Request, label string) {
|
||||||
|
session, err := fh.store.Get(req, flashSession)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("flashHelper: failed to get session: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ih := fh.locHelper.FromRequest(req)
|
||||||
|
|
||||||
|
session.AddFlash(FlashMessage{
|
||||||
|
Kind: FlashNotification,
|
||||||
|
Message: ih.LocalizeSimple(label),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := session.Save(req, rw); err != nil {
|
||||||
|
panic(fmt.Errorf("flashHelper: failed to save session: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddError adds a FlashError and translates the passed err using localizeError()
|
||||||
|
func (fh FlashHelper) AddError(rw http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
session, getErr := fh.store.Get(req, flashSession)
|
||||||
|
if getErr != nil {
|
||||||
|
panic(fmt.Errorf("flashHelper: failed to get session: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
ih := fh.locHelper.FromRequest(req)
|
||||||
|
|
||||||
|
_, msg := localizeError(ih, err)
|
||||||
|
|
||||||
|
session.AddFlash(FlashMessage{
|
||||||
|
Kind: FlashError,
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
if err := session.Save(req, rw); err != nil {
|
||||||
|
panic(fmt.Errorf("flashHelper: failed to save session: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all the FlashMessages, emptys and updates the store
|
||||||
|
func (fh FlashHelper) GetAll(rw http.ResponseWriter, req *http.Request) ([]FlashMessage, error) {
|
||||||
|
session, err := fh.store.Get(req, flashSession)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opaqueFlashes := session.Flashes()
|
||||||
|
|
||||||
|
flashes := make([]FlashMessage, len(opaqueFlashes))
|
||||||
|
|
||||||
|
for i, of := range opaqueFlashes {
|
||||||
|
f, ok := of.(FlashMessage)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("GetFlashes: failed to unpack flash: %T", of)
|
||||||
|
}
|
||||||
|
|
||||||
|
flashes[i].Kind = f.Kind
|
||||||
|
flashes[i].Message = f.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
err = session.Save(req, rw)
|
||||||
|
|
||||||
|
return flashes, err
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -20,6 +19,8 @@ import (
|
||||||
type aliasesHandler struct {
|
type aliasesHandler struct {
|
||||||
r *render.Renderer
|
r *render.Renderer
|
||||||
|
|
||||||
|
flashes *weberrors.FlashHelper
|
||||||
|
|
||||||
db roomdb.AliasesService
|
db roomdb.AliasesService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +39,8 @@ func (h aliasesHandler) revokeConfirm(rw http.ResponseWriter, req *http.Request)
|
||||||
|
|
||||||
entry, err := h.db.GetByID(req.Context(), id)
|
entry, err := h.db.GetByID(req.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, roomdb.ErrNotFound) {
|
h.flashes.AddError(rw, req, err)
|
||||||
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
return nil, weberrors.ErrRedirect{Path: redirectToAliases}
|
||||||
return nil, ErrRedirected
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
@ -65,17 +63,12 @@ func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status := http.StatusFound
|
status := http.StatusTemporaryRedirect
|
||||||
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
h.flashes.AddError(rw, req, err)
|
||||||
// TODO: flash error
|
} else {
|
||||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
h.flashes.AddMessage(rw, req, "AdminAliasRevoked")
|
||||||
return
|
|
||||||
}
|
|
||||||
status = http.StatusNotFound
|
|
||||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(rw, req, redirectToAliases, status)
|
http.Redirect(rw, req, redirectToAliases, status)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -23,10 +22,9 @@ func TestAliasesRevokeConfirmation(t *testing.T) {
|
||||||
testEntry := roomdb.Alias{ID: 666, Name: "the-test-name", Feed: *testKey}
|
testEntry := roomdb.Alias{ID: 666, Name: "the-test-name", Feed: *testKey}
|
||||||
ts.AliasesDB.GetByIDReturns(testEntry, nil)
|
ts.AliasesDB.GetByIDReturns(testEntry, nil)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRevokeConfirm := ts.URLTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
||||||
urlRevokeConfirm := urlTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(urlRevokeConfirm.String())
|
html, resp := ts.Client.GetHTML(urlRevokeConfirm)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||||
|
@ -40,10 +38,8 @@ func TestAliasesRevokeConfirmation(t *testing.T) {
|
||||||
action, ok := form.Attr("action")
|
action, ok := form.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminAliasesRevoke).URL()
|
addURL := ts.URLTo(router.AdminAliasesRevoke)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
|
|
||||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||||
{Name: "name", Type: "hidden", Value: testEntry.Name},
|
{Name: "name", Type: "hidden", Value: testEntry.Name},
|
||||||
|
@ -54,14 +50,23 @@ func TestAliasesRevoke(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRevoke := ts.URLTo(router.AdminAliasesRevoke)
|
||||||
urlRevoke := urlTo(router.AdminAliasesRevoke)
|
overviewURL := ts.URLTo(router.AdminAliasesOverview)
|
||||||
|
|
||||||
ts.AliasesDB.RevokeReturns(nil)
|
ts.AliasesDB.RevokeReturns(nil)
|
||||||
|
|
||||||
addVals := url.Values{"name": []string{"the-name"}}
|
addVals := url.Values{"name": []string{"the-name"}}
|
||||||
rec := ts.Client.PostForm(urlRevoke.String(), addVals)
|
rec := ts.Client.PostForm(urlRevoke, addVals)
|
||||||
a.Equal(http.StatusFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
a.Equal(overviewURL.Path, rec.Header().Get("Location"))
|
||||||
|
a.True(len(rec.Result().Cookies()) > 0, "got a cookie")
|
||||||
|
|
||||||
|
// check flash messages
|
||||||
|
doc, resp := ts.Client.GetHTML(overviewURL)
|
||||||
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
flashes := doc.Find("#flashes-list").Children()
|
||||||
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("AdminAliasRevoked", flashes.Text())
|
||||||
|
|
||||||
a.Equal(1, ts.AliasesDB.RevokeCallCount())
|
a.Equal(1, ts.AliasesDB.RevokeCallCount())
|
||||||
_, theName := ts.AliasesDB.RevokeArgsForCall(0)
|
_, theName := ts.AliasesDB.RevokeArgsForCall(0)
|
||||||
|
@ -70,7 +75,15 @@ func TestAliasesRevoke(t *testing.T) {
|
||||||
// now for unknown ID
|
// now for unknown ID
|
||||||
ts.AliasesDB.RevokeReturns(roomdb.ErrNotFound)
|
ts.AliasesDB.RevokeReturns(roomdb.ErrNotFound)
|
||||||
addVals = url.Values{"name": []string{"nope"}}
|
addVals = url.Values{"name": []string{"nope"}}
|
||||||
rec = ts.Client.PostForm(urlRevoke.String(), addVals)
|
rec = ts.Client.PostForm(urlRevoke, addVals)
|
||||||
a.Equal(http.StatusNotFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
//TODO: update redirect code with flash errors
|
a.Equal(overviewURL.Path, rec.Header().Get("Location"))
|
||||||
|
a.True(len(rec.Result().Cookies()) > 0, "got a cookie")
|
||||||
|
|
||||||
|
// check flash messages
|
||||||
|
doc, resp = ts.Client.GetHTML(overviewURL)
|
||||||
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
flashes = doc.Find("#flashes-list").Children()
|
||||||
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("ErrorNotFound", flashes.Text())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
type deniedKeysHandler struct {
|
type deniedKeysHandler struct {
|
||||||
r *render.Renderer
|
r *render.Renderer
|
||||||
|
|
||||||
|
flashes *weberrors.FlashHelper
|
||||||
|
|
||||||
db roomdb.DeniedKeysService
|
db roomdb.DeniedKeysService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +42,8 @@ func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
h.flashes.AddError(w, req, err)
|
||||||
|
http.Redirect(w, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,16 +52,12 @@ func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
err = h.db.Add(req.Context(), *newEntryParsed, comment)
|
err = h.db.Add(req.Context(), *newEntryParsed, comment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := http.StatusInternalServerError
|
h.flashes.AddError(w, req, err)
|
||||||
var aa roomdb.ErrAlreadyAdded
|
} else {
|
||||||
if errors.As(err, &aa) {
|
h.flashes.AddMessage(w, req, "AdminDeniedKeysAdded")
|
||||||
code = http.StatusBadRequest
|
|
||||||
}
|
|
||||||
h.r.Error(w, req, code, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, req, redirectToDeniedKeys, http.StatusFound)
|
http.Redirect(w, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h deniedKeysHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (h deniedKeysHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
|
@ -77,13 +76,14 @@ func (h deniedKeysHandler) overview(rw http.ResponseWriter, req *http.Request) (
|
||||||
}
|
}
|
||||||
|
|
||||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||||
|
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return pageData, nil
|
return pageData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to render package so that we can decide to not render a page during the controller
|
|
||||||
var ErrRedirected = errors.New("render: not rendered but redirected")
|
|
||||||
|
|
||||||
func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -93,12 +93,8 @@ func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Reque
|
||||||
|
|
||||||
entry, err := h.db.GetByID(req.Context(), id)
|
entry, err := h.db.GetByID(req.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, roomdb.ErrNotFound) {
|
h.flashes.AddError(rw, req, err)
|
||||||
// TODO "flash" errors
|
return nil, weberrors.ErrRedirect{Path: redirectToDeniedKeys}
|
||||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
|
||||||
return nil, ErrRedirected
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
@ -112,7 +108,7 @@ func (h deniedKeysHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||||
// TODO "flash" errors
|
// TODO "flash" errors
|
||||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +116,7 @@ func (h deniedKeysHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||||
// TODO "flash" errors
|
// TODO "flash" errors
|
||||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -24,10 +20,9 @@ func TestDeniedKeysEmpty(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.AdminDeniedKeysOverview).URL()
|
url := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||||
a.Nil(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(url)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -41,10 +36,9 @@ func TestDeniedKeysAdd(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
listURL, err := ts.Router.Get(router.AdminDeniedKeysOverview).URL()
|
listURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||||
a.NoError(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(listURL.String())
|
html, resp := ts.Client.GetHTML(listURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
formSelection := html.Find("form#add-entry")
|
formSelection := html.Find("form#add-entry")
|
||||||
|
@ -57,10 +51,8 @@ func TestDeniedKeysAdd(t *testing.T) {
|
||||||
action, ok := formSelection.Attr("action")
|
action, ok := formSelection.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysAdd).URL()
|
addURL := ts.URLTo(router.AdminDeniedKeysAdd)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
|
|
||||||
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
||||||
{Name: "pub_key", Type: "text"},
|
{Name: "pub_key", Type: "text"},
|
||||||
|
@ -73,8 +65,8 @@ func TestDeniedKeysAdd(t *testing.T) {
|
||||||
// just any key that looks valid
|
// just any key that looks valid
|
||||||
"pub_key": []string{newKey},
|
"pub_key": []string{newKey},
|
||||||
}
|
}
|
||||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
rec := ts.Client.PostForm(addURL, addVals)
|
||||||
a.Equal(http.StatusFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
|
||||||
a.Equal(1, ts.DeniedKeysDB.AddCallCount())
|
a.Equal(1, ts.DeniedKeysDB.AddCallCount())
|
||||||
_, addedKey, addedComment := ts.DeniedKeysDB.AddArgsForCall(0)
|
_, addedKey, addedComment := ts.DeniedKeysDB.AddArgsForCall(0)
|
||||||
|
@ -85,29 +77,30 @@ func TestDeniedKeysAdd(t *testing.T) {
|
||||||
func TestDeniedKeysDontAddInvalid(t *testing.T) {
|
func TestDeniedKeysDontAddInvalid(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
r := require.New(t)
|
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysAdd).URL()
|
addURL := ts.URLTo(router.AdminDeniedKeysAdd)
|
||||||
a.NoError(err)
|
|
||||||
|
|
||||||
newKey := "@some-garbage"
|
newKey := "@some-garbage"
|
||||||
addVals := url.Values{
|
addVals := url.Values{
|
||||||
"comment": []string{"some-comment"},
|
"comment": []string{"some-comment"},
|
||||||
"pub_key": []string{newKey},
|
"pub_key": []string{newKey},
|
||||||
}
|
}
|
||||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
rec := ts.Client.PostForm(addURL, addVals)
|
||||||
a.Equal(http.StatusBadRequest, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
|
||||||
a.Equal(0, ts.DeniedKeysDB.AddCallCount())
|
a.Equal(0, ts.DeniedKeysDB.AddCallCount(), "did not call add")
|
||||||
|
|
||||||
doc, err := goquery.NewDocumentFromReader(rec.Body)
|
listURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||||
r.NoError(err)
|
res := rec.Result()
|
||||||
|
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||||
|
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||||
|
|
||||||
expErr := `bad request: feedRef: couldn't parse "@some-garbage"`
|
doc, resp := ts.Client.GetHTML(listURL)
|
||||||
gotMsg := doc.Find("#errBody").Text()
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
if !a.True(strings.HasPrefix(gotMsg, expErr), "did not find errBody") {
|
|
||||||
t.Log(gotMsg)
|
flashes := doc.Find("#flashes-list").Children()
|
||||||
}
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("ErrorBadRequest", flashes.Text())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeniedKeys(t *testing.T) {
|
func TestDeniedKeys(t *testing.T) {
|
||||||
|
@ -121,7 +114,9 @@ func TestDeniedKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.DeniedKeysDB.ListReturns(lst, nil)
|
ts.DeniedKeysDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML("/denied")
|
deniedOverviewURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML(deniedOverviewURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -137,7 +132,7 @@ func TestDeniedKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.DeniedKeysDB.ListReturns(lst, nil)
|
ts.DeniedKeysDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML("/denied")
|
html, resp = ts.Client.GetHTML(deniedOverviewURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -164,10 +159,9 @@ func TestDeniedKeysRemoveConfirmation(t *testing.T) {
|
||||||
testEntry := roomdb.ListEntry{ID: 666, PubKey: *testKey}
|
testEntry := roomdb.ListEntry{ID: 666, PubKey: *testKey}
|
||||||
ts.DeniedKeysDB.GetByIDReturns(testEntry, nil)
|
ts.DeniedKeysDB.GetByIDReturns(testEntry, nil)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRemoveConfirm := ts.URLTo(router.AdminDeniedKeysRemoveConfirm, "id", 3)
|
||||||
urlRemoveConfirm := urlTo(router.AdminDeniedKeysRemoveConfirm, "id", 3)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm.String())
|
html, resp := ts.Client.GetHTML(urlRemoveConfirm)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||||
|
@ -181,10 +175,8 @@ func TestDeniedKeysRemoveConfirmation(t *testing.T) {
|
||||||
action, ok := form.Attr("action")
|
action, ok := form.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysRemove).URL()
|
addURL := ts.URLTo(router.AdminDeniedKeysRemove)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
|
|
||||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||||
{Name: "id", Type: "hidden", Value: "666"},
|
{Name: "id", Type: "hidden", Value: "666"},
|
||||||
|
@ -195,13 +187,12 @@ func TestDeniedKeysRemove(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRemove := ts.URLTo(router.AdminDeniedKeysRemove)
|
||||||
urlRemove := urlTo(router.AdminDeniedKeysRemove)
|
|
||||||
|
|
||||||
ts.DeniedKeysDB.RemoveIDReturns(nil)
|
ts.DeniedKeysDB.RemoveIDReturns(nil)
|
||||||
|
|
||||||
addVals := url.Values{"id": []string{"666"}}
|
addVals := url.Values{"id": []string{"666"}}
|
||||||
rec := ts.Client.PostForm(urlRemove.String(), addVals)
|
rec := ts.Client.PostForm(urlRemove, addVals)
|
||||||
a.Equal(http.StatusFound, rec.Code)
|
a.Equal(http.StatusFound, rec.Code)
|
||||||
|
|
||||||
a.Equal(1, ts.DeniedKeysDB.RemoveIDCallCount())
|
a.Equal(1, ts.DeniedKeysDB.RemoveIDCallCount())
|
||||||
|
@ -211,7 +202,7 @@ func TestDeniedKeysRemove(t *testing.T) {
|
||||||
// now for unknown ID
|
// now for unknown ID
|
||||||
ts.DeniedKeysDB.RemoveIDReturns(roomdb.ErrNotFound)
|
ts.DeniedKeysDB.RemoveIDReturns(roomdb.ErrNotFound)
|
||||||
addVals = url.Values{"id": []string{"667"}}
|
addVals = url.Values{"id": []string{"667"}}
|
||||||
rec = ts.Client.PostForm(urlRemove.String(), addVals)
|
rec = ts.Client.PostForm(urlRemove, addVals)
|
||||||
a.Equal(http.StatusNotFound, rec.Code)
|
a.Equal(http.StatusNotFound, rec.Code)
|
||||||
//TODO: update redirect code with flash errors
|
//TODO: update redirect code with flash errors
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"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/roomstate"
|
||||||
|
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTMLTemplates define the list of files the template system should load.
|
// HTMLTemplates define the list of files the template system should load.
|
||||||
|
@ -60,11 +61,11 @@ func Handler(
|
||||||
domainName string,
|
domainName string,
|
||||||
r *render.Renderer,
|
r *render.Renderer,
|
||||||
roomState *roomstate.Manager,
|
roomState *roomstate.Manager,
|
||||||
|
fh *weberrors.FlashHelper,
|
||||||
dbs Databases,
|
dbs Databases,
|
||||||
) http.Handler {
|
) http.Handler {
|
||||||
mux := &http.ServeMux{}
|
mux := &http.ServeMux{}
|
||||||
|
|
||||||
// TODO: configure 404 handler
|
|
||||||
var dashboardHandler = dashboardHandler{
|
var dashboardHandler = dashboardHandler{
|
||||||
r: r,
|
r: r,
|
||||||
dbs: dbs,
|
dbs: dbs,
|
||||||
|
@ -84,14 +85,18 @@ func Handler(
|
||||||
}))
|
}))
|
||||||
|
|
||||||
var ah = aliasesHandler{
|
var ah = aliasesHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
flashes: fh,
|
||||||
|
|
||||||
db: dbs.Aliases,
|
db: dbs.Aliases,
|
||||||
}
|
}
|
||||||
mux.HandleFunc("/aliases/revoke/confirm", r.HTML("admin/aliases-revoke-confirm.tmpl", ah.revokeConfirm))
|
mux.HandleFunc("/aliases/revoke/confirm", r.HTML("admin/aliases-revoke-confirm.tmpl", ah.revokeConfirm))
|
||||||
mux.HandleFunc("/aliases/revoke", ah.revoke)
|
mux.HandleFunc("/aliases/revoke", ah.revoke)
|
||||||
|
|
||||||
var dh = deniedKeysHandler{
|
var dh = deniedKeysHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
flashes: fh,
|
||||||
|
|
||||||
db: dbs.DeniedKeys,
|
db: dbs.DeniedKeys,
|
||||||
}
|
}
|
||||||
mux.HandleFunc("/denied", r.HTML("admin/denied-keys.tmpl", dh.overview))
|
mux.HandleFunc("/denied", r.HTML("admin/denied-keys.tmpl", dh.overview))
|
||||||
|
@ -100,7 +105,9 @@ func Handler(
|
||||||
mux.HandleFunc("/denied/remove", dh.remove)
|
mux.HandleFunc("/denied/remove", dh.remove)
|
||||||
|
|
||||||
var mh = membersHandler{
|
var mh = membersHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
flashes: fh,
|
||||||
|
|
||||||
db: dbs.Members,
|
db: dbs.Members,
|
||||||
}
|
}
|
||||||
mux.HandleFunc("/member", r.HTML("admin/member.tmpl", mh.details))
|
mux.HandleFunc("/member", r.HTML("admin/member.tmpl", mh.details))
|
||||||
|
@ -111,7 +118,9 @@ func Handler(
|
||||||
mux.HandleFunc("/members/remove", mh.remove)
|
mux.HandleFunc("/members/remove", mh.remove)
|
||||||
|
|
||||||
var ih = invitesHandler{
|
var ih = invitesHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
flashes: fh,
|
||||||
|
|
||||||
db: dbs.Invites,
|
db: dbs.Invites,
|
||||||
config: dbs.Config,
|
config: dbs.Config,
|
||||||
|
|
||||||
|
@ -124,7 +133,9 @@ func Handler(
|
||||||
mux.HandleFunc("/invites/revoke", ih.revoke)
|
mux.HandleFunc("/invites/revoke", ih.revoke)
|
||||||
|
|
||||||
var nh = noticeHandler{
|
var nh = noticeHandler{
|
||||||
r: r,
|
r: r,
|
||||||
|
flashes: fh,
|
||||||
|
|
||||||
noticeDB: dbs.Notices,
|
noticeDB: dbs.Notices,
|
||||||
pinnedDB: dbs.PinnedNotices,
|
pinnedDB: dbs.PinnedNotices,
|
||||||
}
|
}
|
||||||
|
@ -133,6 +144,11 @@ func Handler(
|
||||||
mux.HandleFunc("/notice/translation/add", nh.addTranslation)
|
mux.HandleFunc("/notice/translation/add", nh.addTranslation)
|
||||||
mux.HandleFunc("/notice/save", nh.save)
|
mux.HandleFunc("/notice/save", nh.save)
|
||||||
|
|
||||||
|
// path:/ matches everything that isn't registerd (ie. its the "Not Found handler")
|
||||||
|
mux.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
r.Error(rw, req, 404, weberrors.ErrNotFound{What: req.URL.Path})
|
||||||
|
}))
|
||||||
|
|
||||||
return customStripPrefix("/admin", mux)
|
return customStripPrefix("/admin", mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,9 @@ func TestDashoard(t *testing.T) {
|
||||||
ts.InvitesDB.CountReturns(3, nil) // 3 invites
|
ts.InvitesDB.CountReturns(3, nil) // 3 invites
|
||||||
ts.DeniedKeysDB.CountReturns(2, nil) // 2 banned
|
ts.DeniedKeysDB.CountReturns(2, nil) // 2 banned
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.AdminDashboard).URL()
|
dashURL := ts.URLTo(router.AdminDashboard)
|
||||||
a.Nil(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(dashURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
a.Equal("1", html.Find("#online-count").Text())
|
a.Equal("1", html.Find("#online-count").Text())
|
||||||
|
|
|
@ -17,7 +17,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type invitesHandler struct {
|
type invitesHandler struct {
|
||||||
r *render.Renderer
|
r *render.Renderer
|
||||||
|
flashes *weberrors.FlashHelper
|
||||||
|
|
||||||
db roomdb.InvitesService
|
db roomdb.InvitesService
|
||||||
config roomdb.RoomConfig
|
config roomdb.RoomConfig
|
||||||
|
@ -42,6 +43,10 @@ func (h invitesHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
||||||
}
|
}
|
||||||
|
|
||||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||||
|
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return pageData, nil
|
return pageData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
)
|
)
|
||||||
|
@ -28,7 +27,9 @@ func TestInvitesOverview(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.InvitesDB.ListReturns(lst, nil)
|
ts.InvitesDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML("/invites")
|
invitesOverviewURL := ts.URLTo(router.AdminInvitesOverview)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML(invitesOverviewURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -46,7 +47,7 @@ func TestInvitesOverview(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.InvitesDB.ListReturns(lst, nil)
|
ts.InvitesDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML("/invites")
|
html, resp = ts.Client.GetHTML(invitesOverviewURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -68,10 +69,9 @@ func TestInvitesCreateForm(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.AdminInvitesOverview).URL()
|
overviewURL := ts.URLTo(router.AdminInvitesOverview)
|
||||||
a.Nil(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(overviewURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -89,10 +89,8 @@ func TestInvitesCreateForm(t *testing.T) {
|
||||||
action, ok := formSelection.Attr("action")
|
action, ok := formSelection.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminInvitesCreate).URL()
|
addURL := ts.URLTo(router.AdminInvitesCreate)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvitesCreate(t *testing.T) {
|
func TestInvitesCreate(t *testing.T) {
|
||||||
|
@ -100,13 +98,12 @@ func TestInvitesCreate(t *testing.T) {
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
r := require.New(t)
|
r := require.New(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRemove := ts.URLTo(router.AdminInvitesCreate)
|
||||||
urlRemove := urlTo(router.AdminInvitesCreate)
|
|
||||||
|
|
||||||
testInvite := "your-fake-test-invite"
|
testInvite := "your-fake-test-invite"
|
||||||
ts.InvitesDB.CreateReturns(testInvite, nil)
|
ts.InvitesDB.CreateReturns(testInvite, nil)
|
||||||
|
|
||||||
rec := ts.Client.PostForm(urlRemove.String(), url.Values{})
|
rec := ts.Client.PostForm(urlRemove, url.Values{})
|
||||||
a.Equal(http.StatusOK, rec.Code)
|
a.Equal(http.StatusOK, rec.Code)
|
||||||
|
|
||||||
r.Equal(1, ts.InvitesDB.CreateCallCount(), "expected one invites.Create call")
|
r.Equal(1, ts.InvitesDB.CreateCallCount(), "expected one invites.Create call")
|
||||||
|
@ -121,9 +118,7 @@ func TestInvitesCreate(t *testing.T) {
|
||||||
{"#welcome", "AdminInviteCreatedTitle" + "AdminInviteCreatedInstruct"},
|
{"#welcome", "AdminInviteCreatedTitle" + "AdminInviteCreatedInstruct"},
|
||||||
})
|
})
|
||||||
|
|
||||||
wantURL := urlTo(router.CompleteInviteFacade, "token", testInvite)
|
wantURL := ts.URLTo(router.CompleteInviteFacade, "token", testInvite)
|
||||||
wantURL.Host = ts.Domain
|
|
||||||
wantURL.Scheme = "https"
|
|
||||||
|
|
||||||
shownLink := doc.Find("#invite-facade-link").Text()
|
shownLink := doc.Find("#invite-facade-link").Text()
|
||||||
a.Equal(wantURL.String(), shownLink)
|
a.Equal(wantURL.String(), shownLink)
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
type membersHandler struct {
|
type membersHandler struct {
|
||||||
r *render.Renderer
|
r *render.Renderer
|
||||||
|
|
||||||
|
flashes *weberrors.FlashHelper
|
||||||
|
|
||||||
db roomdb.MembersService
|
db roomdb.MembersService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,22 +46,19 @@ func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
||||||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
h.flashes.AddError(w, req, err)
|
||||||
|
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.db.Add(req.Context(), *newEntryParsed, roomdb.RoleMember)
|
_, err = h.db.Add(req.Context(), *newEntryParsed, roomdb.RoleMember)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := http.StatusInternalServerError
|
h.flashes.AddError(w, req, err)
|
||||||
var aa roomdb.ErrAlreadyAdded
|
} else {
|
||||||
if errors.As(err, &aa) {
|
h.flashes.AddMessage(w, req, "AdminMemberAdded")
|
||||||
code = http.StatusBadRequest
|
|
||||||
}
|
|
||||||
h.r.Error(w, req, code, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, req, redirectToMembers, http.StatusFound)
|
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -98,11 +97,12 @@ func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
if err := h.db.SetRole(req.Context(), memberID, role); err != nil {
|
if err := h.db.SetRole(req.Context(), memberID, role); err != nil {
|
||||||
err = weberrors.DatabaseError{Reason: err}
|
err = weberrors.DatabaseError{Reason: err}
|
||||||
// TODO: not found error
|
|
||||||
h.r.Error(w, req, http.StatusInternalServerError, err)
|
h.r.Error(w, req, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.flashes.AddMessage(w, req, "AdminMemberUpdated")
|
||||||
|
|
||||||
urlTo := web.NewURLTo(router.CompleteApp())
|
urlTo := web.NewURLTo(router.CompleteApp())
|
||||||
memberDetailsURL := urlTo(router.AdminMemberDetails, "id", memberID).String()
|
memberDetailsURL := urlTo(router.AdminMemberDetails, "id", memberID).String()
|
||||||
http.Redirect(w, req, memberDetailsURL, http.StatusTemporaryRedirect)
|
http.Redirect(w, req, memberDetailsURL, http.StatusTemporaryRedirect)
|
||||||
|
@ -127,6 +127,11 @@ func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
||||||
|
|
||||||
pageData["AllRoles"] = []roomdb.Role{roomdb.RoleMember, roomdb.RoleModerator, roomdb.RoleAdmin}
|
pageData["AllRoles"] = []roomdb.Role{roomdb.RoleMember, roomdb.RoleModerator, roomdb.RoleAdmin}
|
||||||
|
|
||||||
|
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return pageData, nil
|
return pageData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +167,8 @@ func (h membersHandler) removeConfirm(rw http.ResponseWriter, req *http.Request)
|
||||||
entry, err := h.db.GetByID(req.Context(), id)
|
entry, err := h.db.GetByID(req.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, roomdb.ErrNotFound) {
|
if errors.Is(err, roomdb.ErrNotFound) {
|
||||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
h.flashes.AddError(rw, req, err)
|
||||||
return nil, ErrRedirected
|
return nil, weberrors.ErrRedirect{Path: redirectToMembers}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -175,32 +180,32 @@ func (h membersHandler) removeConfirm(rw http.ResponseWriter, req *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h membersHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
func (h membersHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
||||||
err := req.ParseForm()
|
if req.Method != "POST" {
|
||||||
if err != nil {
|
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||||
|
h.r.Error(rw, req, http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||||
// TODO "flash" errors
|
h.r.Error(rw, req, http.StatusBadRequest, err)
|
||||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := strconv.ParseInt(req.FormValue("id"), 10, 64)
|
id, err := strconv.ParseInt(req.FormValue("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||||
// TODO "flash" errors
|
h.flashes.AddError(rw, req, err)
|
||||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
http.Redirect(rw, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
status := http.StatusFound
|
|
||||||
err = h.db.RemoveID(req.Context(), id)
|
err = h.db.RemoveID(req.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
h.flashes.AddError(rw, req, err)
|
||||||
// TODO "flash" errors
|
} else {
|
||||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
h.flashes.AddMessage(rw, req, "AdminMemberRemoved")
|
||||||
return
|
|
||||||
}
|
|
||||||
status = http.StatusNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(rw, req, redirectToMembers, status)
|
http.Redirect(rw, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -23,10 +18,9 @@ func TestMembersEmpty(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.AdminMembersOverview).URL()
|
url := ts.URLTo(router.AdminMembersOverview)
|
||||||
a.Nil(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(url)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -40,10 +34,9 @@ func TestMembersAdd(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
listURL, err := ts.Router.Get(router.AdminMembersOverview).URL()
|
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||||
a.NoError(err)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(listURL.String())
|
html, resp := ts.Client.GetHTML(listURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
formSelection := html.Find("form#add-entry")
|
formSelection := html.Find("form#add-entry")
|
||||||
|
@ -56,10 +49,8 @@ func TestMembersAdd(t *testing.T) {
|
||||||
action, ok := formSelection.Attr("action")
|
action, ok := formSelection.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminMembersAdd).URL()
|
addURL := ts.URLTo(router.AdminMembersAdd)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
|
|
||||||
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
||||||
{Name: "pub_key", Type: "text"},
|
{Name: "pub_key", Type: "text"},
|
||||||
|
@ -70,8 +61,8 @@ func TestMembersAdd(t *testing.T) {
|
||||||
// just any key that looks valid
|
// just any key that looks valid
|
||||||
"pub_key": []string{newKey},
|
"pub_key": []string{newKey},
|
||||||
}
|
}
|
||||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
rec := ts.Client.PostForm(addURL, addVals)
|
||||||
a.Equal(http.StatusFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
|
||||||
a.Equal(1, ts.MembersDB.AddCallCount())
|
a.Equal(1, ts.MembersDB.AddCallCount())
|
||||||
_, addedPubKey, addedRole := ts.MembersDB.AddArgsForCall(0)
|
_, addedPubKey, addedRole := ts.MembersDB.AddArgsForCall(0)
|
||||||
|
@ -83,29 +74,31 @@ func TestMembersAdd(t *testing.T) {
|
||||||
func TestMembersDontAddInvalid(t *testing.T) {
|
func TestMembersDontAddInvalid(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
r := require.New(t)
|
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminMembersAdd).URL()
|
addURL := ts.URLTo(router.AdminMembersAdd)
|
||||||
a.NoError(err)
|
|
||||||
|
|
||||||
newKey := "@some-garbage"
|
newKey := "@some-garbage"
|
||||||
addVals := url.Values{
|
addVals := url.Values{
|
||||||
"nick": []string{"some-test-nick"},
|
"nick": []string{"some-test-nick"},
|
||||||
"pub_key": []string{newKey},
|
"pub_key": []string{newKey},
|
||||||
}
|
}
|
||||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
rec := ts.Client.PostForm(addURL, addVals)
|
||||||
a.Equal(http.StatusBadRequest, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
|
||||||
a.Equal(0, ts.MembersDB.AddCallCount())
|
a.Equal(0, ts.MembersDB.AddCallCount())
|
||||||
|
|
||||||
doc, err := goquery.NewDocumentFromReader(rec.Body)
|
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||||
r.NoError(err)
|
res := rec.Result()
|
||||||
|
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||||
|
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||||
|
|
||||||
|
doc, resp := ts.Client.GetHTML(listURL)
|
||||||
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
flashes := doc.Find("#flashes-list").Children()
|
||||||
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("ErrorBadRequest", flashes.Text())
|
||||||
|
|
||||||
expErr := `bad public key: feedRef: couldn't parse "@some-garbage"`
|
|
||||||
gotMsg := doc.Find("#errBody").Text()
|
|
||||||
if !a.True(strings.HasPrefix(gotMsg, expErr), "did not find errBody") {
|
|
||||||
t.Log(gotMsg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMembers(t *testing.T) {
|
func TestMembers(t *testing.T) {
|
||||||
|
@ -119,7 +112,9 @@ func TestMembers(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.MembersDB.ListReturns(lst, nil)
|
ts.MembersDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML("/members")
|
membersOveriwURL := ts.URLTo(router.AdminMembersOverview)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML(membersOveriwURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -135,7 +130,7 @@ func TestMembers(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.MembersDB.ListReturns(lst, nil)
|
ts.MembersDB.ListReturns(lst, nil)
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML("/members")
|
html, resp = ts.Client.GetHTML(membersOveriwURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -218,10 +213,9 @@ func TestMembersRemoveConfirmation(t *testing.T) {
|
||||||
testEntry := roomdb.Member{ID: 666, PubKey: *testKey}
|
testEntry := roomdb.Member{ID: 666, PubKey: *testKey}
|
||||||
ts.MembersDB.GetByIDReturns(testEntry, nil)
|
ts.MembersDB.GetByIDReturns(testEntry, nil)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRemoveConfirm := ts.URLTo(router.AdminMembersRemoveConfirm, "id", 3)
|
||||||
urlRemoveConfirm := urlTo(router.AdminMembersRemoveConfirm, "id", 3)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm.String())
|
html, resp := ts.Client.GetHTML(urlRemoveConfirm)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||||
|
@ -235,10 +229,8 @@ func TestMembersRemoveConfirmation(t *testing.T) {
|
||||||
action, ok := form.Attr("action")
|
action, ok := form.Attr("action")
|
||||||
a.True(ok, "form has action set")
|
a.True(ok, "form has action set")
|
||||||
|
|
||||||
addURL, err := ts.Router.Get(router.AdminMembersRemove).URL()
|
addURL := ts.URLTo(router.AdminMembersRemove)
|
||||||
a.NoError(err)
|
a.Equal(addURL.Path, action)
|
||||||
|
|
||||||
a.Equal(addURL.String(), action)
|
|
||||||
|
|
||||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||||
{Name: "id", Type: "hidden", Value: "666"},
|
{Name: "id", Type: "hidden", Value: "666"},
|
||||||
|
@ -249,23 +241,46 @@ func TestMembersRemove(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
urlRemove := ts.URLTo(router.AdminMembersRemove)
|
||||||
urlRemove := urlTo(router.AdminMembersRemove)
|
|
||||||
|
|
||||||
ts.MembersDB.RemoveIDReturns(nil)
|
ts.MembersDB.RemoveIDReturns(nil)
|
||||||
|
|
||||||
addVals := url.Values{"id": []string{"666"}}
|
addVals := url.Values{"id": []string{"666"}}
|
||||||
rec := ts.Client.PostForm(urlRemove.String(), addVals)
|
rec := ts.Client.PostForm(urlRemove, addVals)
|
||||||
a.Equal(http.StatusFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
|
|
||||||
a.Equal(1, ts.MembersDB.RemoveIDCallCount())
|
a.Equal(1, ts.MembersDB.RemoveIDCallCount())
|
||||||
_, theID := ts.MembersDB.RemoveIDArgsForCall(0)
|
_, theID := ts.MembersDB.RemoveIDArgsForCall(0)
|
||||||
a.EqualValues(666, theID)
|
a.EqualValues(666, theID)
|
||||||
|
|
||||||
|
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||||
|
// check flash message
|
||||||
|
res := rec.Result()
|
||||||
|
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||||
|
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||||
|
|
||||||
|
doc, resp := ts.Client.GetHTML(listURL)
|
||||||
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
flashes := doc.Find("#flashes-list").Children()
|
||||||
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("AdminMemberRemoved", flashes.Text())
|
||||||
|
|
||||||
// now for unknown ID
|
// now for unknown ID
|
||||||
ts.MembersDB.RemoveIDReturns(roomdb.ErrNotFound)
|
ts.MembersDB.RemoveIDReturns(roomdb.ErrNotFound)
|
||||||
addVals = url.Values{"id": []string{"667"}}
|
addVals = url.Values{"id": []string{"667"}}
|
||||||
rec = ts.Client.PostForm(urlRemove.String(), addVals)
|
rec = ts.Client.PostForm(urlRemove, addVals)
|
||||||
a.Equal(http.StatusNotFound, rec.Code)
|
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||||
//TODO: update redirect code with flash errors
|
|
||||||
|
// check flash message
|
||||||
|
res = rec.Result()
|
||||||
|
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||||
|
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||||
|
|
||||||
|
doc, resp = ts.Client.GetHTML(listURL)
|
||||||
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
flashes = doc.Find("#flashes-list").Children()
|
||||||
|
a.Equal(1, flashes.Length())
|
||||||
|
a.Equal("ErrorNotFound", flashes.Text())
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
type noticeHandler struct {
|
type noticeHandler struct {
|
||||||
r *render.Renderer
|
r *render.Renderer
|
||||||
|
|
||||||
|
flashes *weberrors.FlashHelper
|
||||||
|
|
||||||
noticeDB roomdb.NoticesService
|
noticeDB roomdb.NoticesService
|
||||||
pinnedDB roomdb.PinnedNoticesService
|
pinnedDB roomdb.PinnedNoticesService
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -17,8 +16,6 @@ import (
|
||||||
func TestNoticeSaveActuallyCalled(t *testing.T) {
|
func TestNoticeSaveActuallyCalled(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
// instantiate the urlTo helper (constructs urls for us!)
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
id := []string{"1"}
|
id := []string{"1"}
|
||||||
title := []string{"SSB Breaking News: This Test Is Great"}
|
title := []string{"SSB Breaking News: This Test Is Great"}
|
||||||
|
@ -26,9 +23,9 @@ func TestNoticeSaveActuallyCalled(t *testing.T) {
|
||||||
language := []string{"en-GB"}
|
language := []string{"en-GB"}
|
||||||
|
|
||||||
// POST a correct request to the save handler, and verify that the save was handled using the mock database)
|
// POST a correct request to the save handler, and verify that the save was handled using the mock database)
|
||||||
u := urlTo(router.AdminNoticeSave)
|
u := ts.URLTo(router.AdminNoticeSave)
|
||||||
formValues := url.Values{"id": id, "title": title, "content": content, "language": language}
|
formValues := url.Values{"id": id, "title": title, "content": content, "language": language}
|
||||||
resp := ts.Client.PostForm(u.String(), formValues)
|
resp := ts.Client.PostForm(u, formValues)
|
||||||
a.Equal(http.StatusSeeOther, resp.Code, "POST should work")
|
a.Equal(http.StatusSeeOther, resp.Code, "POST should work")
|
||||||
a.Equal(1, ts.NoticeDB.SaveCallCount(), "noticedb should have saved after POST completed")
|
a.Equal(1, ts.NoticeDB.SaveCallCount(), "noticedb should have saved after POST completed")
|
||||||
}
|
}
|
||||||
|
@ -37,8 +34,6 @@ func TestNoticeSaveActuallyCalled(t *testing.T) {
|
||||||
func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
// instantiate the urlTo helper (constructs urls for us!)
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
// notice values we are selectively omitting in the tests below
|
// notice values we are selectively omitting in the tests below
|
||||||
id := []string{"1"}
|
id := []string{"1"}
|
||||||
|
@ -47,24 +42,24 @@ func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
||||||
language := []string{"pt"}
|
language := []string{"pt"}
|
||||||
|
|
||||||
/* save without id */
|
/* save without id */
|
||||||
u := urlTo(router.AdminNoticeSave)
|
u := ts.URLTo(router.AdminNoticeSave)
|
||||||
emptyParams := url.Values{}
|
emptyParams := url.Values{}
|
||||||
resp := ts.Client.PostForm(u.String(), emptyParams)
|
resp := ts.Client.PostForm(u, emptyParams)
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without id should not work")
|
a.Equal(http.StatusInternalServerError, resp.Code, "saving without id should not work")
|
||||||
|
|
||||||
/* save without title */
|
/* save without title */
|
||||||
formValues := url.Values{"id": id, "content": content, "language": language}
|
formValues := url.Values{"id": id, "content": content, "language": language}
|
||||||
resp = ts.Client.PostForm(u.String(), formValues)
|
resp = ts.Client.PostForm(u, formValues)
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without title should not work")
|
a.Equal(http.StatusInternalServerError, resp.Code, "saving without title should not work")
|
||||||
|
|
||||||
/* save without content */
|
/* save without content */
|
||||||
formValues = url.Values{"id": id, "title": title, "language": language}
|
formValues = url.Values{"id": id, "title": title, "language": language}
|
||||||
resp = ts.Client.PostForm(u.String(), formValues)
|
resp = ts.Client.PostForm(u, formValues)
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without content should not work")
|
a.Equal(http.StatusInternalServerError, resp.Code, "saving without content should not work")
|
||||||
|
|
||||||
/* save without language */
|
/* save without language */
|
||||||
formValues = url.Values{"id": id, "title": title, "content": content}
|
formValues = url.Values{"id": id, "title": title, "content": content}
|
||||||
resp = ts.Client.PostForm(u.String(), formValues)
|
resp = ts.Client.PostForm(u, formValues)
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without language should not work")
|
a.Equal(http.StatusInternalServerError, resp.Code, "saving without language should not work")
|
||||||
|
|
||||||
a.Equal(0, ts.NoticeDB.SaveCallCount(), "noticedb should never save incomplete requests")
|
a.Equal(0, ts.NoticeDB.SaveCallCount(), "noticedb should never save incomplete requests")
|
||||||
|
@ -74,12 +69,11 @@ func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
||||||
func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
// instantiate the urlTo helper (constructs urls for us!)
|
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
// verify that a GET request is no bueno
|
// verify that a GET request is no bueno
|
||||||
u := urlTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
|
u := ts.URLTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
|
||||||
_, resp := ts.Client.GetHTML(u.String())
|
_, resp := ts.Client.GetHTML(u)
|
||||||
a.Equal(http.StatusMethodNotAllowed, resp.Code, "GET should not be allowed for this route")
|
a.Equal(http.StatusMethodNotAllowed, resp.Code, "GET should not be allowed for this route")
|
||||||
|
|
||||||
// next up, we verify that a correct POST request actually works:
|
// next up, we verify that a correct POST request actually works:
|
||||||
|
@ -89,7 +83,7 @@ func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
||||||
language := []string{"pt"}
|
language := []string{"pt"}
|
||||||
|
|
||||||
formValues := url.Values{"name": []string{roomdb.NoticeNews.String()}, "id": id, "title": title, "content": content, "language": language}
|
formValues := url.Values{"name": []string{roomdb.NoticeNews.String()}, "id": id, "title": title, "content": content, "language": language}
|
||||||
resp = ts.Client.PostForm(u.String(), formValues)
|
resp = ts.Client.PostForm(u, formValues)
|
||||||
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +91,7 @@ func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
||||||
func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
// instantiate the urlTo helper (constructs urls for us!)
|
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
// to test translations we first need to add a notice to the notice mockdb
|
// to test translations we first need to add a notice to the notice mockdb
|
||||||
notice := roomdb.Notice{
|
notice := roomdb.Notice{
|
||||||
|
@ -110,8 +103,8 @@ func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
||||||
// make sure we return a notice when accessing pinned notices (which are the only notices with translations at writing (2021-03-11)
|
// make sure we return a notice when accessing pinned notices (which are the only notices with translations at writing (2021-03-11)
|
||||||
ts.PinnedDB.GetReturns(¬ice, nil)
|
ts.PinnedDB.GetReturns(¬ice, nil)
|
||||||
|
|
||||||
u := urlTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews.String())
|
u := ts.URLTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews.String())
|
||||||
html, resp := ts.Client.GetHTML(u.String())
|
html, resp := ts.Client.GetHTML(u)
|
||||||
form := html.Find("form")
|
form := html.Find("form")
|
||||||
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
||||||
// FormElement defaults to input if tag omitted
|
// FormElement defaults to input if tag omitted
|
||||||
|
@ -125,8 +118,7 @@ func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
||||||
func TestNoticeEditFormIncludesAllFields(t *testing.T) {
|
func TestNoticeEditFormIncludesAllFields(t *testing.T) {
|
||||||
ts := newSession(t)
|
ts := newSession(t)
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
// instantiate the urlTo helper (constructs urls for us!)
|
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
// Create mock notice data to operate on
|
// Create mock notice data to operate on
|
||||||
notice := roomdb.Notice{
|
notice := roomdb.Notice{
|
||||||
|
@ -137,8 +129,8 @@ func TestNoticeEditFormIncludesAllFields(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.NoticeDB.GetByIDReturns(notice, nil)
|
ts.NoticeDB.GetByIDReturns(notice, nil)
|
||||||
|
|
||||||
u := urlTo(router.AdminNoticeEdit, "id", 1)
|
u := ts.URLTo(router.AdminNoticeEdit, "id", 1)
|
||||||
html, resp := ts.Client.GetHTML(u.String())
|
html, resp := ts.Client.GetHTML(u)
|
||||||
form := html.Find("form")
|
form := html.Find("form")
|
||||||
|
|
||||||
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
||||||
|
|
|
@ -4,30 +4,41 @@ package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.mindeco.de/http/render"
|
"go.mindeco.de/http/render"
|
||||||
"go.mindeco.de/http/tester"
|
"go.mindeco.de/http/tester"
|
||||||
"go.mindeco.de/logging/logtest"
|
"go.mindeco.de/logging/logtest"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/randutil"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/randutil"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
"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/i18n"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n/i18ntesting"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/members"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/members"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testSession struct {
|
type testSession struct {
|
||||||
|
Domain string
|
||||||
Mux *http.ServeMux
|
Mux *http.ServeMux
|
||||||
|
|
||||||
Client *tester.Tester
|
Client *tester.Tester
|
||||||
Router *mux.Router
|
|
||||||
|
URLTo web.URLMaker
|
||||||
|
|
||||||
AliasesDB *mockdb.FakeAliasesService
|
AliasesDB *mockdb.FakeAliasesService
|
||||||
ConfigDB *mockdb.FakeRoomConfig
|
ConfigDB *mockdb.FakeRoomConfig
|
||||||
|
@ -39,8 +50,6 @@ type testSession struct {
|
||||||
|
|
||||||
User roomdb.Member
|
User roomdb.Member
|
||||||
|
|
||||||
Domain string
|
|
||||||
|
|
||||||
RoomState *roomstate.Manager
|
RoomState *roomstate.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,49 +71,78 @@ func newSession(t *testing.T) *testSession {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
ts.RoomState = roomstate.NewManager(ctx, log)
|
ts.RoomState = roomstate.NewManager(ctx, log)
|
||||||
|
|
||||||
ts.Router = router.CompleteApp()
|
|
||||||
|
|
||||||
ts.Domain = randutil.String(10)
|
ts.Domain = randutil.String(10)
|
||||||
|
|
||||||
|
// instantiate the urlTo helper (constructs urls for us!)
|
||||||
|
// the cookiejar in our custom http/tester needs a non-empty domain and scheme
|
||||||
|
router := router.CompleteApp()
|
||||||
|
urlTo := web.NewURLTo(router)
|
||||||
|
ts.URLTo = func(name string, vals ...interface{}) *url.URL {
|
||||||
|
testURL := urlTo(name, vals...)
|
||||||
|
if testURL == nil {
|
||||||
|
t.Fatalf("no URL for %s", name)
|
||||||
|
}
|
||||||
|
testURL.Host = ts.Domain
|
||||||
|
testURL.Scheme = "https" // fake
|
||||||
|
return testURL
|
||||||
|
}
|
||||||
|
|
||||||
// fake user
|
// fake user
|
||||||
ts.User = roomdb.Member{
|
ts.User = roomdb.Member{
|
||||||
ID: 1234,
|
ID: 1234,
|
||||||
Role: roomdb.RoleModerator,
|
Role: roomdb.RoleModerator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testPath := filepath.Join("testrun", t.Name())
|
||||||
|
os.RemoveAll(testPath)
|
||||||
|
|
||||||
|
i18ntesting.WriteReplacement(t)
|
||||||
|
|
||||||
|
testRepo := repo.New(testPath)
|
||||||
|
locHelper, err := i18n.New(testRepo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authKey := make([]byte, 64)
|
||||||
|
rand.Read(authKey)
|
||||||
|
encKey := make([]byte, 32)
|
||||||
|
rand.Read(encKey)
|
||||||
|
|
||||||
|
sessionsPath := filepath.Join(testPath, "sessions")
|
||||||
|
os.MkdirAll(sessionsPath, 0700)
|
||||||
|
fsStore := sessions.NewFilesystemStore(sessionsPath, authKey, encKey)
|
||||||
|
|
||||||
|
flashHelper := weberrs.NewFlashHelper(fsStore, locHelper)
|
||||||
|
|
||||||
// setup rendering
|
// setup rendering
|
||||||
|
|
||||||
// TODO: make testing utils and move these there
|
// TODO: make testing utils and move these there
|
||||||
testFuncs := web.TemplateFuncs(ts.Router)
|
testFuncs := web.TemplateFuncs(router)
|
||||||
testFuncs["i18n"] = func(msgID string) string { return msgID }
|
|
||||||
testFuncs["i18npl"] = func(msgID string, count int, _ ...interface{}) string {
|
|
||||||
if count == 1 {
|
|
||||||
return msgID + "Singular"
|
|
||||||
}
|
|
||||||
return msgID + "Plural"
|
|
||||||
}
|
|
||||||
testFuncs["current_page_is"] = func(routeName string) bool { return true }
|
testFuncs["current_page_is"] = func(routeName string) bool { return true }
|
||||||
testFuncs["is_logged_in"] = func() *roomdb.Member { return &ts.User }
|
testFuncs["is_logged_in"] = func() *roomdb.Member { return &ts.User }
|
||||||
testFuncs["urlToNotice"] = func(name string) string { return "" }
|
testFuncs["urlToNotice"] = func(name string) string { return "" }
|
||||||
testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) }
|
testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) }
|
||||||
|
|
||||||
r, err := render.New(web.Templates,
|
renderOpts := []render.Option{
|
||||||
render.SetLogger(log),
|
render.SetLogger(log),
|
||||||
render.BaseTemplates("base.tmpl", "menu.tmpl"),
|
render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"),
|
||||||
render.AddTemplates(append(HTMLTemplates, "error.tmpl")...),
|
render.AddTemplates(append(HTMLTemplates, "error.tmpl")...),
|
||||||
render.ErrorTemplate("error.tmpl"),
|
render.ErrorTemplate("error.tmpl"),
|
||||||
render.FuncMap(testFuncs),
|
render.FuncMap(testFuncs),
|
||||||
)
|
}
|
||||||
|
renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...)
|
||||||
|
|
||||||
|
r, err := render.New(web.Templates, renderOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(errors.Wrap(err, "setup: render init failed"))
|
t.Fatal(errors.Wrap(err, "setup: render init failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.Mux = http.NewServeMux()
|
|
||||||
|
|
||||||
handler := Handler(
|
handler := Handler(
|
||||||
ts.Domain,
|
ts.Domain,
|
||||||
r,
|
r,
|
||||||
ts.RoomState,
|
ts.RoomState,
|
||||||
|
flashHelper,
|
||||||
Databases{
|
Databases{
|
||||||
Aliases: ts.AliasesDB,
|
Aliases: ts.AliasesDB,
|
||||||
Config: ts.ConfigDB,
|
Config: ts.ConfigDB,
|
||||||
|
@ -118,6 +156,7 @@ func newSession(t *testing.T) *testSession {
|
||||||
|
|
||||||
handler = members.MiddlewareForTests(ts.User)(handler)
|
handler = members.MiddlewareForTests(ts.User)(handler)
|
||||||
|
|
||||||
|
ts.Mux = http.NewServeMux()
|
||||||
ts.Mux.Handle("/", handler)
|
ts.Mux.Handle("/", handler)
|
||||||
|
|
||||||
ts.Client = tester.New(ts.Mux, t)
|
ts.Client = tester.New(ts.Mux, t)
|
||||||
|
|
|
@ -32,23 +32,30 @@ func TestAliasResolve(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.AliasesDB.ResolveReturns(testAlias, nil)
|
ts.AliasesDB.ResolveReturns(testAlias, nil)
|
||||||
|
|
||||||
|
// to construct the /alias/{name} url we need to bypass urlTo
|
||||||
|
// (which builds ?alias=name)
|
||||||
|
routes := router.CompleteApp()
|
||||||
|
|
||||||
// default is HTML
|
// default is HTML
|
||||||
htmlURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
|
||||||
r.Nil(err)
|
htmlURL, err := routes.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
t.Log("resolving", htmlURL.String())
|
t.Log("resolving", htmlURL.String())
|
||||||
html, resp := ts.Client.GetHTML(htmlURL.String())
|
html, resp := ts.Client.GetHTML(htmlURL)
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
a.Equal(testAlias.Name, html.Find("title").Text())
|
a.Equal(testAlias.Name, html.Find("title").Text())
|
||||||
|
|
||||||
// default is HTML
|
// now as JSON
|
||||||
jsonURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
jsonURL, err := routes.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||||
r.Nil(err)
|
r.NoError(err)
|
||||||
|
|
||||||
q := jsonURL.Query()
|
q := jsonURL.Query()
|
||||||
q.Set("encoding", "json")
|
q.Set("encoding", "json")
|
||||||
jsonURL.RawQuery = q.Encode()
|
jsonURL.RawQuery = q.Encode()
|
||||||
t.Log("resolving", jsonURL.String())
|
t.Log("resolving", jsonURL.String())
|
||||||
resp = ts.Client.GetBody(jsonURL.String())
|
resp = ts.Client.GetBody(jsonURL)
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
var ar aliasJSONResponse
|
var ar aliasJSONResponse
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
@ -223,9 +224,8 @@ func (h WithSSBHandler) DecideMethod(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
_, err := h.membersdb.GetByFeed(req.Context(), *cid)
|
_, err := h.membersdb.GetByFeed(req.Context(), *cid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == roomdb.ErrNotFound {
|
if errors.Is(err, roomdb.ErrNotFound) {
|
||||||
errMsg := fmt.Errorf("ssb http auth: client isn't a member: %w", err)
|
h.render.Error(w, req, http.StatusForbidden, weberrors.ErrForbidden{Details: err})
|
||||||
h.render.Error(w, req, http.StatusForbidden, errMsg)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.render.Error(w, req, http.StatusInternalServerError, err)
|
h.render.Error(w, req, http.StatusInternalServerError, err)
|
||||||
|
@ -273,11 +273,11 @@ func (h WithSSBHandler) clientInitiated(w http.ResponseWriter, req *http.Request
|
||||||
// check that we have that member
|
// check that we have that member
|
||||||
member, err := h.membersdb.GetByFeed(req.Context(), client)
|
member, err := h.membersdb.GetByFeed(req.Context(), client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := fmt.Errorf("ssb http auth: client isn't a member: %w", err)
|
if errors.Is(err, roomdb.ErrNotFound) {
|
||||||
if err == roomdb.ErrNotFound {
|
errMsg := fmt.Errorf("ssb http auth: client isn't a member: %w", err)
|
||||||
return weberrors.ErrForbidden{Details: errMsg}
|
return weberrors.ErrForbidden{Details: errMsg}
|
||||||
}
|
}
|
||||||
return errMsg
|
return err
|
||||||
}
|
}
|
||||||
payload.ClientID = client
|
payload.ClientID = client
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -21,7 +20,6 @@ import (
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
|
@ -29,30 +27,32 @@ import (
|
||||||
|
|
||||||
func TestRestricted(t *testing.T) {
|
func TestRestricted(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
|
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
testURLs := []string{
|
testURLs := []string{
|
||||||
"/admin/admin",
|
"/admin/",
|
||||||
"/admin/admin/",
|
"/admin/anything/",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, turl := range testURLs {
|
for _, tstr := range testURLs {
|
||||||
|
turl, err := url.Parse(tstr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
html, resp := ts.Client.GetHTML(turl)
|
html, resp := ts.Client.GetHTML(turl)
|
||||||
a.Equal(http.StatusUnauthorized, resp.Code, "wrong HTTP status code for %q", turl)
|
a.Equal(http.StatusForbidden, resp.Code, "wrong HTTP status code for %q", turl)
|
||||||
found := html.Find("h1").Text()
|
found := html.Find("h1").Text()
|
||||||
a.Equal("Error #401 - Unauthorized", found, "wrong error message code for %q", turl)
|
a.Equal("Error #403 - Forbidden", found, "wrong error message code for %q", turl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoginForm(t *testing.T) {
|
func TestLoginForm(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
a, r := assert.New(t), require.New(t)
|
url := ts.URLTo(router.AuthFallbackLogin)
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.AuthFallbackLogin).URL()
|
html, resp := ts.Client.GetHTML(url)
|
||||||
r.Nil(err)
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -63,24 +63,15 @@ func TestLoginForm(t *testing.T) {
|
||||||
|
|
||||||
func TestFallbackAuth(t *testing.T) {
|
func TestFallbackAuth(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
a, r := assert.New(t), require.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
// very cheap "browser" client session
|
signInFormURL := ts.URLTo(router.AuthFallbackLogin)
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
r.NoError(err)
|
|
||||||
|
|
||||||
signInFormURL, err := ts.Router.Get(router.AuthFallbackLogin).URL()
|
doc, resp := ts.Client.GetHTML(signInFormURL)
|
||||||
r.Nil(err)
|
|
||||||
signInFormURL.Host = "localhost"
|
|
||||||
signInFormURL.Scheme = "https"
|
|
||||||
|
|
||||||
doc, resp := ts.Client.GetHTML(signInFormURL.String())
|
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
csrfCookie := resp.Result().Cookies()
|
csrfCookie := resp.Result().Cookies()
|
||||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
||||||
|
|
||||||
jar.SetCookies(signInFormURL, csrfCookie)
|
|
||||||
|
|
||||||
passwordForm := doc.Find("#password-fallback")
|
passwordForm := doc.Find("#password-fallback")
|
||||||
webassert.CSRFTokenPresent(t, passwordForm)
|
webassert.CSRFTokenPresent(t, passwordForm)
|
||||||
|
@ -102,53 +93,21 @@ func TestFallbackAuth(t *testing.T) {
|
||||||
}
|
}
|
||||||
ts.AuthFallbackDB.CheckReturns(int64(23), nil)
|
ts.AuthFallbackDB.CheckReturns(int64(23), nil)
|
||||||
|
|
||||||
signInURL, err := ts.Router.Get(router.AuthFallbackFinalize).URL()
|
signInURL := ts.URLTo(router.AuthFallbackFinalize)
|
||||||
r.Nil(err)
|
|
||||||
|
|
||||||
signInURL.Host = "localhost"
|
var csrfCookieHeader = make(http.Header)
|
||||||
signInURL.Scheme = "https"
|
|
||||||
|
|
||||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
|
||||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
csrfCookieHeader.Set("Referer", "https://localhost")
|
||||||
cs := jar.Cookies(signInURL)
|
|
||||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
|
||||||
theCookie := cs[0].String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
csrfCookieHeader.Set("Cookie", theCookie)
|
|
||||||
ts.Client.SetHeaders(csrfCookieHeader)
|
ts.Client.SetHeaders(csrfCookieHeader)
|
||||||
|
|
||||||
resp = ts.Client.PostForm(signInURL.String(), loginVals)
|
resp = ts.Client.PostForm(signInURL, loginVals)
|
||||||
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
||||||
|
|
||||||
a.Equal(1, ts.AuthFallbackDB.CheckCallCount())
|
a.Equal(1, ts.AuthFallbackDB.CheckCallCount())
|
||||||
|
|
||||||
sessionCookie := resp.Result().Cookies()
|
|
||||||
jar.SetCookies(signInURL, sessionCookie)
|
|
||||||
|
|
||||||
// now request the protected dashboard page
|
// now request the protected dashboard page
|
||||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||||
r.Nil(err)
|
|
||||||
dashboardURL.Host = "localhost"
|
|
||||||
dashboardURL.Scheme = "https"
|
|
||||||
|
|
||||||
var sessionHeader = http.Header(map[string][]string{})
|
html, resp := ts.Client.GetHTML(dashboardURL)
|
||||||
cs = jar.Cookies(dashboardURL)
|
|
||||||
// TODO: why doesnt this return the csrf cookie?!
|
|
||||||
// r.Len(cs, 2, "expecting one cookie!")
|
|
||||||
for _, c := range cs {
|
|
||||||
theCookie := c.String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
sessionHeader.Add("Cookie", theCookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
durl := dashboardURL.String()
|
|
||||||
t.Log(durl)
|
|
||||||
|
|
||||||
// update headers
|
|
||||||
ts.Client.ClearHeaders()
|
|
||||||
ts.Client.SetHeaders(sessionHeader)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(durl)
|
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -160,7 +119,7 @@ func TestFallbackAuth(t *testing.T) {
|
||||||
testRef := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{0}, 16)}
|
testRef := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{0}, 16)}
|
||||||
ts.RoomState.AddEndpoint(testRef, nil)
|
ts.RoomState.AddEndpoint(testRef, nil)
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML(durl)
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -171,7 +130,7 @@ func TestFallbackAuth(t *testing.T) {
|
||||||
testRef2 := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{1}, 16)}
|
testRef2 := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{1}, 16)}
|
||||||
ts.RoomState.AddEndpoint(testRef2, nil)
|
ts.RoomState.AddEndpoint(testRef2, nil)
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML(durl)
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
|
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
|
@ -192,17 +151,13 @@ func TestAuthWithSSBClientInitNotConnected(t *testing.T) {
|
||||||
|
|
||||||
cc := signinwithssb.GenerateChallenge()
|
cc := signinwithssb.GenerateChallenge()
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||||
|
|
||||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
|
||||||
"cid", client.Feed.Ref(),
|
"cid", client.Feed.Ref(),
|
||||||
"cc", cc,
|
"cc", cc,
|
||||||
)
|
)
|
||||||
r.NotNil(signInStartURL)
|
r.NotNil(signInStartURL)
|
||||||
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||||
t.Log(signInStartURL.String())
|
a.Equal(http.StatusForbidden, resp.Code)
|
||||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code) // TODO: StatusForbidden
|
|
||||||
|
|
||||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||||
// {"#welcome", "AuthWithSSBWelcome"},
|
// {"#welcome", "AuthWithSSBWelcome"},
|
||||||
|
@ -223,17 +178,15 @@ func TestAuthWithSSBClientInitNotAllowed(t *testing.T) {
|
||||||
|
|
||||||
cc := signinwithssb.GenerateChallenge()
|
cc := signinwithssb.GenerateChallenge()
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||||
|
|
||||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
|
||||||
"cid", client.Feed.Ref(),
|
"cid", client.Feed.Ref(),
|
||||||
"cc", cc,
|
"cc", cc,
|
||||||
)
|
)
|
||||||
r.NotNil(signInStartURL)
|
r.NotNil(signInStartURL)
|
||||||
|
|
||||||
t.Log(signInStartURL.String())
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
|
||||||
a.Equal(http.StatusForbidden, resp.Code)
|
a.Equal(http.StatusForbidden, resp.Code)
|
||||||
|
t.Log(resp.Body.String())
|
||||||
|
|
||||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||||
// {"#welcome", "AuthWithSSBWelcome"},
|
// {"#welcome", "AuthWithSSBWelcome"},
|
||||||
|
@ -278,10 +231,6 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
a, r := assert.New(t), require.New(t)
|
a, r := assert.New(t), require.New(t)
|
||||||
|
|
||||||
// very cheap "browser" client session
|
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
r.NoError(err)
|
|
||||||
|
|
||||||
// the request to be signed later
|
// the request to be signed later
|
||||||
var payload signinwithssb.ClientPayload
|
var payload signinwithssb.ClientPayload
|
||||||
payload.ServerID = ts.NetworkInfo.RoomID
|
payload.ServerID = ts.NetworkInfo.RoomID
|
||||||
|
@ -339,8 +288,8 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
||||||
payload.ClientChallenge = cc
|
payload.ClientChallenge = cc
|
||||||
|
|
||||||
// prepare the url
|
// prepare the url
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||||
"cid", client.Feed.Ref(),
|
"cid", client.Feed.Ref(),
|
||||||
"cc", cc,
|
"cc", cc,
|
||||||
)
|
)
|
||||||
|
@ -350,11 +299,10 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
||||||
r.NotNil(signInStartURL)
|
r.NotNil(signInStartURL)
|
||||||
|
|
||||||
t.Log(signInStartURL.String())
|
t.Log(signInStartURL.String())
|
||||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||||
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
||||||
|
|
||||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||||
r.Nil(err)
|
|
||||||
a.Equal(dashboardURL.Path, resp.Header().Get("Location"))
|
a.Equal(dashboardURL.Path, resp.Header().Get("Location"))
|
||||||
|
|
||||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||||
|
@ -373,31 +321,8 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
||||||
// check that we have a new cookie
|
// check that we have a new cookie
|
||||||
sessionCookie := resp.Result().Cookies()
|
sessionCookie := resp.Result().Cookies()
|
||||||
r.True(len(sessionCookie) > 0, "expecting one cookie!")
|
r.True(len(sessionCookie) > 0, "expecting one cookie!")
|
||||||
jar.SetCookies(signInStartURL, sessionCookie)
|
|
||||||
|
|
||||||
// now request the protected dashboard page
|
html, resp := ts.Client.GetHTML(dashboardURL)
|
||||||
dashboardURL.Host = "localhost"
|
|
||||||
dashboardURL.Scheme = "https"
|
|
||||||
|
|
||||||
// load the cookie for the dashboard
|
|
||||||
cs := jar.Cookies(dashboardURL)
|
|
||||||
r.True(len(cs) > 0, "expecting one cookie!")
|
|
||||||
|
|
||||||
var sessionHeader = http.Header(map[string][]string{})
|
|
||||||
for _, c := range cs {
|
|
||||||
theCookie := c.String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
sessionHeader.Add("Cookie", theCookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
durl := dashboardURL.String()
|
|
||||||
t.Log(durl)
|
|
||||||
|
|
||||||
// update headers
|
|
||||||
ts.Client.ClearHeaders()
|
|
||||||
ts.Client.SetHeaders(sessionHeader)
|
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(durl)
|
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -421,13 +346,13 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
||||||
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
||||||
|
|
||||||
// prepare the url
|
// prepare the url
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||||
"cid", client.Feed.Ref(),
|
"cid", client.Feed.Ref(),
|
||||||
)
|
)
|
||||||
r.NotNil(signInStartURL)
|
r.NotNil(signInStartURL)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(signInStartURL.String())
|
html, resp := ts.Client.GetHTML(signInStartURL)
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -476,8 +401,8 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// start reading sse
|
// start reading sse
|
||||||
sseURL := urlTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||||
resp = ts.Client.GetBody(sseURL.String())
|
resp = ts.Client.GetBody(sseURL)
|
||||||
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
||||||
|
|
||||||
// check contents of sse channel
|
// check contents of sse channel
|
||||||
|
@ -492,38 +417,14 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
||||||
|
|
||||||
// use the token and go to /withssb/finalize and get a cookie
|
// use the token and go to /withssb/finalize and get a cookie
|
||||||
// (this happens in the browser engine via login-events.js)
|
// (this happens in the browser engine via login-events.js)
|
||||||
finalizeURL := urlTo(router.AuthWithSSBFinalize, "token", testToken)
|
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", testToken)
|
||||||
finalizeURL.Host = "localhost"
|
|
||||||
finalizeURL.Scheme = "https"
|
|
||||||
|
|
||||||
resp = ts.Client.GetBody(finalizeURL.String())
|
resp = ts.Client.GetBody(finalizeURL)
|
||||||
|
|
||||||
csrfCookie := resp.Result().Cookies()
|
|
||||||
a.Len(csrfCookie, 2, "csrf and session cookie")
|
|
||||||
|
|
||||||
// very cheap "browser" client session
|
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
r.NoError(err)
|
|
||||||
jar.SetCookies(finalizeURL, csrfCookie)
|
|
||||||
|
|
||||||
// now request the protected dashboard page
|
// now request the protected dashboard page
|
||||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||||
r.Nil(err)
|
|
||||||
dashboardURL.Host = "localhost"
|
|
||||||
dashboardURL.Scheme = "https"
|
|
||||||
|
|
||||||
// load the cookie for the dashboard
|
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||||
cs := jar.Cookies(dashboardURL)
|
|
||||||
r.True(len(cs) > 0, "expecting one cookie!")
|
|
||||||
var sessionHeader = http.Header(map[string][]string{})
|
|
||||||
for _, c := range cs {
|
|
||||||
theCookie := c.String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
sessionHeader.Add("Cookie", theCookie)
|
|
||||||
}
|
|
||||||
ts.Client.SetHeaders(sessionHeader)
|
|
||||||
|
|
||||||
html, resp = ts.Client.GetHTML(dashboardURL.String())
|
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -547,13 +448,12 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
||||||
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
||||||
|
|
||||||
// prepare the url
|
// prepare the url
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
|
||||||
"cid", client.Feed.Ref(),
|
"cid", client.Feed.Ref(),
|
||||||
)
|
)
|
||||||
r.NotNil(signInStartURL)
|
r.NotNil(signInStartURL)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML(signInStartURL.String())
|
html, resp := ts.Client.GetHTML(signInStartURL)
|
||||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||||
t.Log(html.Find("body").Text())
|
t.Log(html.Find("body").Text())
|
||||||
}
|
}
|
||||||
|
@ -571,8 +471,8 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// start reading sse
|
// start reading sse
|
||||||
sseURL := urlTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||||
resp = ts.Client.GetBody(sseURL.String())
|
resp = ts.Client.GetBody(sseURL)
|
||||||
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
||||||
|
|
||||||
// check contents of sse channel
|
// check contents of sse channel
|
||||||
|
@ -585,7 +485,7 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
||||||
a.True(strings.Contains(sseBody, "event: failed\n"), "success event")
|
a.True(strings.Contains(sseBody, "event: failed\n"), "success event")
|
||||||
|
|
||||||
// use an invalid token
|
// use an invalid token
|
||||||
finalizeURL := urlTo(router.AuthWithSSBFinalize, "token", "wrong")
|
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", "wrong")
|
||||||
resp = ts.Client.GetBody(finalizeURL.String())
|
resp = ts.Client.GetBody(finalizeURL)
|
||||||
a.Equal(http.StatusForbidden, resp.Result().StatusCode)
|
a.Equal(http.StatusForbidden, resp.Result().StatusCode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
|
@ -17,11 +17,10 @@ func TestIndex(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
|
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
r := require.New(t)
|
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.CompleteIndex).URL()
|
url := ts.URLTo(router.CompleteIndex)
|
||||||
r.Nil(err)
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(url)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||||
{"h1", "Default Notice Title"},
|
{"h1", "Default Notice Title"},
|
||||||
|
@ -36,11 +35,10 @@ func TestAbout(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
|
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
r := require.New(t)
|
|
||||||
|
|
||||||
url, err := ts.Router.Get(router.CompleteAbout).URL()
|
url := ts.URLTo(router.CompleteAbout)
|
||||||
r.Nil(err)
|
|
||||||
html, resp := ts.Client.GetHTML(url.String())
|
html, resp := ts.Client.GetHTML(url)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||||
found := html.Find("h1").Text()
|
found := html.Find("h1").Text()
|
||||||
a.Equal("The about page", found)
|
a.Equal("The about page", found)
|
||||||
|
@ -51,7 +49,10 @@ func TestNotFound(t *testing.T) {
|
||||||
|
|
||||||
a := assert.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
html, resp := ts.Client.GetHTML("/some/random/ASDKLANZXC")
|
url404, err := url.Parse("/some/random/ASDKLANZXC")
|
||||||
|
a.NoError(err)
|
||||||
|
|
||||||
|
html, resp := ts.Client.GetHTML(url404)
|
||||||
a.Equal(http.StatusNotFound, resp.Code, "wrong HTTP status code")
|
a.Equal(http.StatusNotFound, resp.Code, "wrong HTTP status code")
|
||||||
found := html.Find("h1").Text()
|
found := html.Find("h1").Text()
|
||||||
a.Equal("Error #404 - Not Found", found)
|
a.Equal("Error #404 - Not Found", found)
|
||||||
|
|
|
@ -85,26 +85,14 @@ func New(
|
||||||
roomsAuth.HTMLTemplates,
|
roomsAuth.HTMLTemplates,
|
||||||
admin.HTMLTemplates,
|
admin.HTMLTemplates,
|
||||||
)
|
)
|
||||||
allTheTemplates = append(allTheTemplates, "error.tmpl")
|
|
||||||
|
|
||||||
r, err := render.New(web.Templates,
|
renderOpts := []render.Option{
|
||||||
render.SetLogger(logger),
|
render.SetLogger(logger),
|
||||||
render.BaseTemplates("base.tmpl", "menu.tmpl"),
|
render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"),
|
||||||
render.AddTemplates(allTheTemplates...),
|
render.AddTemplates(allTheTemplates...),
|
||||||
// render.ErrorTemplate(),
|
|
||||||
render.SetErrorHandler(eh.Handle),
|
render.SetErrorHandler(eh.Handle),
|
||||||
render.FuncMap(web.TemplateFuncs(m)),
|
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)
|
|
||||||
return loc.LocalizePlurals
|
|
||||||
}),
|
|
||||||
render.InjectTemplateFunc("i18n", func(r *http.Request) interface{} {
|
|
||||||
loc := i18n.LocalizerFromRequest(locHelper, r)
|
|
||||||
return loc.LocalizeSimple
|
|
||||||
}),
|
|
||||||
|
|
||||||
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
||||||
return func(routeName string) bool {
|
return func(routeName string) bool {
|
||||||
route := m.Get(routeName)
|
route := m.Get(routeName)
|
||||||
|
@ -146,7 +134,10 @@ func New(
|
||||||
}),
|
}),
|
||||||
|
|
||||||
render.InjectTemplateFunc("is_logged_in", members.TemplateHelper()),
|
render.InjectTemplateFunc("is_logged_in", members.TemplateHelper()),
|
||||||
)
|
}
|
||||||
|
renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...)
|
||||||
|
|
||||||
|
r, err := render.New(web.Templates, renderOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
|
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -165,6 +156,8 @@ func New(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flashHelper := weberrs.NewFlashHelper(cookieStore, locHelper)
|
||||||
|
|
||||||
authWithPassword, err := auth.NewHandler(dbs.AuthFallback,
|
authWithPassword, err := auth.NewHandler(dbs.AuthFallback,
|
||||||
auth.SetStore(cookieStore),
|
auth.SetStore(cookieStore),
|
||||||
auth.SetErrorHandler(func(rw http.ResponseWriter, req *http.Request, err error, code int) {
|
auth.SetErrorHandler(func(rw http.ResponseWriter, req *http.Request, err error, code int) {
|
||||||
|
@ -238,6 +231,7 @@ func New(
|
||||||
netInfo.Domain,
|
netInfo.Domain,
|
||||||
r,
|
r,
|
||||||
roomState,
|
roomState,
|
||||||
|
flashHelper,
|
||||||
admin.Databases{
|
admin.Databases{
|
||||||
Aliases: dbs.Aliases,
|
Aliases: dbs.Aliases,
|
||||||
Config: dbs.Config,
|
Config: dbs.Config,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -14,7 +13,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||||
|
@ -24,22 +22,18 @@ import (
|
||||||
func TestInviteShowAcceptForm(t *testing.T) {
|
func TestInviteShowAcceptForm(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
t.Run("token doesnt exist", func(t *testing.T) {
|
t.Run("token doesnt exist", func(t *testing.T) {
|
||||||
a, r := assert.New(t), require.New(t)
|
a, r := assert.New(t), require.New(t)
|
||||||
|
|
||||||
testToken := "nonexistant-test-token"
|
testToken := "nonexistant-test-token"
|
||||||
acceptURL404 := urlTo(router.CompleteInviteFacade, "token", testToken)
|
acceptURL404 := ts.URLTo(router.CompleteInviteFacade, "token", testToken)
|
||||||
r.NotNil(acceptURL404)
|
r.NotNil(acceptURL404)
|
||||||
|
|
||||||
// prep the mocked db for http:404
|
// prep the mocked db for http:404
|
||||||
ts.InvitesDB.GetByTokenReturns(roomdb.Invite{}, roomdb.ErrNotFound)
|
ts.InvitesDB.GetByTokenReturns(roomdb.Invite{}, roomdb.ErrNotFound)
|
||||||
|
|
||||||
// request the form
|
// request the form
|
||||||
acceptForm := acceptURL404.String()
|
doc, resp := ts.Client.GetHTML(acceptURL404)
|
||||||
t.Log(acceptForm)
|
|
||||||
doc, resp := ts.Client.GetHTML(acceptForm)
|
|
||||||
// 500 until https://github.com/ssb-ngi-pointer/go-ssb-room/issues/66 is fixed
|
// 500 until https://github.com/ssb-ngi-pointer/go-ssb-room/issues/66 is fixed
|
||||||
a.Equal(http.StatusInternalServerError, resp.Code)
|
a.Equal(http.StatusInternalServerError, resp.Code)
|
||||||
|
|
||||||
|
@ -62,17 +56,14 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
||||||
a, r := assert.New(t), require.New(t)
|
a, r := assert.New(t), require.New(t)
|
||||||
|
|
||||||
testToken := "existing-test-token"
|
testToken := "existing-test-token"
|
||||||
facadeURL := urlTo(router.CompleteInviteFacade, "token", testToken)
|
validAcceptURL := ts.URLTo(router.CompleteInviteFacade, "token", testToken)
|
||||||
r.NotNil(facadeURL)
|
|
||||||
|
|
||||||
// prep the mocked db for http:200
|
// prep the mocked db for http:200
|
||||||
fakeExistingInvite := roomdb.Invite{ID: 1234}
|
fakeExistingInvite := roomdb.Invite{ID: 1234}
|
||||||
ts.InvitesDB.GetByTokenReturns(fakeExistingInvite, nil)
|
ts.InvitesDB.GetByTokenReturns(fakeExistingInvite, nil)
|
||||||
|
|
||||||
// request the form
|
// request the form
|
||||||
validAcceptForm := facadeURL.String()
|
doc, resp := ts.Client.GetHTML(validAcceptURL)
|
||||||
t.Log(validAcceptForm)
|
|
||||||
doc, resp := ts.Client.GetHTML(validAcceptForm)
|
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
// check database calls
|
// check database calls
|
||||||
|
@ -91,9 +82,10 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
||||||
a.True(ok)
|
a.True(ok)
|
||||||
|
|
||||||
// Fallback URL in data-href-fallback
|
// Fallback URL in data-href-fallback
|
||||||
fallbackURL := urlTo(router.CompleteInviteFacadeFallback, "token", testToken)
|
fallbackURL := ts.URLTo(router.CompleteInviteFacadeFallback, "token", testToken)
|
||||||
|
want := fallbackURL.Path + "?" + fallbackURL.RawQuery
|
||||||
joinDataHrefFallback, ok := doc.Find("#join-room-uri").Attr("data-href-fallback")
|
joinDataHrefFallback, ok := doc.Find("#join-room-uri").Attr("data-href-fallback")
|
||||||
a.Equal(fallbackURL.String(), joinDataHrefFallback)
|
a.Equal(want, joinDataHrefFallback)
|
||||||
a.True(ok)
|
a.True(ok)
|
||||||
|
|
||||||
// ssb-uri in data-href
|
// ssb-uri in data-href
|
||||||
|
@ -112,10 +104,7 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
||||||
a.Equal(testToken, inviteParam)
|
a.Equal(testToken, inviteParam)
|
||||||
|
|
||||||
postTo := params.Get("postTo")
|
postTo := params.Get("postTo")
|
||||||
expectedConsumeInviteURL, err := ts.Router.Get(router.CompleteInviteConsume).URL()
|
expectedConsumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||||
expectedConsumeInviteURL.Scheme = "https"
|
|
||||||
expectedConsumeInviteURL.Host = "localhost"
|
|
||||||
r.NoError(err)
|
|
||||||
a.Equal(expectedConsumeInviteURL.String(), postTo)
|
a.Equal(expectedConsumeInviteURL.String(), postTo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -123,41 +112,30 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
||||||
func TestInviteConsumeInviteHTTP(t *testing.T) {
|
func TestInviteConsumeInviteHTTP(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
a, r := assert.New(t), require.New(t)
|
a, r := assert.New(t), require.New(t)
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
testToken := "existing-test-token-2"
|
testToken := "existing-test-token-2"
|
||||||
validAcceptURL := urlTo(router.CompleteInviteInsertID, "token", testToken)
|
validAcceptURL := ts.URLTo(router.CompleteInviteInsertID, "token", testToken)
|
||||||
r.NotNil(validAcceptURL)
|
|
||||||
validAcceptURL.Host = "localhost"
|
|
||||||
validAcceptURL.Scheme = "https"
|
|
||||||
|
|
||||||
testInvite := roomdb.Invite{ID: 4321}
|
testInvite := roomdb.Invite{ID: 4321}
|
||||||
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
||||||
|
|
||||||
// request the form (for a valid csrf token)
|
// request the form (for a valid csrf token)
|
||||||
validAcceptForm := validAcceptURL.String()
|
doc, resp := ts.Client.GetHTML(validAcceptURL)
|
||||||
t.Log(validAcceptForm)
|
|
||||||
doc, resp := ts.Client.GetHTML(validAcceptForm)
|
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
form := doc.Find("form#consume")
|
form := doc.Find("form#consume")
|
||||||
r.Equal(1, form.Length())
|
r.Equal(1, form.Length())
|
||||||
|
|
||||||
|
consumeInviteURLString, has := form.Attr("action")
|
||||||
|
a.True(has, "form should have an action attribute")
|
||||||
|
expectedConsumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||||
|
a.Equal(expectedConsumeInviteURL.Path, consumeInviteURLString)
|
||||||
|
|
||||||
webassert.CSRFTokenPresent(t, form)
|
webassert.CSRFTokenPresent(t, form)
|
||||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||||
{Name: "invite", Type: "hidden", Value: testToken},
|
{Name: "invite", Type: "hidden", Value: testToken},
|
||||||
{Name: "id", Type: "text"},
|
{Name: "id", Type: "text"},
|
||||||
})
|
})
|
||||||
|
|
||||||
// we need a functional jar to unpack the Set-Cookie response for the csrf token
|
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
r.NoError(err)
|
|
||||||
|
|
||||||
// update the jar
|
|
||||||
csrfCookie := resp.Result().Cookies()
|
|
||||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
|
||||||
jar.SetCookies(validAcceptURL, csrfCookie)
|
|
||||||
|
|
||||||
// get the corresponding token from the page
|
// get the corresponding token from the page
|
||||||
csrfTokenElem := doc.Find("input[name='gorilla.csrf.Token']")
|
csrfTokenElem := doc.Find("input[name='gorilla.csrf.Token']")
|
||||||
a.Equal(1, csrfTokenElem.Length())
|
a.Equal(1, csrfTokenElem.Length())
|
||||||
|
@ -179,31 +157,18 @@ func TestInviteConsumeInviteHTTP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct the consume endpoint url
|
// construct the consume endpoint url
|
||||||
consumeInviteURLString, has := form.Attr("action")
|
consumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||||
a.True(has, "form should have an action attribute")
|
|
||||||
expectedConsumeInviteURL, err := ts.Router.Get(router.CompleteInviteConsume).URL()
|
|
||||||
r.Nil(err)
|
|
||||||
a.Equal(expectedConsumeInviteURL.String(), consumeInviteURLString)
|
|
||||||
consumeInviteURL, err := url.Parse(consumeInviteURLString)
|
|
||||||
r.Nil(err)
|
|
||||||
consumeInviteURL.Host = "localhost"
|
|
||||||
consumeInviteURL.Scheme = "https"
|
|
||||||
|
|
||||||
// construct the header with Referer and Cookie
|
// construct the header with the Referer or csrf check
|
||||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
var csrfCookieHeader = http.Header(map[string][]string{})
|
||||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
csrfCookieHeader.Set("Referer", "https://localhost")
|
||||||
cs := jar.Cookies(consumeInviteURL)
|
|
||||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
|
||||||
theCookie := cs[0].String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
csrfCookieHeader.Set("Cookie", theCookie)
|
|
||||||
ts.Client.SetHeaders(csrfCookieHeader)
|
ts.Client.SetHeaders(csrfCookieHeader)
|
||||||
|
|
||||||
// prepare the mock
|
// prepare the mock
|
||||||
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
||||||
|
|
||||||
// send the POST
|
// send the POST
|
||||||
resp = ts.Client.PostForm(consumeInviteURL.String(), consumeVals)
|
resp = ts.Client.PostForm(consumeInviteURL, consumeVals)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
||||||
|
|
||||||
// check how consume was called
|
// check how consume was called
|
||||||
|
@ -216,11 +181,8 @@ func TestInviteConsumeInviteHTTP(t *testing.T) {
|
||||||
func TestInviteConsumeInviteJSON(t *testing.T) {
|
func TestInviteConsumeInviteJSON(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
a, r := assert.New(t), require.New(t)
|
a, r := assert.New(t), require.New(t)
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
testToken := "existing-test-token-2"
|
testToken := "existing-test-token-2"
|
||||||
validAcceptURL := urlTo(router.CompleteInviteFacade, "token", testToken)
|
|
||||||
r.NotNil(validAcceptURL)
|
|
||||||
|
|
||||||
testInvite := roomdb.Invite{ID: 4321}
|
testInvite := roomdb.Invite{ID: 4321}
|
||||||
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
||||||
|
@ -236,14 +198,14 @@ func TestInviteConsumeInviteJSON(t *testing.T) {
|
||||||
consume.ID = testNewMember
|
consume.ID = testNewMember
|
||||||
|
|
||||||
// construct the consume endpoint url
|
// construct the consume endpoint url
|
||||||
consumeInviteURL := urlTo(router.CompleteInviteConsume)
|
consumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||||
r.NotNil(consumeInviteURL)
|
r.NotNil(consumeInviteURL)
|
||||||
|
|
||||||
// prepare the mock
|
// prepare the mock
|
||||||
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
||||||
|
|
||||||
// send the POST
|
// send the POST
|
||||||
resp := ts.Client.SendJSON(consumeInviteURL.String(), consume)
|
resp := ts.Client.SendJSON(consumeInviteURL, consume)
|
||||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
||||||
|
|
||||||
// check how consume was called
|
// check how consume was called
|
||||||
|
|
|
@ -2,15 +2,12 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNoticeSmokeTest ensures the most basic notice serving is working
|
// TestNoticeSmokeTest ensures the most basic notice serving is working
|
||||||
|
@ -25,7 +22,8 @@ func TestNoticeSmokeTest(t *testing.T) {
|
||||||
|
|
||||||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||||
|
|
||||||
html, res := ts.Client.GetHTML("/notice/show?id=1")
|
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", "1")
|
||||||
|
html, res := ts.Client.GetHTML(noticeURL)
|
||||||
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
||||||
a.Equal("Welcome!", html.Find("title").Text())
|
a.Equal("Welcome!", html.Find("title").Text())
|
||||||
}
|
}
|
||||||
|
@ -47,7 +45,8 @@ Hello world!
|
||||||
|
|
||||||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||||
|
|
||||||
html, res := ts.Client.GetHTML("/notice/show?id=1")
|
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", "1")
|
||||||
|
html, res := ts.Client.GetHTML(noticeURL)
|
||||||
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
||||||
a.Equal("Welcome!", html.Find("title").Text())
|
a.Equal("Welcome!", html.Find("title").Text())
|
||||||
a.Equal("The loveliest of rooms is here", html.Find("h2").Text())
|
a.Equal("The loveliest of rooms is here", html.Find("h2").Text())
|
||||||
|
@ -57,9 +56,7 @@ Hello world!
|
||||||
// then we log in as an admin and see that the edit links are there.
|
// then we log in as an admin and see that the edit links are there.
|
||||||
func TestNoticesEditButtonVisible(t *testing.T) {
|
func TestNoticesEditButtonVisible(t *testing.T) {
|
||||||
ts := setup(t)
|
ts := setup(t)
|
||||||
a, r := assert.New(t), require.New(t)
|
a := assert.New(t)
|
||||||
|
|
||||||
urlTo := web.NewURLTo(ts.Router)
|
|
||||||
|
|
||||||
ts.AliasesDB.ResolveReturns(roomdb.Alias{}, roomdb.ErrNotFound)
|
ts.AliasesDB.ResolveReturns(roomdb.Alias{}, roomdb.ErrNotFound)
|
||||||
|
|
||||||
|
@ -71,12 +68,11 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
||||||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||||
|
|
||||||
// first, we confirm that the button is missing when not logged in
|
// first, we confirm that the button is missing when not logged in
|
||||||
noticeURL := urlTo(router.CompleteNoticeShow, "id", 42)
|
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", 42)
|
||||||
noticeURL.Host = "localhost"
|
|
||||||
noticeURL.Scheme = "https"
|
|
||||||
editButtonSelector := `#edit-notice`
|
editButtonSelector := `#edit-notice`
|
||||||
|
|
||||||
doc, resp := ts.Client.GetHTML(noticeURL.String())
|
doc, resp := ts.Client.GetHTML(noticeURL)
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
// empty selection <=> we have no link
|
// empty selection <=> we have no link
|
||||||
|
@ -84,25 +80,17 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
||||||
|
|
||||||
// start preparing the ~login dance~
|
// start preparing the ~login dance~
|
||||||
// TODO: make this code reusable and share it with the login => /dashboard http:200 test
|
// TODO: make this code reusable and share it with the login => /dashboard http:200 test
|
||||||
// cookiejar: a very cheap client session
|
|
||||||
// TODO: refactor login dance for re-use in testing / across tests
|
// TODO: refactor login dance for re-use in testing / across tests
|
||||||
jar, err := cookiejar.New(nil)
|
|
||||||
r.NoError(err)
|
|
||||||
|
|
||||||
// when dealing with cookies we also need to have an Host and URL-Scheme
|
// when dealing with cookies we also need to have an Host and URL-Scheme
|
||||||
// for the jar to save and load them correctly
|
// for the jar to save and load them correctly
|
||||||
formEndpoint := urlTo(router.AuthFallbackLogin)
|
formEndpoint := ts.URLTo(router.AuthFallbackLogin)
|
||||||
r.NotNil(formEndpoint)
|
|
||||||
formEndpoint.Host = "localhost"
|
|
||||||
formEndpoint.Scheme = "https"
|
|
||||||
|
|
||||||
doc, resp = ts.Client.GetHTML(formEndpoint.String())
|
doc, resp = ts.Client.GetHTML(formEndpoint)
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
|
|
||||||
csrfCookie := resp.Result().Cookies()
|
csrfCookie := resp.Result().Cookies()
|
||||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
||||||
t.Log(csrfCookie)
|
|
||||||
jar.SetCookies(formEndpoint, csrfCookie)
|
|
||||||
|
|
||||||
csrfTokenElem := doc.Find("input[type=hidden]")
|
csrfTokenElem := doc.Find("input[type=hidden]")
|
||||||
a.Equal(1, csrfTokenElem.Length())
|
a.Equal(1, csrfTokenElem.Length())
|
||||||
|
@ -125,44 +113,19 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
||||||
ts.AuthFallbackDB.CheckReturns(testUser.ID, nil)
|
ts.AuthFallbackDB.CheckReturns(testUser.ID, nil)
|
||||||
ts.MembersDB.GetByIDReturns(testUser, nil)
|
ts.MembersDB.GetByIDReturns(testUser, nil)
|
||||||
|
|
||||||
postEndpoint, err := ts.Router.Get(router.AuthFallbackFinalize).URL()
|
postEndpoint := ts.URLTo(router.AuthFallbackFinalize)
|
||||||
r.Nil(err)
|
|
||||||
postEndpoint.Host = "localhost"
|
|
||||||
postEndpoint.Scheme = "https"
|
|
||||||
|
|
||||||
// construct HTTP Header with Referer and Cookie
|
// construct HTTP Header with Referer and Cookie
|
||||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
var refererHeader = make(http.Header)
|
||||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
refererHeader.Set("Referer", "https://localhost")
|
||||||
cs := jar.Cookies(postEndpoint)
|
ts.Client.SetHeaders(refererHeader)
|
||||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
|
||||||
theCookie := cs[0].String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
csrfCookieHeader.Set("Cookie", theCookie)
|
|
||||||
ts.Client.SetHeaders(csrfCookieHeader)
|
|
||||||
|
|
||||||
resp = ts.Client.PostForm(postEndpoint.String(), loginVals)
|
resp = ts.Client.PostForm(postEndpoint, loginVals)
|
||||||
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
||||||
|
|
||||||
sessionCookie := resp.Result().Cookies()
|
|
||||||
jar.SetCookies(postEndpoint, sessionCookie)
|
|
||||||
|
|
||||||
var sessionHeader = http.Header(map[string][]string{})
|
|
||||||
cs = jar.Cookies(noticeURL)
|
|
||||||
// TODO: why doesnt this return the csrf cookie?!
|
|
||||||
r.NotEqual(len(cs), 0, "expecting a cookie!")
|
|
||||||
for _, c := range cs {
|
|
||||||
theCookie := c.String()
|
|
||||||
a.NotEqual("", theCookie, "should have a new cookie")
|
|
||||||
sessionHeader.Add("Cookie", theCookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update headers
|
|
||||||
ts.Client.ClearHeaders()
|
|
||||||
ts.Client.SetHeaders(sessionHeader)
|
|
||||||
|
|
||||||
cnt := ts.MembersDB.GetByIDCallCount()
|
cnt := ts.MembersDB.GetByIDCallCount()
|
||||||
// now we are logged in, anchor tag should be there
|
// now we are logged in, anchor tag should be there
|
||||||
doc, resp = ts.Client.GetHTML(noticeURL.String())
|
doc, resp = ts.Client.GetHTML(noticeURL)
|
||||||
a.Equal(http.StatusOK, resp.Code)
|
a.Equal(http.StatusOK, resp.Code)
|
||||||
a.Equal(cnt+1, ts.MembersDB.GetByIDCallCount())
|
a.Equal(cnt+1, ts.MembersDB.GetByIDCallCount())
|
||||||
|
|
||||||
|
|
|
@ -5,15 +5,12 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"go.mindeco.de/http/tester"
|
"go.mindeco.de/http/tester"
|
||||||
"go.mindeco.de/logging/logtest"
|
"go.mindeco.de/logging/logtest"
|
||||||
|
|
||||||
|
@ -24,7 +21,8 @@ import (
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n/i18ntesting"
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||||
refs "go.mindeco.de/ssb-refs"
|
refs "go.mindeco.de/ssb-refs"
|
||||||
)
|
)
|
||||||
|
@ -32,7 +30,7 @@ import (
|
||||||
type testSession struct {
|
type testSession struct {
|
||||||
Mux *http.ServeMux
|
Mux *http.ServeMux
|
||||||
Client *tester.Tester
|
Client *tester.Tester
|
||||||
Router *mux.Router
|
URLTo web.URLMaker
|
||||||
|
|
||||||
// mocked dbs
|
// mocked dbs
|
||||||
AuthDB *mockdb.FakeAuthWithSSBService
|
AuthDB *mockdb.FakeAuthWithSSBService
|
||||||
|
@ -55,8 +53,6 @@ type testSession struct {
|
||||||
NetworkInfo network.ServerEndpointDetails
|
NetworkInfo network.ServerEndpointDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
var testI18N = justTheKeys()
|
|
||||||
|
|
||||||
func setup(t *testing.T) *testSession {
|
func setup(t *testing.T) *testSession {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var ts testSession
|
var ts testSession
|
||||||
|
@ -65,12 +61,7 @@ func setup(t *testing.T) *testSession {
|
||||||
os.RemoveAll(testRepoPath)
|
os.RemoveAll(testRepoPath)
|
||||||
testRepo := repo.New(testRepoPath)
|
testRepo := repo.New(testRepoPath)
|
||||||
|
|
||||||
testOverride := filepath.Join(testRepo.GetPath("i18n"), "active.en.toml")
|
i18ntesting.WriteReplacement(t)
|
||||||
os.MkdirAll(filepath.Dir(testOverride), 0700)
|
|
||||||
err := ioutil.WriteFile(testOverride, testI18N, 0700)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.AuthDB = new(mockdb.FakeAuthWithSSBService)
|
ts.AuthDB = new(mockdb.FakeAuthWithSSBService)
|
||||||
ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
||||||
|
@ -107,7 +98,18 @@ func setup(t *testing.T) *testSession {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
ts.RoomState = roomstate.NewManager(ctx, log)
|
ts.RoomState = roomstate.NewManager(ctx, log)
|
||||||
|
|
||||||
ts.Router = router.CompleteApp()
|
// instantiate the urlTo helper (constructs urls for us!)
|
||||||
|
// the cookiejar in our custom http/tester needs a non-empty domain and scheme
|
||||||
|
urlTo := web.NewURLTo(router.CompleteApp())
|
||||||
|
ts.URLTo = func(name string, vals ...interface{}) *url.URL {
|
||||||
|
testURL := urlTo(name, vals...)
|
||||||
|
if testURL == nil {
|
||||||
|
t.Fatalf("no URL for %s", name)
|
||||||
|
}
|
||||||
|
testURL.Host = ts.NetworkInfo.Domain
|
||||||
|
testURL.Scheme = "https" // fake
|
||||||
|
return testURL
|
||||||
|
}
|
||||||
|
|
||||||
ts.SignalBridge = signinwithssb.NewSignalBridge()
|
ts.SignalBridge = signinwithssb.NewSignalBridge()
|
||||||
|
|
||||||
|
@ -140,55 +142,3 @@ func setup(t *testing.T) *testSession {
|
||||||
|
|
||||||
return &ts
|
return &ts
|
||||||
}
|
}
|
||||||
|
|
||||||
// auto generate from defaults a list of Label = "Label"
|
|
||||||
// must keep order of input intact
|
|
||||||
// (at least all the globals before starting with nested plurals)
|
|
||||||
// also replaces 'one' and 'other' in plurals
|
|
||||||
func justTheKeys() []byte {
|
|
||||||
f, err := i18n.Defaults.Open("active.en.toml")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
justAMap := make(map[string]interface{})
|
|
||||||
md, err := toml.DecodeReader(f, &justAMap)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf = &bytes.Buffer{}
|
|
||||||
|
|
||||||
// if we don't produce the same order as the input
|
|
||||||
// (in go maps are ALWAYS random access when ranged over)
|
|
||||||
// nested keys (such as plural form) will mess up the global level...
|
|
||||||
for _, k := range md.Keys() {
|
|
||||||
key := k.String()
|
|
||||||
val, has := justAMap[key]
|
|
||||||
if !has {
|
|
||||||
// fmt.Println("i18n test warning:", key, "not unmarshaled")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tv := val.(type) {
|
|
||||||
|
|
||||||
case string:
|
|
||||||
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
|
||||||
|
|
||||||
case map[string]interface{}:
|
|
||||||
// fmt.Println("i18n test warning: custom map for ", key)
|
|
||||||
|
|
||||||
fmt.Fprintf(buf, "\n[%s]\n", key)
|
|
||||||
// replace "one" and "other" keys
|
|
||||||
// with Label and LabelPlural
|
|
||||||
tv["one"] = key + "Singular"
|
|
||||||
tv["other"] = key + "Plural"
|
|
||||||
toml.NewEncoder(buf).Encode(tv)
|
|
||||||
fmt.Fprintln(buf)
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unhandled toml structure under %s: %T\n", key, val))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ Settings = "Settings"
|
||||||
AdminDeniedKeysTitle = "Banned"
|
AdminDeniedKeysTitle = "Banned"
|
||||||
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
|
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
|
||||||
AdminDeniedKeysAdd = "Add"
|
AdminDeniedKeysAdd = "Add"
|
||||||
|
AdminDeniedKeysAdded = "Key was added to the list."
|
||||||
AdminDeniedKeysRemove = "Remove"
|
AdminDeniedKeysRemove = "Remove"
|
||||||
AdminDeniedKeysComment = "Comment"
|
AdminDeniedKeysComment = "Comment"
|
||||||
AdminDeniedKeysCommentDescription = "The person who added this ban, added the following comment"
|
AdminDeniedKeysCommentDescription = "The person who added this ban, added the following comment"
|
||||||
|
@ -112,6 +113,10 @@ AdminMemberDetailsAliasRevoke = "Revoke"
|
||||||
AdminMemberDetailsExclusion = "Exclusion from this room"
|
AdminMemberDetailsExclusion = "Exclusion from this room"
|
||||||
AdminMemberDetailsRemove = "Remove member"
|
AdminMemberDetailsRemove = "Remove member"
|
||||||
|
|
||||||
|
AdminMemberAdded = "Member added successfully."
|
||||||
|
AdminMemberUpdated = "Member updated."
|
||||||
|
AdminMemberRemoved = "Member removed."
|
||||||
|
|
||||||
# invite dashboard
|
# invite dashboard
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"go.mindeco.de/http/render"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
|
@ -116,11 +117,26 @@ func New(r repo.Interface) (*Helper, error) {
|
||||||
return &Helper{bundle: bundle}, nil
|
return &Helper{bundle: bundle}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Helper) GetRenderFuncs() []render.Option {
|
||||||
|
var opts = []render.Option{
|
||||||
|
render.InjectTemplateFunc("i18npl", func(r *http.Request) interface{} {
|
||||||
|
loc := h.FromRequest(r)
|
||||||
|
return loc.LocalizePlurals
|
||||||
|
}),
|
||||||
|
|
||||||
|
render.InjectTemplateFunc("i18n", func(r *http.Request) interface{} {
|
||||||
|
loc := h.FromRequest(r)
|
||||||
|
return loc.LocalizeSimple
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
type Localizer struct {
|
type Localizer struct {
|
||||||
loc *i18n.Localizer
|
loc *i18n.Localizer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Helper) NewLocalizer(lang string, accept ...string) *Localizer {
|
func (h Helper) newLocalizer(lang string, accept ...string) *Localizer {
|
||||||
var langs = []string{lang}
|
var langs = []string{lang}
|
||||||
langs = append(langs, accept...)
|
langs = append(langs, accept...)
|
||||||
var l Localizer
|
var l Localizer
|
||||||
|
@ -128,6 +144,15 @@ func (h Helper) NewLocalizer(lang string, accept ...string) *Localizer {
|
||||||
return &l
|
return &l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromRequest returns a new Localizer for the passed helper,
|
||||||
|
// using form value 'lang' and Accept-Language http header from the passed request.
|
||||||
|
// TODO: user settings/cookie values?
|
||||||
|
func (h Helper) FromRequest(r *http.Request) *Localizer {
|
||||||
|
lang := r.FormValue("lang")
|
||||||
|
accept := r.Header.Get("Accept-Language")
|
||||||
|
return h.newLocalizer(lang, accept)
|
||||||
|
}
|
||||||
|
|
||||||
func (l Localizer) LocalizeSimple(messageID string) string {
|
func (l Localizer) LocalizeSimple(messageID string) string {
|
||||||
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
||||||
MessageID: messageID,
|
MessageID: messageID,
|
||||||
|
@ -178,12 +203,3 @@ func (l Localizer) LocalizePluralsWithData(messageID string, pluralCount int, tp
|
||||||
|
|
||||||
panic(fmt.Sprintf("i18n/error: failed to localize label %s: %s", messageID, err))
|
panic(fmt.Sprintf("i18n/error: failed to localize label %s: %s", messageID, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalizerFromRequest returns a new Localizer for the passed helper,
|
|
||||||
// using form value 'lang' and Accept-Language http header from the passed request.
|
|
||||||
// TODO: user settings/cookie values?
|
|
||||||
func LocalizerFromRequest(helper *Helper, r *http.Request) *Localizer {
|
|
||||||
lang := r.FormValue("lang")
|
|
||||||
accept := r.Header.Get("Accept-Language")
|
|
||||||
return helper.NewLocalizer(lang, accept)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package i18ntesting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||||
|
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// justTheKeys auto generates from the defaults a list of Label = "Label"
|
||||||
|
// must keep order of input intact
|
||||||
|
// (at least all the globals before starting with nested plurals)
|
||||||
|
// also replaces 'one' and 'other' in plurals
|
||||||
|
func justTheKeys(t *testing.T) []byte {
|
||||||
|
f, err := i18n.Defaults.Open("active.en.toml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
justAMap := make(map[string]interface{})
|
||||||
|
md, err := toml.DecodeReader(f, &justAMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = &bytes.Buffer{}
|
||||||
|
|
||||||
|
// if we don't produce the same order as the input
|
||||||
|
// (in go maps are ALWAYS random access when ranged over)
|
||||||
|
// nested keys (such as plural form) will mess up the global level...
|
||||||
|
for _, k := range md.Keys() {
|
||||||
|
key := k.String()
|
||||||
|
val, has := justAMap[key]
|
||||||
|
if !has {
|
||||||
|
// fmt.Println("i18n test warning:", key, "not unmarshaled")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tv := val.(type) {
|
||||||
|
|
||||||
|
case string:
|
||||||
|
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
||||||
|
|
||||||
|
case map[string]interface{}:
|
||||||
|
// fmt.Println("i18n test warning: custom map for ", key)
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "\n[%s]\n", key)
|
||||||
|
// replace "one" and "other" keys
|
||||||
|
// with Label and LabelPlural
|
||||||
|
tv["one"] = key + "Singular"
|
||||||
|
tv["other"] = key + "Plural"
|
||||||
|
toml.NewEncoder(buf).Encode(tv)
|
||||||
|
fmt.Fprintln(buf)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Fatalf("unhandled toml structure under %s: %T\n", key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteReplacement(t *testing.T) {
|
||||||
|
r := repo.New(filepath.Join("testrun", t.Name()))
|
||||||
|
|
||||||
|
testOverride := filepath.Join(r.GetPath("i18n"), "active.en.toml")
|
||||||
|
t.Log(testOverride)
|
||||||
|
os.MkdirAll(filepath.Dir(testOverride), 0700)
|
||||||
|
|
||||||
|
content := justTheKeys(t)
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(testOverride, content, 0700)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
<p id="welcome" class="my-2">{{i18n "AdminDeniedKeysWelcome"}}</p>
|
<p id="welcome" class="my-2">{{i18n "AdminDeniedKeysWelcome"}}</p>
|
||||||
|
|
||||||
|
{{ template "flashes" . }}
|
||||||
|
|
||||||
<p
|
<p
|
||||||
id="DeniedKeysCount"
|
id="DeniedKeysCount"
|
||||||
class="text-lg font-bold my-2"
|
class="text-lg font-bold my-2"
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
<p id="welcome" class="my-2">{{i18n "AdminMembersWelcome"}}</p>
|
<p id="welcome" class="my-2">{{i18n "AdminMembersWelcome"}}</p>
|
||||||
|
|
||||||
|
{{ template "flashes" . }}
|
||||||
|
|
||||||
<form
|
<form
|
||||||
id="add-entry"
|
id="add-entry"
|
||||||
action="{{urlTo "admin:members:add"}}"
|
action="{{urlTo "admin:members:add"}}"
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{{ define "flashes" }}
|
||||||
|
{{if .Flashes}}
|
||||||
|
<ul id="flashes-list">
|
||||||
|
{{range .Flashes}}
|
||||||
|
<li
|
||||||
|
class="{{if eq .Kind 1}}text-red-600{{else}}text-green-600{{end}}"
|
||||||
|
>{{.Message}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
{{ end }}
|
|
@ -34,12 +34,14 @@ func TemplateFuncs(m *mux.Router) template.FuncMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URLMaker func(string, ...interface{}) *url.URL
|
||||||
|
|
||||||
// NewURLTo returns a template helper function for a router.
|
// NewURLTo returns a template helper function for a router.
|
||||||
// It is usually called with one parameter, the route name, which should be defined in the router package.
|
// It is usually called with one parameter, the route name, which should be defined in the router package.
|
||||||
// If it's called with more then one, it has a to be a pair of two values. (1, 3, 5, 7, etc.)
|
// If it's called with more then one, it has a to be a pair of two values. (1, 3, 5, 7, etc.)
|
||||||
// The first value of such a pair is the placeholder name in the router (i.e. in '/our/routes/{id:[0-9]+}/test' it would be id )
|
// The first value of such a pair is the placeholder name in the router (i.e. in '/our/routes/{id:[0-9]+}/test' it would be id )
|
||||||
// and the 2nd value is the actual value that should be put in place of the placeholder.
|
// and the 2nd value is the actual value that should be put in place of the placeholder.
|
||||||
func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
|
func NewURLTo(appRouter *mux.Router) URLMaker {
|
||||||
l := logging.Logger("helper.URLTo") // TOOD: inject in a scoped way
|
l := logging.Logger("helper.URLTo") // TOOD: inject in a scoped way
|
||||||
return func(routeName string, ps ...interface{}) *url.URL {
|
return func(routeName string, ps ...interface{}) *url.URL {
|
||||||
route := appRouter.Get(routeName)
|
route := appRouter.Get(routeName)
|
||||||
|
|
Loading…
Reference in New Issue