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

View File

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

View File

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

View File

@ -32,16 +32,21 @@ var HTMLTemplates = []string{
"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.
// It is not registering on the mux router like other pages to clean up the authorize flow.
func Handler(
domainName string,
r *render.Renderer,
roomState *roomstate.Manager,
al admindb.AllowListService,
is admindb.InviteService,
ndb admindb.NoticesService,
pdb admindb.PinnedNoticesService,
dbs Databases,
) http.Handler {
mux := &http.ServeMux{}
// TODO: configure 404 handler
@ -59,7 +64,7 @@ func Handler(
var ah = allowListHandler{
r: r,
al: al,
al: dbs.AllowList,
}
mux.HandleFunc("/members", r.HTML("admin/allow-list.tmpl", ah.overview))
mux.HandleFunc("/members/add", ah.add)
@ -68,7 +73,7 @@ func Handler(
var ih = invitesHandler{
r: r,
db: is,
db: dbs.Invites,
domainName: domainName,
}
@ -79,8 +84,8 @@ func Handler(
var nh = noticeHandler{
r: r,
noticeDB: ndb,
pinnedDB: pdb,
noticeDB: dbs.Notices,
pinnedDB: dbs.PinnedNotices,
}
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))

View File

@ -39,18 +39,24 @@ var HTMLTemplates = []string{
"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.
func New(
logger logging.Interface,
repo repo.Interface,
domainName string,
roomState *roomstate.Manager,
as admindb.AuthWithSSBService,
fs admindb.AuthFallbackService,
al admindb.AllowListService,
is admindb.InviteService,
ns admindb.NoticesService,
ps admindb.PinnedNoticesService,
dbs Databases,
) (http.Handler, error) {
m := router.CompleteApp()
@ -93,7 +99,7 @@ func New(
if !noticeName.Valid() {
return nil
}
notice, err := ps.Get(r.Context(), noticeName, "en-GB")
notice, err := dbs.PinnedNotices.Get(r.Context(), noticeName, "en-GB")
if err != nil {
return nil
}
@ -173,7 +179,7 @@ func New(
}, nil
})
a, err := auth.NewHandler(fs,
a, err := auth.NewHandler(dbs.AuthFallback,
auth.SetStore(store),
auth.SetErrorHandler(authErrH),
auth.SetNotAuthorizedHandler(notAuthorizedH),
@ -204,18 +210,21 @@ func New(
// hookup handlers to the router
roomsAuth.Handler(m, r, a)
adminHandler := a.Authenticate(admin.Handler(
adminHandler := admin.Handler(
domainName,
r,
roomState,
al,
is,
ns,
ps))
mainMux.Handle("/admin/", adminHandler)
admin.Databases{
AllowList: dbs.AllowList,
Invites: dbs.Invites,
Notices: dbs.Notices,
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) {
notice, err := ps.Get(req.Context(), admindb.NoticeDescription, "en-GB")
notice, err := dbs.PinnedNotices.Get(req.Context(), admindb.NoticeDescription, "en-GB")
if err != nil {
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"))
var nh = noticeHandler{
notices: ns,
pinned: ps,
notices: dbs.Notices,
pinned: dbs.PinnedNotices,
}
m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nh.list))
m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nh.show))
var ih = inviteHandler{
invites: is,
invites: dbs.Invites,
}
m.Get(router.CompleteInviteAccept).Handler(r.HTML("invite/accept.tmpl", ih.acceptForm))
m.Get(router.CompleteInviteConsume).Handler(r.HTML("invite/consumed.tmpl", ih.consume))
@ -255,7 +264,7 @@ func New(
// apply HTTP middleware
middlewares := []func(http.Handler) http.Handler{
logging.InjectHandler(logger),
user.ContextInjecter(fs, a),
user.ContextInjecter(dbs.AuthFallback, a),
CSRF,
}
@ -264,8 +273,8 @@ func New(
}
var finalHandler http.Handler = mainMux
for _, mw := range middlewares {
finalHandler = mw(finalHandler)
for _, applyMiddleware := range middlewares {
finalHandler = applyMiddleware(finalHandler)
}
return finalHandler, nil

View File

@ -5,7 +5,9 @@ import (
"net/http"
"go.mindeco.de/http/render"
"go.mindeco.de/logging"
"github.com/go-kit/kit/log/level"
"github.com/gorilla/csrf"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
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) {
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 errors.Is(err, admindb.ErrNotFound) {
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{}{
"Invite": inv,
"Token": token,
"Invite": inv,
csrf.TemplateTag: csrf.TemplateField(req),
}, 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}
}
alias := req.FormValue("alias")
token := req.FormValue("token")
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
}
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{}{
"TunnelAddress": "pew pew",

View File

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

View File

@ -2,6 +2,7 @@ GenericConfirm = "Yes"
GenericGoBack = "Back"
GenericSave = "Save"
GenericPreview = "Preview"
GenericLanguage = "Language"
PageNotFound = "The requested page was not found."
@ -45,9 +46,13 @@ NavAdminInvites = "Invites"
NavAdminNotices = "Notices"
InviteAccept = "Accept invite"
InviteAcceptTitle = "Accept Invite
InviteAcceptTitle = "Accept Invite"
InviteAcceptWelcome = "elaborate welcome message for a new member with good words and stuff."
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"
NoticeList = "Notices"

View File

@ -38,7 +38,7 @@
>{{.Notice.Content}}</textarea>
<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
type="text"
name="language"

View File

@ -1,16 +1,49 @@
{{ define "title" }}{{i18n "InviteAcceptTitle"}}{{ end }}
{{ define "title" }}{{ i18n "InviteAcceptTitle" }}{{ end }}
{{ define "content" }}
<div class="flex flex-col justify-center items-center h-64">
<span
id="welcome"
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>
{{end}}
{{ end }}

View File

@ -13,7 +13,7 @@ type roomUserContextKeyType string
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 {
v := ctx.Value(roomUserContextKey)
@ -25,11 +25,10 @@ func FromContext(ctx context.Context) *admindb.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 {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
v, err := a.AuthenticateRequest(req)
if err != nil {
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.
// 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.
// (Sadly +build test does not exist https://github.com/golang/go/issues/21360 )
func MiddlewareForTests(user *admindb.User) func(http.Handler) http.Handler {