template-monorepo/internal/library/actor/actor.go

46 lines
1.5 KiB
Go
Raw Normal View History

refactor(middleware): wire AuthJWT + CasbinRBAC via .api middleware directive Stop relying on a global server.Use(CloudEPJWT) that was invisible from the .api source. Protected routes now declare middleware explicitly in each @server block and goctl chains them into routes.go — the .api file is the single source of truth for "who needs Bearer / who needs RBAC". Concretely: - Rewrite middleware to go-zero's standard struct + Handle() pattern. AuthJWT becomes strict: missing/invalid Bearer returns 28501000 (was soft passthrough). CasbinRBAC stays nil-tolerant so dev/test boots without a policy. - Files renamed to goctl's stringx convention (authjwt_middleware.go, casbinrbac_middleware.go) so future `make gen-api` runs see them as already-generated and skip the empty stub. - Move actor context helpers (Actor, WithActor, ActorFromContext) into internal/library/actor so middleware and BOTH logic packages share one context key. Previously each logic package had its own private actorKey struct{}, so an actor injected for member was invisible to permission — the permission RBAC chain would always see "missing actor". member/permission actor.go are now thin type-alias shims. - .api files declare middleware per group: auth.api (public) → no middleware (register/login/token/...) auth.api (logout) → middleware: AuthJWT member.api → middleware: AuthJWT permission.api (catalog,me) → middleware: AuthJWT permission.api (admin ops) → middleware: AuthJWT,CasbinRBAC normal.api (/health) → no middleware - ServiceContext exposes AuthJWT / CasbinRBAC as rest.Middleware; the global server.Use(...) in gateway.go is removed. - Document the pattern in AGENTS.md (cross-agent rules) and generate/api/README.md (detailed examples + filename rules) so any future AI agent or human follows the same convention. make gen-api / gen-doc / lint / build all pass. Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 09:30:50 +00:00
// Package actor stores the calling user (tenant_id + uid) on a request
// context so middleware can inject it once and any downstream layer
// (handler / logic / usecase / repository) can read it without
// re-parsing the JWT.
//
// The package lives under internal/library because it is depended on
// by BOTH internal/middleware AND internal/logic/*. Defining the
// context key here is the only way to keep a single key value across
// packages (Go context.Value keys are typed by package, so each
// package would otherwise get its own isolated slot).
package actor
import (
"context"
"fmt"
)
type contextKey struct{}
// Actor identifies the calling tenant member, injected by the AuthJWT
// middleware after a successful Bearer token parse.
type Actor struct {
TenantID string
UID string
}
// WithActor stores tenant/uid on ctx. Returns the original ctx
// unchanged when either field is empty.
func WithActor(ctx context.Context, tenantID, uid string) context.Context {
if tenantID == "" || uid == "" {
return ctx
}
return context.WithValue(ctx, contextKey{}, Actor{TenantID: tenantID, UID: uid})
}
// ActorFromContext returns the actor injected by AuthJWT. The error is
// a plain error (no biz code) so callers can wrap it with their own
// scope-specific Builder when needed.
func ActorFromContext(ctx context.Context) (Actor, error) {
v, ok := ctx.Value(contextKey{}).(Actor)
if !ok || v.TenantID == "" || v.UID == "" {
return Actor{}, fmt.Errorf("missing bearer token or X-Tenant-ID/X-UID headers")
}
return v, nil
}