package admin import ( "context" "encoding/json" "errors" "github.com/google/uuid" ) // Permission catalog frozen in ECR-013A Spec. const ( PermUsersRead = "admin.users.read" PermMembershipGrant = "admin.users.membership.grant" PermAskQuotaGrant = "admin.users.ask_quota.grant" PermOrdersRead = "admin.orders.read" PermAuditRead = "admin.audit.read" PermAnalyticsRead = "admin.analytics.read" PermContentWrite = "admin.content.write" PermRolesRead = "admin.roles.read" PermRolesWrite = "admin.roles.write" PermUsersStatusWrite = "admin.users.status.write" PermMembershipPlansRead = "admin.membership.plans.read" PermMembershipPlansWrite = "admin.membership.plans.write" PermMembershipCodesRead = "admin.membership.codes.read" PermMembershipCodesWrite = "admin.membership.codes.write" PermAskRead = "admin.ask.read" PermAskFeedbackWrite = "admin.ask.feedback.write" PermContentSafetyRead = "admin.content_safety.read" PermAIConfigRead = "admin.ai_config.read" PermCrisisRead = "admin.crisis.read" PermCMSRead = "admin.cms.read" PermPrivacyRead = "admin.privacy.read" PermExploreRead = "admin.explore.read" PermGrowthRead = "admin.growth.read" ) var knownPermissions = map[string]struct{}{ PermUsersRead: {}, PermMembershipGrant: {}, PermAskQuotaGrant: {}, PermOrdersRead: {}, PermAuditRead: {}, PermAnalyticsRead: {}, PermContentWrite: {}, PermRolesRead: {}, PermRolesWrite: {}, PermUsersStatusWrite: {}, PermMembershipPlansRead: {}, PermMembershipPlansWrite: {}, PermMembershipCodesRead: {}, PermMembershipCodesWrite: {}, PermAskRead: {}, PermAskFeedbackWrite: {}, PermContentSafetyRead: {}, PermAIConfigRead: {}, PermCrisisRead: {}, PermCMSRead: {}, PermGrowthRead: {}, PermExploreRead: {}, PermPrivacyRead: {}, } var ( ErrForbidden = errString("forbidden") ErrRoleNotFound = errString("role not found") ErrInvalidPerm = errString("invalid permission code") ErrProtectSystem = errString("system role protected") ) // AdminMe is the public admin profile (with RBAC). type AdminMe struct { ID uuid.UUID `json:"id"` Username string `json:"username"` Role string `json:"role,omitempty"` Permissions []string `json:"permissions"` } // RoleDTO is list/detail payload. type RoleDTO struct { ID uuid.UUID `json:"id"` Name string `json:"name"` System bool `json:"system"` Permissions []string `json:"permissions,omitempty"` } // HasPermission reports whether admin holds code. func (s *Service) HasPermission(ctx context.Context, adminID uuid.UUID, code string) (bool, error) { perms, err := s.Repo.ListPermissionsForAdmin(ctx, adminID) if err != nil { return false, err } for _, p := range perms { if p == code { return true, nil } } return false, nil } // DenyPermission audits a forbidden attempt. func (s *Service) DenyPermission(ctx context.Context, adminID uuid.UUID, code, path string) { meta, _ := json.Marshal(map[string]string{"permission": code, "path": path}) _ = s.Repo.InsertAudit(ctx, adminID, "permission.denied", "permission", code, meta) } // Me returns the current admin profile with permissions. func (s *Service) Me(ctx context.Context, adminID uuid.UUID) (*AdminMe, error) { acc, err := s.Repo.FindAccountByID(ctx, adminID) if err != nil || acc == nil { return nil, errors.New("admin not found") } perms, err := s.Repo.ListPermissionsForAdmin(ctx, adminID) if err != nil { return nil, err } if perms == nil { perms = []string{} } _, roleName, _ := s.Repo.GetAdminRoleMeta(ctx, adminID) return &AdminMe{ID: acc.ID, Username: acc.Username, Role: roleName, Permissions: perms}, nil } // ListRoles returns roles without permissions. func (s *Service) ListRoles(ctx context.Context) ([]RoleDTO, error) { roles, err := s.Repo.ListAdminRoles(ctx) if err != nil { return nil, err } out := make([]RoleDTO, 0, len(roles)) for _, r := range roles { out = append(out, RoleDTO{ID: r.ID, Name: r.Name, System: r.System}) } return out, nil } // GetRole returns role + permissions. func (s *Service) GetRole(ctx context.Context, id uuid.UUID) (*RoleDTO, error) { role, err := s.Repo.GetAdminRole(ctx, id) if err != nil || role == nil { return nil, ErrRoleNotFound } perms, err := s.Repo.ListRolePermissions(ctx, id) if err != nil { return nil, err } if perms == nil { perms = []string{} } return &RoleDTO{ID: role.ID, Name: role.Name, System: role.System, Permissions: perms}, nil } // ReplaceRolePermissions updates permissions with audit. func (s *Service) ReplaceRolePermissions(ctx context.Context, adminID, roleID uuid.UUID, codes []string) error { role, err := s.Repo.GetAdminRole(ctx, roleID) if err != nil || role == nil { return ErrRoleNotFound } for _, c := range codes { if _, ok := knownPermissions[c]; !ok { return ErrInvalidPerm } } if err := s.Repo.ReplaceRolePermissions(ctx, roleID, codes); err != nil { return err } meta, _ := json.Marshal(map[string]any{"permissions": codes, "role": role.Name}) return s.Repo.InsertAudit(ctx, adminID, "roles.permissions.update", "admin_role", roleID.String(), meta) }