template-monorepo/internal/svc/service_context.go

203 lines
6.3 KiB
Go
Raw Permalink Normal View History

2026-05-19 11:00:28 +00:00
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package svc
import (
"context"
2026-05-19 11:00:28 +00:00
"gateway/internal/config"
2026-05-20 23:51:22 +00:00
libmongo "gateway/internal/library/mongo"
redislib "gateway/internal/library/redis"
2026-05-19 12:56:32 +00:00
"gateway/internal/library/validate"
"gateway/internal/library/zitadel"
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
"gateway/internal/middleware"
authdomrepo "gateway/internal/model/auth/domain/repository"
domauth "gateway/internal/model/auth/domain/usecase"
authrepo "gateway/internal/model/auth/repository"
authusecase "gateway/internal/model/auth/usecase"
2026-05-20 13:03:59 +00:00
domrepo "gateway/internal/model/member/domain/repository"
dommember "gateway/internal/model/member/domain/usecase"
memberusecase "gateway/internal/model/member/usecase"
domnotif "gateway/internal/model/notification/domain/usecase"
notifusecase "gateway/internal/model/notification/usecase"
dompermrepo "gateway/internal/model/permission/domain/repository"
domperm "gateway/internal/model/permission/domain/usecase"
permusecase "gateway/internal/model/permission/usecase"
"gateway/internal/worker/notification_retry"
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
"github.com/zeromicro/go-zero/rest"
2026-05-19 11:00:28 +00:00
)
type ServiceContext struct {
Config config.Config
Validator validate.Validate
Redis *redislib.Client
AuthToken domauth.TokenUseCase
AuthInvite domauth.InviteUseCase
AuthRegistrationMeta domauth.RegistrationMetaUseCase
AuthRegistrationSession domauth.RegistrationSessionUseCase
AuthLoginSession domauth.LoginSessionUseCase
AuthLoginMFAChallenge domauth.LoginMFAChallengeUseCase
Zitadel *zitadel.Client
Notifier domnotif.NotifierUseCase
NotificationAdmin domnotif.AdminNotifierUseCase
NotificationRetry *notification_retry.Runner
2026-05-20 13:03:59 +00:00
2026-05-20 23:51:22 +00:00
MemberOTP dommember.OTPUseCase
MemberTOTP dommember.TOTPUseCase
2026-05-27 09:28:13 +00:00
MemberStepUp dommember.StepUpUseCase
2026-05-20 23:51:22 +00:00
MemberProfile dommember.ProfileUseCase
MemberLifecycle dommember.LifecycleUseCase
MemberProvisioning dommember.ProvisioningUseCase
MemberTenant dommember.TenantUseCase
MemberVerifyRate dommember.VerifyRateUseCase
2026-05-20 23:51:22 +00:00
MemberRepo domrepo.MemberRepository
PermissionCatalog domperm.PermissionUseCase
PermissionRole domperm.RoleUseCase
PermissionRolePermission domperm.RolePermissionUseCase
PermissionUserRole domperm.UserRoleUseCase
PermissionRoleMapping domperm.RoleMappingUseCase
PermissionAuthQuery domperm.AuthorizationQueryUseCase
PermissionRBAC domperm.RBACUseCase
PermissionRoleRepo dompermrepo.RoleRepository
permissionModule *permusecase.Module
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
// Middlewares mounted per route group via .api `middleware:` directive.
// AuthJWT enforces Bearer token (sets actor on ctx).
// CasbinRBAC enforces RBAC; must be chained AFTER AuthJWT.
AuthJWT rest.Middleware
CasbinRBAC rest.Middleware
2026-05-19 11:00:28 +00:00
}
func NewServiceContext(c config.Config) *ServiceContext {
2026-05-19 12:56:32 +00:00
v, err := validate.NewWithDefaultEN()
if err != nil {
panic(err)
}
rds, err := redislib.NewClient(c.Redis)
if err != nil {
panic(err)
}
sc := &ServiceContext{
2026-05-19 12:56:32 +00:00
Config: c,
Validator: v,
Redis: rds,
}
authCfg := c.Auth.Defaults()
if authCfg.Enabled() {
var revoke authdomrepo.TokenRevokeStore
if rds != nil && rds.Zero() != nil {
revoke = authrepo.NewRedisTokenRevokeStore(rds)
}
sc.AuthToken = authusecase.MustTokenUseCase(authusecase.TokenUseCaseParam{
Config: authCfg,
Revoke: revoke,
})
}
zClient, err := zitadel.NewClient(c.Zitadel)
if err != nil {
panic(err)
}
sc.Zitadel = zClient
if c.Mongo.Host != "" {
mod, err := notifusecase.NewModuleFromParam(notifusecase.FactoryParam{
MongoConf: &c.Mongo,
Redis: rds,
Config: c.Notification,
})
if err != nil {
panic(err)
}
sc.Notifier = mod.Notifier
sc.NotificationAdmin = mod.Admin
sc.NotificationRetry = notification_retry.NewRunner(mod.RetryWorker)
authMod, err := authusecase.NewModuleFromParam(authusecase.ModuleParam{
MongoConf: &c.Mongo,
Redis: rds,
})
if err != nil {
panic(err)
}
sc.AuthInvite = authMod.Invite
sc.AuthRegistrationMeta = authMod.RegistrationMeta
sc.AuthRegistrationSession = authMod.RegistrationSession
sc.AuthLoginSession = authMod.LoginSession
sc.AuthLoginMFAChallenge = authMod.LoginMFAChallenge
}
2026-05-20 13:03:59 +00:00
if rds != nil && rds.Zero() != nil {
2026-05-20 23:51:22 +00:00
var mongoConf *libmongo.Conf
if c.Mongo.Host != "" {
mongoConf = &c.Mongo
}
memberMod, err := memberusecase.NewModuleFromParam(memberusecase.ModuleParam{
2026-05-20 23:51:22 +00:00
Redis: rds,
MongoConf: mongoConf,
Config: c.Member,
})
if err != nil {
panic(err)
}
2026-05-20 13:03:59 +00:00
sc.MemberOTP = memberMod.OTP
sc.MemberTOTP = memberMod.TOTP
2026-05-27 09:28:13 +00:00
sc.MemberStepUp = memberMod.StepUp
2026-05-20 13:03:59 +00:00
sc.MemberProfile = memberMod.Profile
2026-05-20 23:51:22 +00:00
sc.MemberLifecycle = memberMod.Lifecycle
sc.MemberProvisioning = memberMod.Provisioning
sc.MemberTenant = memberMod.Tenant
sc.MemberVerifyRate = memberMod.VerifyRate
sc.MemberRepo = memberMod.Members
}
if c.Mongo.Host != "" {
permMod, err := permusecase.NewModuleFromParam(permusecase.FactoryParam{
MongoConf: &c.Mongo,
Redis: rds,
Config: c.Permission,
})
if err != nil {
panic(err)
}
sc.PermissionCatalog = permMod.Permission
sc.PermissionRole = permMod.Role
sc.PermissionRolePermission = permMod.RolePermission
sc.PermissionUserRole = permMod.UserRole
sc.PermissionRoleMapping = permMod.RoleMapping
sc.PermissionAuthQuery = permMod.AuthorizationQuery
sc.PermissionRBAC = permMod.RBAC
sc.PermissionRoleRepo = permMod.Roles
sc.permissionModule = permMod
}
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
// Wire middleware last so dependencies (AuthToken / PermissionRBAC)
// are populated. Each middleware tolerates nil deps (auth → 501,
// rbac → passthrough) so the gateway boots even with partial config.
sc.AuthJWT = middleware.NewAuthJWTMiddleware(sc.AuthToken).Handle
sc.CasbinRBAC = middleware.NewCasbinRBACMiddleware(sc.PermissionRBAC, middleware.CasbinRBACOptions{}).Handle
return sc
}
func (sc *ServiceContext) StartWorkers(ctx context.Context) {
if sc.NotificationRetry != nil {
sc.NotificationRetry.Start(ctx)
}
if sc.permissionModule != nil {
if err := sc.permissionModule.StartBackground(ctx); err != nil {
panic(err)
}
}
}
func (sc *ServiceContext) StopWorkers() {
if sc.NotificationRetry != nil {
sc.NotificationRetry.Stop()
2026-05-19 11:00:28 +00:00
}
if sc.permissionModule != nil {
sc.permissionModule.StopBackground()
}
2026-05-19 11:00:28 +00:00
}