Files
NianAIGC/backend/internal/httpapi/platform_auth.go

146 lines
4.7 KiB
Go

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
}
// NewPlatformAuthorizer creates the single HTTP-side platform authentication
// seam shared by protected route Modules.
func NewPlatformAuthorizer(state AuthState, resolver SessionResolver) (*PlatformAuthorizer, error) {
if state.Configured && resolver == nil {
return nil, fmt.Errorf("platform authorization: configured authentication requires a session resolver")
}
return &PlatformAuthorizer{state: state, resolver: resolver, now: time.Now}, 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 {
return authorizePlatformRole(authorizer.localSession(), requirement)
}
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{},
},
}
}