package middleware import ( "net/http" errs "gateway/internal/library/errors" "gateway/internal/library/errors/code" logicmember "gateway/internal/logic/member" domperm "gateway/internal/model/permission/domain/usecase" "gateway/internal/response" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/rest" ) // CasbinRBACOptions tunes the enforcement middleware. type CasbinRBACOptions struct { // PlatformAdminRoleKey short-circuits enforcement when the actor's // auth context flags them as platform admin (handled upstream); the // value is the role key seeded for that role (e.g. // "platform_super_admin"). Empty disables the bypass. PlatformAdminRoleKey string // AllowMissingActor lets unauthenticated requests through without // enforcement. Set to true on routes that do their own auth (e.g. // public catalog reads in dev mode). AllowMissingActor bool // SkipPaths is an exact-match allowlist (e.g. /api/v1/health). Useful // for opting out specific routes when the middleware is mounted // globally. SkipPaths map[string]struct{} } // CasbinRBAC returns a go-zero middleware that calls // rbac.Check(tenant, uid, path, method) and rejects with HTTP 403 when // the result is deny. // // The middleware is intentionally NOT wired into routes.go yet — wiring // happens once the platform admin role + audit log pipeline are in // place (design §6.7, §8.2). To opt-in, append it to a route group's // middleware chain in routes.go. func CasbinRBAC(rbac domperm.RBACUseCase, opts CasbinRBACOptions) rest.Middleware { skip := opts.SkipPaths if skip == nil { skip = map[string]struct{}{} } bld := errs.For(code.Permission) return func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if rbac == nil { next(w, r) return } if _, ok := skip[r.URL.Path]; ok { next(w, r) return } actor, err := logicmember.ActorFromContext(r.Context()) if err != nil { if opts.AllowMissingActor { next(w, r) return } response.Write(r.Context(), w, nil, bld.AuthUnauthorized("missing actor for rbac check").WithCause(err)) return } result, err := rbac.Check(r.Context(), &domperm.CheckRequest{ TenantID: actor.TenantID, UID: actor.UID, Path: r.URL.Path, Method: r.Method, }) if err != nil { logx.WithContext(r.Context()).Errorf( "casbin: enforce error tenant=%s uid=%s path=%s method=%s: %v", actor.TenantID, actor.UID, r.URL.Path, r.Method, err) response.Write(r.Context(), w, nil, bld.SysInternal("casbin enforce failed").WithCause(err)) return } if !result.Allow { response.Write(r.Context(), w, nil, bld.AuthForbidden("rbac denied").WithCause(nil)) return } next(w, r) } } }