Various fixes

* fix accept and consumed view
* Apply suggestions from Alex' code review
* define admin.Databases options struct
* structify database parameters of web/handlers
This commit is contained in:
Henry 2021-03-05 11:15:36 +01:00
parent 672647cd4d
commit fd21dfc60a
12 changed files with 143 additions and 60 deletions

View File

@ -155,6 +155,9 @@ func (i Invites) GetByToken(ctx context.Context, token string) (admindb.Invite,
qm.Load("CreatedByAuthFallback"), qm.Load("CreatedByAuthFallback"),
).One(ctx, i.db) ).One(ctx, i.db)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return inv, admindb.ErrNotFound
}
return inv, err return inv, err
} }
@ -174,6 +177,9 @@ func (i Invites) GetByID(ctx context.Context, id int64) (admindb.Invite, error)
qm.Load("CreatedByAuthFallback"), qm.Load("CreatedByAuthFallback"),
).One(ctx, i.db) ).One(ctx, i.db)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return inv, admindb.ErrNotFound
}
return inv, err return inv, err
} }
@ -225,6 +231,9 @@ func (i Invites) Revoke(ctx context.Context, id int64) error {
qm.Where("active = true AND id = ?", id), qm.Where("active = true AND id = ?", id),
).One(ctx, tx) ).One(ctx, tx)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return admindb.ErrNotFound
}
return err return err
} }

View File

@ -215,12 +215,14 @@ func runroomsrv() error {
repo.New(repoDir), repo.New(repoDir),
httpsDomain, httpsDomain,
roomsrv.StateManager, roomsrv.StateManager,
db.AuthWithSSB, handlers.Databases{
db.AuthFallback, AuthWithSSB: db.AuthWithSSB,
db.AllowList, AuthFallback: db.AuthFallback,
db.Invites, AllowList: db.AllowList,
db.Notices, Invites: db.Invites,
db.PinnedNotices, Notices: db.Notices,
PinnedNotices: db.PinnedNotices,
},
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to create HTTPdashboard handler: %w", err) return fmt.Errorf("failed to create HTTPdashboard handler: %w", err)

View File

@ -96,10 +96,12 @@ func newSession(t *testing.T) *testSession {
ts.Domain, ts.Domain,
r, r,
ts.RoomState, ts.RoomState,
ts.AllowListDB, Databases{
ts.InvitesDB, AllowList: ts.AllowListDB,
ts.NoticeDB, Invites: ts.InvitesDB,
ts.PinnedDB, Notices: ts.NoticeDB,
PinnedNotices: ts.PinnedDB,
},
) )
handler = user.MiddlewareForTests(ts.User)(handler) handler = user.MiddlewareForTests(ts.User)(handler)

View File

@ -32,16 +32,21 @@ var HTMLTemplates = []string{
"admin/notice-edit.tmpl", "admin/notice-edit.tmpl",
} }
// Databases is an option struct that encapsualtes the required database services
type Databases struct {
AllowList admindb.AllowListService
Invites admindb.InviteService
Notices admindb.NoticesService
PinnedNotices admindb.PinnedNoticesService
}
// Handler supplies the elevated access pages to known users. // Handler supplies the elevated access pages to known users.
// It is not registering on the mux router like other pages to clean up the authorize flow. // It is not registering on the mux router like other pages to clean up the authorize flow.
func Handler( func Handler(
domainName string, domainName string,
r *render.Renderer, r *render.Renderer,
roomState *roomstate.Manager, roomState *roomstate.Manager,
al admindb.AllowListService, dbs Databases,
is admindb.InviteService,
ndb admindb.NoticesService,
pdb admindb.PinnedNoticesService,
) http.Handler { ) http.Handler {
mux := &http.ServeMux{} mux := &http.ServeMux{}
// TODO: configure 404 handler // TODO: configure 404 handler
@ -59,7 +64,7 @@ func Handler(
var ah = allowListHandler{ var ah = allowListHandler{
r: r, r: r,
al: al, al: dbs.AllowList,
} }
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", ah.overview)) mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", ah.overview))
mux.HandleFunc("/members/add", ah.add) mux.HandleFunc("/members/add", ah.add)
@ -68,7 +73,7 @@ func Handler(
var ih = invitesHandler{ var ih = invitesHandler{
r: r, r: r,
db: is, db: dbs.Invites,
domainName: domainName, domainName: domainName,
} }
@ -79,8 +84,8 @@ func Handler(
var nh = noticeHandler{ var nh = noticeHandler{
r: r, r: r,
noticeDB: ndb, noticeDB: dbs.Notices,
pinnedDB: pdb, pinnedDB: dbs.PinnedNotices,
} }
mux.HandleFunc("/notice/edit", r.HTML("admin/notice-edit.tmpl", nh.edit)) mux.HandleFunc("/notice/edit", r.HTML("admin/notice-edit.tmpl", nh.edit))
mux.HandleFunc("/notice/translation/draft", r.HTML("admin/notice-edit.tmpl", nh.draftTranslation)) mux.HandleFunc("/notice/translation/draft", r.HTML("admin/notice-edit.tmpl", nh.draftTranslation))

View File

@ -39,18 +39,24 @@ var HTMLTemplates = []string{
"error.tmpl", "error.tmpl",
} }
// Databases is an options stuct for the required databases of the web handlers
type Databases struct {
AuthWithSSB admindb.AuthWithSSBService
AuthFallback admindb.AuthFallbackService
AllowList admindb.AllowListService
Invites admindb.InviteService
Notices admindb.NoticesService
PinnedNotices admindb.PinnedNoticesService
}
// New initializes the whole web stack for rooms, with all the sub-modules and routing. // New initializes the whole web stack for rooms, with all the sub-modules and routing.
func New( func New(
logger logging.Interface, logger logging.Interface,
repo repo.Interface, repo repo.Interface,
domainName string, domainName string,
roomState *roomstate.Manager, roomState *roomstate.Manager,
as admindb.AuthWithSSBService, dbs Databases,
fs admindb.AuthFallbackService,
al admindb.AllowListService,
is admindb.InviteService,
ns admindb.NoticesService,
ps admindb.PinnedNoticesService,
) (http.Handler, error) { ) (http.Handler, error) {
m := router.CompleteApp() m := router.CompleteApp()
@ -93,7 +99,7 @@ func New(
if !noticeName.Valid() { if !noticeName.Valid() {
return nil return nil
} }
notice, err := ps.Get(r.Context(), noticeName, "en-GB") notice, err := dbs.PinnedNotices.Get(r.Context(), noticeName, "en-GB")
if err != nil { if err != nil {
return nil return nil
} }
@ -173,7 +179,7 @@ func New(
}, nil }, nil
}) })
a, err := auth.NewHandler(fs, a, err := auth.NewHandler(dbs.AuthFallback,
auth.SetStore(store), auth.SetStore(store),
auth.SetErrorHandler(authErrH), auth.SetErrorHandler(authErrH),
auth.SetNotAuthorizedHandler(notAuthorizedH), auth.SetNotAuthorizedHandler(notAuthorizedH),
@ -204,18 +210,21 @@ func New(
// hookup handlers to the router // hookup handlers to the router
roomsAuth.Handler(m, r, a) roomsAuth.Handler(m, r, a)
adminHandler := a.Authenticate(admin.Handler( adminHandler := admin.Handler(
domainName, domainName,
r, r,
roomState, roomState,
al, admin.Databases{
is, AllowList: dbs.AllowList,
ns, Invites: dbs.Invites,
ps)) Notices: dbs.Notices,
mainMux.Handle("/admin/", adminHandler) PinnedNotices: dbs.PinnedNotices,
},
)
mainMux.Handle("/admin/", a.Authenticate(adminHandler))
m.Get(router.CompleteIndex).Handler(r.HTML("landing/index.tmpl", func(w http.ResponseWriter, req *http.Request) (interface{}, error) { m.Get(router.CompleteIndex).Handler(r.HTML("landing/index.tmpl", func(w http.ResponseWriter, req *http.Request) (interface{}, error) {
notice, err := ps.Get(req.Context(), admindb.NoticeDescription, "en-GB") notice, err := dbs.PinnedNotices.Get(req.Context(), admindb.NoticeDescription, "en-GB")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to find description: %w", err) return nil, fmt.Errorf("failed to find description: %w", err)
} }
@ -230,14 +239,14 @@ func New(
m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl")) m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl"))
var nh = noticeHandler{ var nh = noticeHandler{
notices: ns, notices: dbs.Notices,
pinned: ps, pinned: dbs.PinnedNotices,
} }
m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list)) m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list))
m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show)) m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show))
var ih = inviteHandler{ var ih = inviteHandler{
invites: is, invites: dbs.Invites,
} }
m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm)) m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm))
m.Get(router.CompleteInviteConsume).Handler(r.HTML("invite/consumed.tmpl", ih.consume)) m.Get(router.CompleteInviteConsume).Handler(r.HTML("invite/consumed.tmpl", ih.consume))
@ -255,7 +264,7 @@ func New(
// apply HTTP middleware // apply HTTP middleware
middlewares := []func(http.Handler) http.Handler{ middlewares := []func(http.Handler) http.Handler{
logging.InjectHandler(logger), logging.InjectHandler(logger),
user.ContextInjecter(fs, a), user.ContextInjecter(dbs.AuthFallback, a),
CSRF, CSRF,
} }
@ -264,8 +273,8 @@ func New(
} }
var finalHandler http.Handler = mainMux var finalHandler http.Handler = mainMux
for _, mw := range middlewares { for _, applyMiddleware := range middlewares {
finalHandler = mw(finalHandler) finalHandler = applyMiddleware(finalHandler)
} }
return finalHandler, nil return finalHandler, nil

View File

@ -5,7 +5,9 @@ import (
"net/http" "net/http"
"go.mindeco.de/http/render" "go.mindeco.de/http/render"
"go.mindeco.de/logging"
"github.com/go-kit/kit/log/level"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb" "github.com/ssb-ngi-pointer/go-ssb-room/admindb"
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors" weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
@ -20,7 +22,9 @@ type inviteHandler struct {
} }
func (h inviteHandler) acceptForm(rw http.ResponseWriter, req *http.Request) (interface{}, error) { func (h inviteHandler) acceptForm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
inv, err := h.invites.GetByToken(req.Context(), req.URL.Query().Get("token")) token := req.URL.Query().Get("token")
inv, err := h.invites.GetByToken(req.Context(), token)
if err != nil { if err != nil {
if errors.Is(err, admindb.ErrNotFound) { if errors.Is(err, admindb.ErrNotFound) {
return nil, weberrors.ErrNotFound{What: "invite"} return nil, weberrors.ErrNotFound{What: "invite"}
@ -29,7 +33,9 @@ func (h inviteHandler) acceptForm(rw http.ResponseWriter, req *http.Request) (in
} }
return map[string]interface{}{ return map[string]interface{}{
"Invite": inv, "Token": token,
"Invite": inv,
csrf.TemplateTag: csrf.TemplateField(req), csrf.TemplateTag: csrf.TemplateField(req),
}, nil }, nil
} }
@ -39,6 +45,8 @@ func (h inviteHandler) consume(rw http.ResponseWriter, req *http.Request) (inter
return nil, weberrors.ErrBadRequest{Where: "form data", Details: err} return nil, weberrors.ErrBadRequest{Where: "form data", Details: err}
} }
alias := req.FormValue("alias")
token := req.FormValue("token") token := req.FormValue("token")
newMember, err := refs.ParseFeedRef(req.FormValue("new_member")) newMember, err := refs.ParseFeedRef(req.FormValue("new_member"))
@ -53,6 +61,15 @@ func (h inviteHandler) consume(rw http.ResponseWriter, req *http.Request) (inter
} }
return nil, err return nil, err
} }
log := logging.FromContext(req.Context())
level.Info(log).Log("event", "invite consumed", "id", inv.ID, "ref", newMember.ShortRef())
if alias != "" {
level.Warn(log).Log(
"TODO", "invite registration",
"alias", alias,
)
}
return map[string]interface{}{ return map[string]interface{}{
"TunnelAddress": "pew pew", "TunnelAddress": "pew pew",

View File

@ -80,12 +80,14 @@ func setup(t *testing.T) *testSession {
testRepo, testRepo,
"localhost", "localhost",
ts.RoomState, ts.RoomState,
ts.AuthDB, Databases{
ts.AuthFallbackDB, AuthWithSSB: ts.AuthDB,
ts.AllowListDB, AuthFallback: ts.AuthFallbackDB,
ts.InvitesDB, AllowList: ts.AllowListDB,
ts.NoticeDB, Invites: ts.InvitesDB,
ts.PinnedDB, Notices: ts.NoticeDB,
PinnedNotices: ts.PinnedDB,
},
) )
if err != nil { if err != nil {
t.Fatal("setup: handler init failed:", err) t.Fatal("setup: handler init failed:", err)

View File

@ -2,6 +2,7 @@ GenericConfirm = "Yes"
GenericGoBack = "Back" GenericGoBack = "Back"
GenericSave = "Save" GenericSave = "Save"
GenericPreview = "Preview" GenericPreview = "Preview"
GenericLanguage = "Language"
PageNotFound = "The requested page was not found." PageNotFound = "The requested page was not found."
@ -45,9 +46,13 @@ NavAdminInvites = "Invites"
NavAdminNotices = "Notices" NavAdminNotices = "Notices"
InviteAccept = "Accept invite" InviteAccept = "Accept invite"
InviteAcceptTitle = "Accept Invite InviteAcceptTitle = "Accept Invite"
InviteAcceptWelcome = "elaborate welcome message for a new member with good words and stuff." InviteAcceptWelcome = "elaborate welcome message for a new member with good words and stuff."
InviteAcceptAliasSuggestion = "The persone who created thought you might like this alias:" InviteAcceptAliasSuggestion = "The persone who created thought you might like this alias:"
InviteAcceptPublicKey = "Public Key"
InviteConsumedTitle = "Invite accepted!"
InviteConsumedWelcome = "Even more elaborate message that the person is now a member of the room!"
NoticeEditTitle = "Edit Notice" NoticeEditTitle = "Edit Notice"
NoticeList = "Notices" NoticeList = "Notices"

View File

@ -38,7 +38,7 @@
>{{.Notice.Content}}</textarea> >{{.Notice.Content}}</textarea>
<div class="my-4 flex flex-row items-center justify-start"> <div class="my-4 flex flex-row items-center justify-start">
<label class="mr-2">Language</label> <label class="mr-2">{{i18n "GenericLanguage"}}</label>
<input <input
type="text" type="text"
name="language" name="language"

View File

@ -1,16 +1,49 @@
{{ define "title" }}{{i18n "InviteAcceptTitle"}}{{ end }} {{ define "title" }}{{ i18n "InviteAcceptTitle" }}{{ end }}
{{ define "content" }} {{ define "content" }}
<div class="flex flex-col justify-center items-center h-64"> <div class="flex flex-col justify-center items-center h-64">
<span <span
id="welcome" id="welcome"
class="text-center" class="text-center"
>{{i18n "InviteAcceptWelcome"}}</span> >{{ i18n "InviteAcceptWelcome" }}</span>
{{if ne .AliasSuggestion ""}}
<!-- https://github.com/ssb-ngi-pointer/go-ssb-room/issues/60 -->
<p>{{i18n "InviteAcceptAliasSuggestion"}} {{.AliasSuggestion}}</p>
{{end}} <form id="confirm" action="{{urlTo "complete:invite:consume"}}" method="POST">
{{ .csrfField }}
<input type="hidden" name="token" value={{.Token}}>
<div class="grid grid-cols-2 gap-4">
<div class="my-4 flex flex-row items-center justify-start">
<label class="mr-2">{{ i18n "InviteAcceptPublicKey" }}</label>
<input
type="text"
name="new_member"
placeholder="@ .ed25519"
class="shadow rounded border border-transparent h-8 p-1 focus:outline-none focus:ring-2 focus:ring-pink-400 focus:border-transparent">
<span class="ml-2 text-red-400">TODO: make this a dropdown</span>
</div>
{{ if ne .Invite.AliasSuggestion "" }}
<p>{{ i18n "InviteAcceptAliasSuggestion" }}</p>
<input
name="alias"
value="{{ .Invite.AliasSuggestion }}"
></p>
{{ end }}
<a
href="javascript:history.back()"
class="px-4 h-8 shadow rounded flex flex-row justify-center items-center bg-white align-middle text-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-opacity-50"
>{{i18n "GenericGoBack"}}</a>
<button
type="submit"
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
>{{i18n "GenericConfirm"}}</button>
</div>
</form>
</div> </div>
{{end}} {{ end }}

View File

@ -13,7 +13,7 @@ type roomUserContextKeyType string
var roomUserContextKey roomUserContextKeyType = "ssb:room:httpcontext:user" var roomUserContextKey roomUserContextKeyType = "ssb:room:httpcontext:user"
// FromContext returns the user or nil of it's not logged in // FromContext returns the user or nil if not logged in
func FromContext(ctx context.Context) *admindb.User { func FromContext(ctx context.Context) *admindb.User {
v := ctx.Value(roomUserContextKey) v := ctx.Value(roomUserContextKey)
@ -25,11 +25,10 @@ func FromContext(ctx context.Context) *admindb.User {
return user return user
} }
// ContextInjecter returns the middleware that injects the user value into the request context // ContextInjecter returns middleware for injecting a user id into the request context
func ContextInjecter(fs admindb.AuthFallbackService, a *auth.Handler) func(http.Handler) http.Handler { func ContextInjecter(fs admindb.AuthFallbackService, a *auth.Handler) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
v, err := a.AuthenticateRequest(req) v, err := a.AuthenticateRequest(req)
if err != nil { if err != nil {
next.ServeHTTP(w, req) next.ServeHTTP(w, req)

View File

@ -8,7 +8,7 @@ import (
) )
// MiddlewareForTests gives us a way to inject _test users_. It should not be used in production. // MiddlewareForTests gives us a way to inject _test users_. It should not be used in production.
// This is exists here because we need to use roomUserContextKey which shouldn't be exported either. // This is part of testing.go because we need to use roomUserContextKey, which shouldn't be exported either.
// TODO: could be protected with an extra build tag. // TODO: could be protected with an extra build tag.
// (Sadly +build test does not exist https://github.com/golang/go/issues/21360 ) // (Sadly +build test does not exist https://github.com/golang/go/issues/21360 )
func MiddlewareForTests(user *admindb.User) func(http.Handler) http.Handler { func MiddlewareForTests(user *admindb.User) func(http.Handler) http.Handler {