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. // 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. 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) } }