package httpapi import ( "errors" "fmt" "net/http" "time" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" ) // PlatformRequirement is the route-level authorization policy evaluated after // Identity has refreshed all account and organization claims from persistence. type PlatformRequirement string const ( PlatformApp PlatformRequirement = "app" PlatformAdmin PlatformRequirement = "admin" PlatformSuperAdmin PlatformRequirement = "super_admin" ) // PlatformAuthErrorKind is deliberately transport-oriented. Domain handlers // can map it to the current stable status without learning Cookie/parser detail. type PlatformAuthErrorKind string const ( PlatformUnauthenticated PlatformAuthErrorKind = "unauthenticated" PlatformForbidden PlatformAuthErrorKind = "forbidden" PlatformConfigurationError PlatformAuthErrorKind = "configuration_error" ) // PlatformAuthError represents only expected authentication and RBAC denials. // Resolver/database failures remain ordinary errors and must become generic 500s. type PlatformAuthError struct { Kind PlatformAuthErrorKind Status int Message string } func (err *PlatformAuthError) Error() string { if err.Message != "" { return err.Message } return string(err.Kind) } type PlatformAuthorizer struct { state AuthState resolver SessionResolver now func() time.Time localDevelopmentFallback bool } type PlatformAuthorizerOption func(*PlatformAuthorizer) // WithLocalDevelopmentFallback controls the privileged demo identity used by // the explicit local backend. Production composition disables it even when a // legacy auth environment value says authentication is optional. func WithLocalDevelopmentFallback(enabled bool) PlatformAuthorizerOption { return func(authorizer *PlatformAuthorizer) { authorizer.localDevelopmentFallback = enabled } } // NewPlatformAuthorizer creates the single HTTP-side platform authentication // seam shared by protected route Modules. func NewPlatformAuthorizer(state AuthState, resolver SessionResolver, options ...PlatformAuthorizerOption) (*PlatformAuthorizer, error) { if state.Configured && resolver == nil { return nil, fmt.Errorf("platform authorization: configured authentication requires a session resolver") } authorizer := &PlatformAuthorizer{state: state, resolver: resolver, now: time.Now, localDevelopmentFallback: true} for _, option := range options { if option != nil { option(authorizer) } } return authorizer, nil } // Authorize returns either a database-refreshed platform Session, the exact // local-development fallback when authentication is optional, an expected // typed denial, or an unclassified infrastructure error. func (authorizer *PlatformAuthorizer) Authorize(r *http.Request, requirement PlatformRequirement) (identity.Session, error) { if authorizer == nil { return identity.Session{}, fmt.Errorf("platform authorizer is not configured") } if !validPlatformRequirement(requirement) { return identity.Session{}, fmt.Errorf("unsupported platform authorization requirement %q", requirement) } if authorizer.state.Configured { cookieValue, found := readSessionCookie(r) if found && len(cookieValue) <= identity.CookieMaxValueLength { session, err := authorizer.resolver.Resolve(r.Context(), cookieValue) if err == nil { return authorizePlatformRole(session, requirement) } if !errors.Is(err, identity.ErrUnauthenticated) { return identity.Session{}, err } } } if !authorizer.state.Required && authorizer.localDevelopmentFallback { return authorizePlatformRole(authorizer.localSession(), requirement) } if !authorizer.state.Required { return identity.Session{}, &PlatformAuthError{ Kind: PlatformUnauthenticated, Status: http.StatusUnauthorized, Message: "请先登录。", } } if !authorizer.state.Configured { return identity.Session{}, &PlatformAuthError{ Kind: PlatformConfigurationError, Status: http.StatusServiceUnavailable, Message: "认证配置不完整。", } } return identity.Session{}, &PlatformAuthError{ Kind: PlatformUnauthenticated, Status: http.StatusUnauthorized, Message: "请先登录。", } } func authorizePlatformRole(session identity.Session, requirement PlatformRequirement) (identity.Session, error) { allowed := requirement == PlatformApp if requirement == PlatformAdmin { allowed = session.AuthMode == identity.AuthModeAdmin && (session.User.Role == "organization_admin" || session.User.Role == "super_admin") } if requirement == PlatformSuperAdmin { allowed = session.User.Role == "super_admin" } if allowed { return session, nil } return identity.Session{}, &PlatformAuthError{ Kind: PlatformForbidden, Status: http.StatusForbidden, Message: "需要管理员权限。", } } func validPlatformRequirement(requirement PlatformRequirement) bool { return requirement == PlatformApp || requirement == PlatformAdmin || requirement == PlatformSuperAdmin } func (authorizer *PlatformAuthorizer) localSession() identity.Session { now := authorizer.now() return identity.Session{ Version: 1, AuthMode: identity.AuthModeAdmin, IssuedAt: now.Unix(), ExpiresAt: now.Add(24 * time.Hour).Unix(), User: identity.User{ ID: "demo-merchant", Subject: "demo-merchant", Username: "13800000000", Phone: "13800000000", DisplayName: "智念用户", ClientID: "local-dev", OrganizationID: "org-demo", OrganizationName: "演示组织", Role: "super_admin", Status: "active", Authorities: []string{"zhinian_admin"}, Scope: []string{}, }, } }