package rbac import ( "context" "fmt" "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 { fmt.Println(user) // 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, }) }