113 lines
3.4 KiB
Go
113 lines
3.4 KiB
Go
package rbac
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"git.haelnorr.com/h/golib/cookies"
|
|
"git.haelnorr.com/h/golib/hws"
|
|
"git.haelnorr.com/h/oslstats/internal/db"
|
|
"git.haelnorr.com/h/oslstats/internal/permissions"
|
|
"git.haelnorr.com/h/oslstats/internal/roles"
|
|
"git.haelnorr.com/h/oslstats/internal/throw"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
// RequirePermission creates middleware that requires a specific permission
|
|
func (c *Checker) RequirePermission(s *hws.Server, permission permissions.Permission) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := db.CurrentUser(r.Context())
|
|
if user == nil {
|
|
// Not logged in - redirect to login with page_from
|
|
cookies.SetPageFrom(w, r, r.URL.Path)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
has, err := c.UserHasPermission(r.Context(), user, permission)
|
|
if err != nil {
|
|
throw.InternalServiceError(s, w, r, "Permission check failed", errors.Wrap(err, "c.UserHasPermission"))
|
|
return
|
|
}
|
|
|
|
if !has {
|
|
throw.Forbidden(s, w, r, "You don't have permission to access this resource", errors.New("invalid permissions"))
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireRole creates middleware that requires a specific role
|
|
func (c *Checker) RequireRole(s *hws.Server, role roles.Role) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := db.CurrentUser(r.Context())
|
|
if user == nil {
|
|
// Not logged in - redirect to login
|
|
cookies.SetPageFrom(w, r, r.URL.Path)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
has, err := c.UserHasRole(r.Context(), user, role)
|
|
if err != nil {
|
|
throw.InternalServiceError(s, w, r, "Role check failed", errors.Wrap(err, "c.UserHasRole"))
|
|
return
|
|
}
|
|
|
|
if !has {
|
|
throw.Forbidden(s, w, r, "You don't have the required role to access this resource", errors.New("missing role"))
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequireAdmin is a convenience middleware for admin-only routes
|
|
func (c *Checker) RequireAdmin(server *hws.Server) func(http.Handler) http.Handler {
|
|
return c.RequireRole(server, roles.Admin)
|
|
}
|
|
|
|
// RequireActualAdmin checks if the user's ACTUAL role is admin, ignoring preview mode
|
|
// This is used for critical operations like stopping preview mode
|
|
func (c *Checker) RequireActualAdmin(s *hws.Server) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
user := db.CurrentUser(r.Context())
|
|
if user == nil {
|
|
// Not logged in - redirect to login
|
|
cookies.SetPageFrom(w, r, r.URL.Path)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Check user's ACTUAL role in database, bypassing preview mode
|
|
var hasAdmin bool
|
|
if ok := db.WithReadTx(s, w, r, c.conn, func(ctx context.Context, tx bun.Tx) (bool, error) {
|
|
var err error
|
|
hasAdmin, err = user.HasRole(ctx, tx, roles.Admin)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "user.HasRole")
|
|
}
|
|
return true, nil
|
|
}); !ok {
|
|
return
|
|
}
|
|
|
|
if !hasAdmin {
|
|
throw.Forbidden(s, w, r, "You don't have the required role to access this resource", errors.New("missing admin role"))
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|