80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/prabhupadasays/spsays-bff/internal/rbac"
|
|
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
|
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
|
)
|
|
|
|
type staffCtxKey struct{}
|
|
|
|
// StaffContext is the caller's resolved admin access, set by RequireStaff.
|
|
type StaffContext struct {
|
|
RoleKey string
|
|
RoleName string
|
|
Perms rbac.Set
|
|
}
|
|
|
|
// Staff returns the caller's staff context, or nil outside RequireStaff.
|
|
func Staff(ctx context.Context) *StaffContext {
|
|
s, _ := ctx.Value(staffCtxKey{}).(*StaffContext)
|
|
return s
|
|
}
|
|
|
|
// RequireStaff admits accounts that hold an admin role and aren't blocked from
|
|
// the panel. It MUST run after RequireAuth.
|
|
//
|
|
// It resolves the role's permissions once per request and puts them in
|
|
// context, so the RequirePermission checks on each route are map lookups
|
|
// rather than further queries. Being staff gets you through the door only;
|
|
// every route then names the permission it needs.
|
|
func RequireStaff(staff *repository.StaffRepo) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
uid := UserID(r.Context())
|
|
if uid == "" {
|
|
response.Error(w, http.StatusUnauthorized, "not authenticated")
|
|
return
|
|
}
|
|
access, err := staff.Access(r.Context(), uid)
|
|
if errors.Is(err, repository.ErrNotStaff) {
|
|
response.Error(w, http.StatusForbidden, "admin access required")
|
|
return
|
|
}
|
|
if err != nil {
|
|
response.Error(w, http.StatusInternalServerError, "auth check failed")
|
|
return
|
|
}
|
|
if access.Blocked {
|
|
response.Error(w, http.StatusForbidden, "your admin access has been suspended")
|
|
return
|
|
}
|
|
ctx := context.WithValue(r.Context(), staffCtxKey{}, &StaffContext{
|
|
RoleKey: access.RoleKey,
|
|
RoleName: access.RoleName,
|
|
Perms: rbac.NewSet(access.Permissions),
|
|
})
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
}
|
|
|
|
// RequirePermission gates a route to staff holding perm. Must run inside
|
|
// RequireStaff; outside it there is no staff context and everything is refused.
|
|
func RequirePermission(perm string) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
s := Staff(r.Context())
|
|
if s == nil || !s.Perms.Has(perm) {
|
|
response.Error(w, http.StatusForbidden, "you don't have permission to do that")
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|