Files
oslstats/internal/rbac/preview_middleware.go

114 lines
3.0 KiB
Go

package rbac
import (
"context"
"net/http"
"strconv"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/contexts"
"git.haelnorr.com/h/oslstats/internal/db"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
// LoadPreviewRoleMiddleware loads the preview role from the session cookie if present
// and adds it to the request context. This must run after authentication but before
// the RBAC cache middleware.
func LoadPreviewRoleMiddleware(s *hws.Server, conn *db.DB) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check if there's a preview role in the cookie
roleID := getPreviewRoleCookie(r)
if roleID == 0 {
// No preview role, continue normally
next.ServeHTTP(w, r)
return
}
user := db.CurrentUser(r.Context())
if user == nil {
// User not logged in
// Auth middleware skips on certain routes like CSS files so even
// if user IS logged in, this will trigger on those routes,
// so we just pass the request on and do nothing.
next.ServeHTTP(w, r)
return
}
// Load the preview role from the database
var previewRole *db.Role
if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) {
isAdmin, err := user.IsAdmin(ctx, tx)
if err != nil {
return false, errors.Wrap(err, "user.IsAdmin")
}
if !isAdmin {
ClearPreviewRoleCookie(w)
return true, nil
}
previewRole, err = db.GetRoleByID(ctx, tx, roleID)
if err != nil {
return false, errors.Wrap(err, "db.GetRoleByID")
}
if previewRole == nil {
// Role doesn't exist anymore, clear the cookie
ClearPreviewRoleCookie(w)
return true, nil
}
return true, nil
}); !ok {
return
}
// If role was found, add it to context
if previewRole != nil {
ctx := contexts.WithPreviewRole(r.Context(), previewRole)
r = r.WithContext(ctx)
}
next.ServeHTTP(w, r)
})
}
}
// SetPreviewRoleCookie sets the preview role ID in a session cookie
func SetPreviewRoleCookie(w http.ResponseWriter, roleID int, ssl bool) {
http.SetCookie(w, &http.Cookie{
Name: "preview_role",
Value: strconv.Itoa(roleID),
Path: "/",
MaxAge: 0, // Session cookie - expires when browser closes or session times out
HttpOnly: true,
Secure: ssl,
SameSite: http.SameSiteLaxMode,
})
}
// getPreviewRoleCookie retrieves the preview role ID from the cookie
// Returns 0 if not present or invalid
func getPreviewRoleCookie(r *http.Request) int {
if r == nil {
return 0
}
cookie, err := r.Cookie("preview_role")
if err != nil {
return 0
}
roleID, err := strconv.Atoi(cookie.Value)
if err != nil {
return 0
}
return roleID
}
// ClearPreviewRoleCookie removes the preview role cookie
func ClearPreviewRoleCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: "preview_role",
Value: "",
Path: "/",
MaxAge: -1,
})
}