add user.FromContext middleware

A helper package so that handler and render code isnt directly tied to
the authentication package.

Also reduces db lookup overhead to one request to sqlite per request for
the user lookup.
This commit is contained in:
Henry 2021-03-04 11:14:59 +01:00
parent 3c58a1361c
commit 91dd6017e0
3 changed files with 94 additions and 29 deletions

View File

@ -50,6 +50,12 @@ func (h invitesH) create(w http.ResponseWriter, req *http.Request) {
return
}
user := user.FromContext(req.Context())
if user == nil {
err := fmt.Errorf("warning: no user session for elevated access request")
h.r.Error(w, req, http.StatusInternalServerError, err)
return
}
aliasSuggestion := req.Form.Get("alias_suggestion")

View File

@ -26,6 +26,7 @@ import (
roomsAuth "github.com/ssb-ngi-pointer/go-ssb-room/web/handlers/auth"
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
"github.com/ssb-ngi-pointer/go-ssb-room/web/user"
)
var HTMLTemplates = []string{
@ -108,27 +109,7 @@ func New(
return u
}
}),
render.InjectTemplateFunc("is_logged_in", func(r *http.Request) interface{} {
no := func() *admindb.User { return nil }
v, err := a.AuthenticateRequest(r)
if err != nil {
return no
}
uid, ok := v.(int64)
if !ok {
panic(fmt.Sprintf("warning: not the expected ID type from authenticated session: %T\n", v))
}
user, err := fs.GetByID(r.Context(), uid)
if err != nil {
return no
}
yes := func() *admindb.User { return user }
return yes
}),
render.InjectTemplateFunc("is_logged_in", user.TemplateHelper()),
)
if err != nil {
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
@ -259,16 +240,23 @@ func New(
mainMux.Handle("/", m)
// apply middleware
var finalHandler http.Handler = mainMux
finalHandler = logging.InjectHandler(logger)(finalHandler)
finalHandler = CSRF(finalHandler)
if web.Production {
return finalHandler, nil
// apply HTTP middleware
middlewares := []func(http.Handler) http.Handler{
logging.InjectHandler(logger),
user.ContextInjecter(fs, a),
CSRF,
}
return r.GetReloader()(finalHandler), nil
if !web.Production {
middlewares = append(middlewares, r.GetReloader())
}
var finalHandler http.Handler = mainMux
for _, mw := range middlewares {
finalHandler = mw(finalHandler)
}
return finalHandler, nil
}
// utils

71
web/user/helper.go Normal file
View File

@ -0,0 +1,71 @@
// Package user implements helpers for accessing the currently logged in admin or moderator of an active request.
package user
import (
"context"
"net/http"
"github.com/ssb-ngi-pointer/go-ssb-room/admindb"
"go.mindeco.de/http/auth"
)
type roomUserContextKeyType string
var roomUserContextKey roomUserContextKeyType = "ssb:room:httpcontext:user"
// FromContext returns the user or nil of it's not logged in
func FromContext(ctx context.Context) *admindb.User {
v := ctx.Value(roomUserContextKey)
user, ok := v.(*admindb.User)
if !ok {
return nil
}
return user
}
// ContextInjecter returns the middleware that injects the user value 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)
return
}
uid, ok := v.(int64)
if !ok {
next.ServeHTTP(w, req)
return
}
user, err := fs.GetByID(req.Context(), uid)
if err != nil {
next.ServeHTTP(w, req)
return
}
ctx := context.WithValue(req.Context(), roomUserContextKey, user)
next.ServeHTTP(w, req.WithContext(ctx))
})
}
}
// TemplateHelper returns a function to be used with the http/render package.
// It has to return a function twice because the first is evaluated with the request before it gets passed onto html/template's FuncMap.
func TemplateHelper() func(*http.Request) interface{} {
return func(r *http.Request) interface{} {
no := func() *admindb.User { return nil }
user := FromContext(r.Context())
if user == nil {
return no
}
yes := func() *admindb.User { return user }
return yes
}
}