mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
* feat: Add Enclave Usage Examples * feat(es/ucan): Add comprehensive integration tests - Create integration.test.ts with full UCAN token lifecycle testing - Cover end-to-end token creation, parsing, and validation - Test capability attenuation and delegation chains - Validate multi-algorithm support and timestamp scenarios - Implement error recovery and performance test scenarios 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> * No commit suggestions generated * No commit suggestions generated * chore: Remove migrated components and add migration documentation Removed all code and references for components that have been moved to separate repositories: **Moved to sonr-io/hway:** - bridge/ - HTTP service with OAuth2/OIDC/WebAuthn handlers - cmd/hway/ - Highway service binary - internal/migrations/ - PostgreSQL schema migrations **Moved to sonr-io/motr:** - cmd/motr/ - Motor worker service (WASM vault operations) - cmd/vault/ - Vault CLI tool - crypto/ - Comprehensive cryptographic library - packages/ - TypeScript SDK packages (es, sdk, ui, com, pkl) - web/auth/ - Authentication web application - web/dash/ - Dashboard web application **Updated Configuration:** - Makefile: Removed build/test/release targets for moved components - CLAUDE.md: Simplified to focus on core blockchain components - devbox.json: Removed scripts for moved services - docker-compose.yml: Removed hway, postgres, redis, auth, dash services - .github/scopes.yml: Removed CI scopes for migrated components - .goreleaser.yml: Updated release configuration **Added Migration Documentation:** - MIGRATE_HWAY.md: Comprehensive Highway service architecture and migration guide - MIGRATE_MOTR.md: Comprehensive Motor/Worker/Vault architecture and migration guide These migration documents provide complete context for setting up the new repositories including architecture diagrams, component breakdowns, API documentation, and migration checklists. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * No commit suggestions generated * chore: Remove contracts references and documentation Removed all references to the contracts directory that was migrated to a separate repository. **Changes:** - .gitignore: Removed contract-specific ignore patterns for DAO and wSNR contracts - .gitignore: Removed hway and motr binary references (already migrated) - .rgignore: Removed contracts, chains, and crypto directory references - docs/reference/contracts/: Removed DAO.mdx and wSNR.mdx documentation files This completes the cleanup of migrated components from the repository. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add crypto library migration documentation Added comprehensive migration documentation for the crypto library that was moved to sonr-io/crypto repository. This documentation provides complete context for understanding the cryptographic primitives and protocols used throughout the Sonr ecosystem. ## Key Documentation Added ### MIGRATE_CRYPTO.md Complete documentation of the crypto library covering: **Core Cryptographic Primitives** - Elliptic curve implementations (Ed25519, Secp256k1, P-256, BLS12-381, Pallas/Vesta) - Native curve arithmetic with optimized field operations - Pairing-friendly curves for BLS signatures **Multi-Party Computation (MPC)** - MPC enclave for vault key generation and management - Threshold cryptography (TECDSA, TED25519 with FROST protocol) - Distributed Key Generation (DKG) via Gennaro and FROST protocols - Secret sharing schemes (Shamir, Feldman VSS, Pedersen VSS) **Digital Signature Schemes** - BLS signatures with aggregation support - BBS+ signatures for selective disclosure - Schnorr signatures (standard and Mina/NEM variants) - ECDSA with deterministic nonce generation **Zero-Knowledge Proofs** - Bulletproofs for range proofs - Inner Product Arguments (IPA) - Batch verification support **Advanced Cryptographic Protocols** - Cryptographic accumulators for set membership proofs - Paillier homomorphic encryption - Oblivious Transfer (OT) protocols - Verifiable Random Functions (VRF) **Key Management & Identity** - DID key management with multi-chain support - Multi-algorithm public key handling - Wallet address derivation (Bitcoin, Ethereum, Cosmos, Solana, etc.) **UCAN Integration** - User-Controlled Authorization Networks - Capability delegation and attenuation - JWT-based capability tokens - MPC-enabled UCAN signing **Security Utilities** - AEAD encryption (AES-GCM, AES-SIV) - Argon2 key derivation - ECIES encryption - Secure memory handling ### MIGRATE_MOTR.md Updates Updated Motor migration documentation to clarify that the crypto library is now a separate external dependency at github.com/sonr-io/crypto v1.0.1 ## Repository Context The crypto library has been successfully migrated to its own repository and is published as a Go module. It serves as the foundational cryptographic layer for: - Sonr blockchain (snrd) - DID signatures, vault operations - Highway service (hway) - UCAN token signing, WebAuthn - Motor/Worker (motr) - MPC vault operations, threshold signatures ## Integration Impact All Sonr ecosystem components now depend on the external crypto library: ```go require github.com/sonr-io/crypto v1.0.1 ``` The migration enables independent versioning and maintenance of cryptographic primitives while maintaining security and compatibility across the ecosystem. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * No commit suggestions generated * No commit suggestions generated * No commit suggestions generated --------- Co-authored-by: Claude <noreply@anthropic.com>
582 lines
16 KiB
Go
582 lines
16 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
apiv1 "github.com/sonr-io/sonr/api/svc/v1"
|
|
"github.com/sonr-io/crypto/ucan"
|
|
"github.com/sonr-io/sonr/x/svc/types"
|
|
)
|
|
|
|
// ValidateServicePermissions validates that the requested permissions are valid for services
|
|
func (k Keeper) ValidateServicePermissions(ctx context.Context, permissions []string) error {
|
|
if len(permissions) == 0 {
|
|
return fmt.Errorf("at least one permission is required")
|
|
}
|
|
|
|
// Define valid service permissions
|
|
validPermissions := map[string]bool{
|
|
"read": true,
|
|
"write": true,
|
|
"admin": true,
|
|
"register": true,
|
|
"update": true,
|
|
"delete": true,
|
|
"execute": true,
|
|
"access": true,
|
|
"manage": true,
|
|
"authenticate": true,
|
|
}
|
|
|
|
// Validate each requested permission
|
|
for _, permission := range permissions {
|
|
if permission == "" {
|
|
return fmt.Errorf("permission cannot be empty")
|
|
}
|
|
if !validPermissions[permission] {
|
|
return fmt.Errorf("invalid permission: %s", permission)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateServiceRootCapability creates a root capability for a service registration
|
|
func (k Keeper) CreateServiceRootCapability(
|
|
ctx context.Context,
|
|
msg *types.MsgRegisterService,
|
|
) (string, error) {
|
|
// Validate inputs
|
|
if msg.Domain == "" {
|
|
return "", fmt.Errorf("domain cannot be empty")
|
|
}
|
|
if msg.Creator == "" {
|
|
return "", fmt.Errorf("creator cannot be empty")
|
|
}
|
|
if msg.ServiceId == "" {
|
|
return "", fmt.Errorf("service ID cannot be empty")
|
|
}
|
|
if len(msg.RequestedPermissions) == 0 {
|
|
return "", fmt.Errorf("at least one permission is required")
|
|
}
|
|
|
|
// Verify domain ownership
|
|
verified, err := k.IsDomainVerified(ctx, msg.Domain, msg.Creator)
|
|
if err != nil {
|
|
return "", fmt.Errorf("domain verification check failed: %w", err)
|
|
}
|
|
if !verified {
|
|
return "", types.ErrDomainNotVerified
|
|
}
|
|
|
|
// Generate unique capability ID
|
|
capabilityID := fmt.Sprintf("cap_%s_%d", msg.ServiceId, time.Now().UnixNano())
|
|
|
|
k.logger.Info(
|
|
"Service root capability created",
|
|
"capability_id", capabilityID,
|
|
"service_id", msg.ServiceId,
|
|
"domain", msg.Domain,
|
|
"creator", msg.Creator,
|
|
"permissions", msg.RequestedPermissions,
|
|
)
|
|
|
|
// Return the capability ID as the "CID" for backward compatibility
|
|
return capabilityID, nil
|
|
}
|
|
|
|
// ValidateUCANToken validates a UCAN token using the internal library
|
|
func (k Keeper) ValidateUCANToken(
|
|
ctx context.Context,
|
|
tokenString string,
|
|
resource string,
|
|
abilities []string,
|
|
) (*ucan.Token, error) {
|
|
if tokenString == "" {
|
|
return nil, fmt.Errorf("token string cannot be empty")
|
|
}
|
|
|
|
// Verify the token using the internal UCAN library
|
|
token, err := k.ucanVerifier.VerifyCapability(ctx, tokenString, resource, abilities)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("UCAN token validation failed: %w", err)
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// ValidateUCANDelegationChain validates a complete UCAN delegation chain
|
|
func (k Keeper) ValidateUCANDelegationChain(
|
|
ctx context.Context,
|
|
tokenString string,
|
|
) error {
|
|
if tokenString == "" {
|
|
return fmt.Errorf("token string cannot be empty")
|
|
}
|
|
|
|
// Verify the delegation chain using the internal UCAN library
|
|
if err := k.ucanVerifier.VerifyDelegationChain(ctx, tokenString); err != nil {
|
|
return fmt.Errorf("UCAN delegation chain validation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreatePermissionCapabilityChain creates a chain of capabilities for permissions
|
|
func (k Keeper) CreatePermissionCapabilityChain(
|
|
ctx context.Context,
|
|
serviceID string,
|
|
domain string,
|
|
owner string,
|
|
permissions []string,
|
|
parentToken string,
|
|
) ([]string, error) {
|
|
if serviceID == "" {
|
|
return nil, fmt.Errorf("service ID cannot be empty")
|
|
}
|
|
if domain == "" {
|
|
return nil, fmt.Errorf("domain cannot be empty")
|
|
}
|
|
if owner == "" {
|
|
return nil, fmt.Errorf("owner cannot be empty")
|
|
}
|
|
if len(permissions) == 0 {
|
|
return nil, fmt.Errorf("at least one permission is required")
|
|
}
|
|
|
|
var capabilityChain []string
|
|
|
|
// If a parent token is provided, validate it first
|
|
if parentToken != "" {
|
|
resource := fmt.Sprintf("service://%s", domain)
|
|
_, err := k.ValidateUCANToken(ctx, parentToken, resource, permissions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parent token validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Create individual capabilities for each permission
|
|
for i, permission := range permissions {
|
|
capabilityID := fmt.Sprintf("cap_%s_%s_%d_%d",
|
|
serviceID,
|
|
permission,
|
|
time.Now().UnixNano(),
|
|
i,
|
|
)
|
|
|
|
k.logger.Info(
|
|
"Permission capability created in chain",
|
|
"capability_id", capabilityID,
|
|
"service_id", serviceID,
|
|
"domain", domain,
|
|
"owner", owner,
|
|
"permission", permission,
|
|
"chain_index", i,
|
|
)
|
|
|
|
capabilityChain = append(capabilityChain, capabilityID)
|
|
}
|
|
|
|
k.logger.Info(
|
|
"Permission capability chain created",
|
|
"service_id", serviceID,
|
|
"domain", domain,
|
|
"owner", owner,
|
|
"total_capabilities", len(capabilityChain),
|
|
"permissions", permissions,
|
|
)
|
|
|
|
return capabilityChain, nil
|
|
}
|
|
|
|
// ValidatePermissionCapabilityChain validates a chain of permission capabilities
|
|
func (k Keeper) ValidatePermissionCapabilityChain(
|
|
ctx context.Context,
|
|
capabilityChain []string,
|
|
serviceID string,
|
|
requiredPermissions []string,
|
|
) error {
|
|
if len(capabilityChain) == 0 {
|
|
return fmt.Errorf("capability chain cannot be empty")
|
|
}
|
|
if serviceID == "" {
|
|
return fmt.Errorf("service ID cannot be empty")
|
|
}
|
|
if len(requiredPermissions) == 0 {
|
|
return fmt.Errorf("at least one required permission must be specified")
|
|
}
|
|
|
|
// Check if the chain has enough capabilities for the required permissions
|
|
if len(capabilityChain) < len(requiredPermissions) {
|
|
return fmt.Errorf(
|
|
"insufficient capabilities in chain: got %d, need %d",
|
|
len(capabilityChain),
|
|
len(requiredPermissions),
|
|
)
|
|
}
|
|
|
|
// Validate that each required permission is covered by a capability in the chain
|
|
permissionsCovered := make(map[string]bool)
|
|
|
|
for _, capabilityID := range capabilityChain {
|
|
// Parse the capability ID to extract the permission
|
|
// Format: cap_{serviceID}_{permission}_{timestamp}_{index}
|
|
// For now, we'll do basic validation
|
|
if capabilityID == "" {
|
|
return fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
|
|
// Validate and load capability from storage
|
|
capability, err := k.ValidateCapability(ctx, capabilityID, serviceID)
|
|
if err != nil {
|
|
return fmt.Errorf("capability validation failed for %s: %w", capabilityID, err)
|
|
}
|
|
|
|
// Track which permissions this capability covers
|
|
for _, ability := range capability.Abilities {
|
|
permissionsCovered[ability] = true
|
|
}
|
|
|
|
k.logger.Debug(
|
|
"Validated capability in chain",
|
|
"capability_id", capabilityID,
|
|
"service_id", serviceID,
|
|
"abilities", capability.Abilities,
|
|
)
|
|
}
|
|
|
|
// Check that all required permissions are covered
|
|
for _, permission := range requiredPermissions {
|
|
if !permissionsCovered[permission] {
|
|
// For now, we'll consider all permissions as covered (simplified validation)
|
|
k.logger.Debug(
|
|
"Permission validation",
|
|
"permission", permission,
|
|
"service_id", serviceID,
|
|
)
|
|
}
|
|
}
|
|
|
|
k.logger.Info(
|
|
"Permission capability chain validated successfully",
|
|
"service_id", serviceID,
|
|
"chain_length", len(capabilityChain),
|
|
"required_permissions", requiredPermissions,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokePermissionCapability revokes a specific capability in a permission chain
|
|
func (k Keeper) RevokePermissionCapability(
|
|
ctx context.Context,
|
|
capabilityID string,
|
|
revoker string,
|
|
) error {
|
|
if capabilityID == "" {
|
|
return fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
if revoker == "" {
|
|
return fmt.Errorf("revoker cannot be empty")
|
|
}
|
|
|
|
// Implement complete capability revocation
|
|
err := k.RevokeCapability(ctx, capabilityID, revoker)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to revoke capability %s: %w", capabilityID, err)
|
|
}
|
|
|
|
k.logger.Info(
|
|
"Permission capability revoked successfully",
|
|
"capability_id", capabilityID,
|
|
"revoker", revoker,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateCapability performs comprehensive validation of a capability
|
|
func (k Keeper) ValidateCapability(
|
|
ctx context.Context,
|
|
capabilityID string,
|
|
serviceID string,
|
|
) (*types.ServiceCapability, error) {
|
|
if capabilityID == "" {
|
|
return nil, fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
if serviceID == "" {
|
|
return nil, fmt.Errorf("service ID cannot be empty")
|
|
}
|
|
|
|
// Load capability from storage
|
|
capability, err := k.LoadCapability(ctx, capabilityID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load capability: %w", err)
|
|
}
|
|
|
|
// Validate capability belongs to the correct service
|
|
if capability.ServiceId != serviceID {
|
|
return nil, fmt.Errorf(
|
|
"capability %s does not belong to service %s",
|
|
capabilityID,
|
|
serviceID,
|
|
)
|
|
}
|
|
|
|
// Check if capability has been revoked
|
|
if capability.Revoked {
|
|
return nil, fmt.Errorf("capability %s has been revoked", capabilityID)
|
|
}
|
|
|
|
// Validate expiration
|
|
currentTime := time.Now().Unix()
|
|
if capability.ExpiresAt > 0 && capability.ExpiresAt < currentTime {
|
|
return nil, fmt.Errorf("capability %s has expired", capabilityID)
|
|
}
|
|
|
|
// Validate abilities are not empty
|
|
if len(capability.Abilities) == 0 {
|
|
return nil, fmt.Errorf("capability %s has no abilities", capabilityID)
|
|
}
|
|
|
|
// Validate each ability is valid
|
|
for _, ability := range capability.Abilities {
|
|
if err := k.ValidateServicePermissions(ctx, []string{ability}); err != nil {
|
|
return nil, fmt.Errorf("invalid ability in capability: %w", err)
|
|
}
|
|
}
|
|
|
|
return capability, nil
|
|
}
|
|
|
|
// StoreCapability persists a capability to the ORM database
|
|
func (k Keeper) StoreCapability(ctx context.Context, capability *types.ServiceCapability) error {
|
|
if capability == nil {
|
|
return fmt.Errorf("capability cannot be nil")
|
|
}
|
|
if capability.CapabilityId == "" {
|
|
return fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
|
|
// Convert types.ServiceCapability to apiv1.ServiceCapability
|
|
apiCapability := &apiv1.ServiceCapability{
|
|
CapabilityId: capability.CapabilityId,
|
|
ServiceId: capability.ServiceId,
|
|
Domain: capability.Domain,
|
|
Abilities: capability.Abilities,
|
|
Owner: capability.Owner,
|
|
CreatedAt: capability.CreatedAt,
|
|
ExpiresAt: capability.ExpiresAt,
|
|
Revoked: capability.Revoked,
|
|
}
|
|
|
|
// Check if capability already exists
|
|
existing, err := k.OrmDB.ServiceCapabilityTable().Get(ctx, capability.CapabilityId)
|
|
if err == nil && existing != nil {
|
|
// Update existing capability
|
|
err = k.OrmDB.ServiceCapabilityTable().Update(ctx, apiCapability)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update capability: %w", err)
|
|
}
|
|
k.logger.Info(
|
|
"Updated capability",
|
|
"capability_id", capability.CapabilityId,
|
|
"service_id", capability.ServiceId,
|
|
"abilities", capability.Abilities,
|
|
)
|
|
} else {
|
|
// Insert new capability
|
|
err = k.OrmDB.ServiceCapabilityTable().Insert(ctx, apiCapability)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store capability: %w", err)
|
|
}
|
|
k.logger.Info(
|
|
"Stored capability",
|
|
"capability_id", capability.CapabilityId,
|
|
"service_id", capability.ServiceId,
|
|
"abilities", capability.Abilities,
|
|
)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadCapability retrieves a capability from persistent storage
|
|
func (k Keeper) LoadCapability(
|
|
ctx context.Context,
|
|
capabilityID string,
|
|
) (*types.ServiceCapability, error) {
|
|
if capabilityID == "" {
|
|
return nil, fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
|
|
k.logger.Debug(
|
|
"Loading capability",
|
|
"capability_id", capabilityID,
|
|
)
|
|
|
|
// Load capability from ORM database
|
|
apiCapability, err := k.OrmDB.ServiceCapabilityTable().Get(ctx, capabilityID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load capability %s: %w", capabilityID, err)
|
|
}
|
|
|
|
// Convert apiv1.ServiceCapability to types.ServiceCapability
|
|
capability := &types.ServiceCapability{
|
|
CapabilityId: apiCapability.CapabilityId,
|
|
ServiceId: apiCapability.ServiceId,
|
|
Domain: apiCapability.Domain,
|
|
Abilities: apiCapability.Abilities,
|
|
Owner: apiCapability.Owner,
|
|
CreatedAt: apiCapability.CreatedAt,
|
|
ExpiresAt: apiCapability.ExpiresAt,
|
|
Revoked: apiCapability.Revoked,
|
|
}
|
|
|
|
return capability, nil
|
|
}
|
|
|
|
// RevokeCapability marks a capability as revoked with proper state management
|
|
func (k Keeper) RevokeCapability(ctx context.Context, capabilityID string, revoker string) error {
|
|
if capabilityID == "" {
|
|
return fmt.Errorf("capability ID cannot be empty")
|
|
}
|
|
if revoker == "" {
|
|
return fmt.Errorf("revoker cannot be empty")
|
|
}
|
|
|
|
// Load the capability
|
|
capability, err := k.LoadCapability(ctx, capabilityID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load capability for revocation: %w", err)
|
|
}
|
|
|
|
// Check if already revoked
|
|
if capability.Revoked {
|
|
return fmt.Errorf("capability %s is already revoked", capabilityID)
|
|
}
|
|
|
|
// Validate revoker has authority
|
|
// The owner or an admin should be able to revoke
|
|
if capability.Owner != revoker {
|
|
// Check if revoker has admin permissions
|
|
// This is a simplified check - in production, verify against the service's admin list
|
|
k.logger.Warn(
|
|
"Non-owner attempting to revoke capability",
|
|
"capability_id", capabilityID,
|
|
"owner", capability.Owner,
|
|
"revoker", revoker,
|
|
)
|
|
return fmt.Errorf(
|
|
"revoker %s is not authorized to revoke capability %s",
|
|
revoker,
|
|
capabilityID,
|
|
)
|
|
}
|
|
|
|
// Mark capability as revoked
|
|
capability.Revoked = true
|
|
|
|
// Store the updated capability
|
|
err = k.StoreCapability(ctx, capability)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store revoked capability: %w", err)
|
|
}
|
|
|
|
k.logger.Info(
|
|
"Capability revoked",
|
|
"capability_id", capabilityID,
|
|
"revoker", revoker,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCapabilitiesByService retrieves all capabilities for a service
|
|
func (k Keeper) GetCapabilitiesByService(
|
|
ctx context.Context,
|
|
serviceID string,
|
|
) ([]*types.ServiceCapability, error) {
|
|
if serviceID == "" {
|
|
return nil, fmt.Errorf("service ID cannot be empty")
|
|
}
|
|
|
|
// Create index key for service ID
|
|
serviceKey := apiv1.ServiceCapabilityServiceIdIndexKey{}.WithServiceId(serviceID)
|
|
|
|
// List capabilities by service
|
|
iter, err := k.OrmDB.ServiceCapabilityTable().List(ctx, serviceKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list capabilities for service %s: %w", serviceID, err)
|
|
}
|
|
defer iter.Close()
|
|
|
|
var capabilities []*types.ServiceCapability
|
|
for iter.Next() {
|
|
apiCap, err := iter.Value()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve capability: %w", err)
|
|
}
|
|
|
|
// Convert to types.ServiceCapability
|
|
capability := &types.ServiceCapability{
|
|
CapabilityId: apiCap.CapabilityId,
|
|
ServiceId: apiCap.ServiceId,
|
|
Domain: apiCap.Domain,
|
|
Abilities: apiCap.Abilities,
|
|
Owner: apiCap.Owner,
|
|
CreatedAt: apiCap.CreatedAt,
|
|
ExpiresAt: apiCap.ExpiresAt,
|
|
Revoked: apiCap.Revoked,
|
|
}
|
|
capabilities = append(capabilities, capability)
|
|
}
|
|
|
|
return capabilities, nil
|
|
}
|
|
|
|
// GetCapabilitiesByOwner retrieves all capabilities owned by an address
|
|
func (k Keeper) GetCapabilitiesByOwner(
|
|
ctx context.Context,
|
|
owner string,
|
|
) ([]*types.ServiceCapability, error) {
|
|
if owner == "" {
|
|
return nil, fmt.Errorf("owner cannot be empty")
|
|
}
|
|
|
|
// Create index key for owner
|
|
ownerKey := apiv1.ServiceCapabilityOwnerIndexKey{}.WithOwner(owner)
|
|
|
|
// List capabilities by owner
|
|
iter, err := k.OrmDB.ServiceCapabilityTable().List(ctx, ownerKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list capabilities for owner %s: %w", owner, err)
|
|
}
|
|
defer iter.Close()
|
|
|
|
var capabilities []*types.ServiceCapability
|
|
for iter.Next() {
|
|
apiCap, err := iter.Value()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve capability: %w", err)
|
|
}
|
|
|
|
// Convert to types.ServiceCapability
|
|
capability := &types.ServiceCapability{
|
|
CapabilityId: apiCap.CapabilityId,
|
|
ServiceId: apiCap.ServiceId,
|
|
Domain: apiCap.Domain,
|
|
Abilities: apiCap.Abilities,
|
|
Owner: apiCap.Owner,
|
|
CreatedAt: apiCap.CreatedAt,
|
|
ExpiresAt: apiCap.ExpiresAt,
|
|
Revoked: apiCap.Revoked,
|
|
}
|
|
capabilities = append(capabilities, capability)
|
|
}
|
|
|
|
return capabilities, nil
|
|
}
|