thread-master/backend/internal/middleware/permissionrbac_middleware.go

67 lines
2.3 KiB
Go
Raw Permalink Normal View History

2026-06-26 08:37:04 +00:00
package middleware
import (
"net/http"
"haixun-backend/internal/library/authctx"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/library/permmatch"
memberdomain "haixun-backend/internal/model/member/domain/usecase"
permissiondomain "haixun-backend/internal/model/permission/domain/usecase"
"haixun-backend/internal/response"
)
// PermissionRBACMiddleware enforces catalog permissions for authenticated members.
2026-06-26 16:02:06 +00:00
// It is composed AFTER AuthJWT / MemberAuth (see svc.NewServiceContext) so the
// actor is already in context.
//
// Route coverage: defaultPermissions uses trailing-* patterns
// (/api/v1/brands/*, /api/v1/personas/*, /api/v1/placement/topics/*, ...) that
// cover nested routes (copy-missions, scan-posts, knowledge-graph, etc.).
// permission.Me also falls back to the default user permission set when a tenant
// has no seeded role_permissions, so members are never locked out by a missing seed.
2026-06-26 08:37:04 +00:00
type PermissionRBACMiddleware struct {
members memberdomain.UseCase
permissions permissiondomain.UseCase
}
func NewPermissionRBACMiddleware(
members memberdomain.UseCase,
permissions permissiondomain.UseCase,
) *PermissionRBACMiddleware {
return &PermissionRBACMiddleware{members: members, permissions: permissions}
}
func (m *PermissionRBACMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
actor, ok := authctx.ActorFromContext(r.Context())
if !ok {
response.Write(r.Context(), w, nil, app.For(code.Auth).AuthUnauthorized("missing actor"))
return
}
if m.members == nil || m.permissions == nil {
response.Write(r.Context(), w, nil, app.For(code.Permission).SysNotImplemented("permission rbac is not configured"))
return
}
member, err := m.members.GetByUID(r.Context(), actor.TenantID, actor.UID)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
me, err := m.permissions.Me(r.Context(), member, false)
if err != nil {
response.Write(r.Context(), w, nil, err)
return
}
if !permmatch.RequestAllowed(me.Permissions, r.Method, r.URL.Path) {
response.Write(r.Context(), w, nil, app.For(code.Permission).AuthForbidden("permission denied"))
return
}
next(w, r)
}
}