Files
sonr/client/modules/ucan/client.go
T

533 lines
20 KiB
Go
Raw Normal View History

2025-10-03 14:45:52 -04:00
// Package ucan provides a client interface for interacting with UCAN (User-Controlled Authorization Networks) functionality.
package ucan
import (
"context"
"fmt"
"time"
"google.golang.org/grpc"
"github.com/sonr-io/sonr/client/config"
"github.com/sonr-io/sonr/client/errors"
"github.com/sonr-io/sonr/client/keys"
)
// Client provides an interface for UCAN operations.
type Client interface {
// UCAN Token Operations
CreateToken(ctx context.Context, req *CreateTokenRequest) (*UCANToken, error)
AttenuateToken(ctx context.Context, req *AttenuateTokenRequest) (*UCANToken, error)
ValidateToken(ctx context.Context, token string) (*TokenValidation, error)
RevokeToken(ctx context.Context, tokenID string) error
// Capability Operations
CreateCapability(ctx context.Context, req *CreateCapabilityRequest) (*Capability, error)
ListCapabilities(ctx context.Context, opts *ListCapabilitiesOptions) (*CapabilityListResponse, error)
RevokeCapability(ctx context.Context, capabilityID string) error
// Delegation Operations
CreateDelegation(ctx context.Context, req *CreateDelegationRequest) (*Delegation, error)
ListDelegations(ctx context.Context, opts *ListDelegationsOptions) (*DelegationListResponse, error)
RevokeDelegation(ctx context.Context, delegationID string) error
// Verification Operations
VerifyToken(ctx context.Context, token string) (*VerificationResult, error)
VerifyCapability(ctx context.Context, token string, resource string, action string) (*CapabilityVerification, error)
// Chain Operations
ValidateTokenChain(ctx context.Context, tokenChain []string) (*ChainValidation, error)
ResolveTokenChain(ctx context.Context, token string) (*TokenChain, error)
}
// UCANToken represents a UCAN JWT token.
type UCANToken struct {
Token string `json:"token"` // JWT string
ID string `json:"id"` // Token ID
Issuer string `json:"issuer"` // Issuer DID
Audience string `json:"audience"` // Audience DID
Subject string `json:"subject,omitempty"` // Subject DID
IssuedAt time.Time `json:"issued_at"`
ExpiresAt time.Time `json:"expires_at"`
NotBefore time.Time `json:"not_before,omitempty"`
Facts []string `json:"facts,omitempty"`
Capabilities []*Capability `json:"capabilities"`
Proof *Proof `json:"proof"`
Metadata map[string]any `json:"metadata,omitempty"`
}
// CreateTokenRequest configures UCAN token creation.
type CreateTokenRequest struct {
Audience string `json:"audience"` // Target audience DID
Subject string `json:"subject,omitempty"` // Subject DID (if different from issuer)
Capabilities []*Capability `json:"capabilities"` // Granted capabilities
Facts []string `json:"facts,omitempty"` // Additional facts
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Expiration time
NotBefore *time.Time `json:"not_before,omitempty"` // Validity start time
Metadata map[string]any `json:"metadata,omitempty"` // Additional metadata
}
// AttenuateTokenRequest configures token attenuation.
type AttenuateTokenRequest struct {
ParentToken string `json:"parent_token"` // Parent token to attenuate
Audience string `json:"audience"` // New audience DID
Capabilities []*Capability `json:"capabilities"` // Attenuated capabilities
Facts []string `json:"facts,omitempty"` // Additional facts
ExpiresAt *time.Time `json:"expires_at,omitempty"` // New expiration (must be earlier)
Metadata map[string]any `json:"metadata,omitempty"` // Additional metadata
}
// Capability represents a UCAN capability.
type Capability struct {
Resource string `json:"resource"` // Resource URI
Actions []string `json:"actions"` // Allowed actions
Conditions map[string]any `json:"conditions,omitempty"` // Capability conditions
Caveats []*Caveat `json:"caveats,omitempty"` // Additional restrictions
}
// Caveat represents a capability caveat (restriction).
type Caveat struct {
Type string `json:"type"` // Caveat type
Condition map[string]any `json:"condition"` // Caveat condition
}
// Proof represents cryptographic proof of authority.
type Proof struct {
Type string `json:"type"` // Proof type (e.g., "Ed25519", "ECDSA")
Created string `json:"created"` // Proof creation time
Signature string `json:"signature"` // Cryptographic signature
Challenge string `json:"challenge,omitempty"` // Challenge if required
}
// TokenValidation contains token validation results.
type TokenValidation struct {
Valid bool `json:"valid"`
Token *UCANToken `json:"token,omitempty"`
Errors []string `json:"errors,omitempty"`
Warnings []string `json:"warnings,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
Chain []*UCANToken `json:"chain,omitempty"`
}
// CreateCapabilityRequest configures capability creation.
type CreateCapabilityRequest struct {
Resource string `json:"resource"`
Actions []string `json:"actions"`
Conditions map[string]any `json:"conditions,omitempty"`
Caveats []*Caveat `json:"caveats,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
// ListCapabilitiesOptions configures capability listing.
type ListCapabilitiesOptions struct {
Resource string `json:"resource,omitempty"`
Action string `json:"action,omitempty"`
Owner string `json:"owner,omitempty"`
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset,omitempty"`
}
// CapabilityListResponse contains a list of capabilities.
type CapabilityListResponse struct {
Capabilities []*Capability `json:"capabilities"`
TotalCount uint64 `json:"total_count"`
Limit uint64 `json:"limit"`
Offset uint64 `json:"offset"`
}
// Delegation represents a UCAN delegation.
type Delegation struct {
ID string `json:"id"`
From string `json:"from"` // Delegator DID
To string `json:"to"` // Delegatee DID
Token *UCANToken `json:"token"` // Delegation token
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
Revoked bool `json:"revoked"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
}
// CreateDelegationRequest configures delegation creation.
type CreateDelegationRequest struct {
To string `json:"to"` // Delegatee DID
Capabilities []*Capability `json:"capabilities"` // Delegated capabilities
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Delegation expiration
Facts []string `json:"facts,omitempty"` // Additional facts
Metadata map[string]any `json:"metadata,omitempty"` // Additional metadata
}
// ListDelegationsOptions configures delegation listing.
type ListDelegationsOptions struct {
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Active *bool `json:"active,omitempty"` // Filter by active status
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset,omitempty"`
}
// DelegationListResponse contains a list of delegations.
type DelegationListResponse struct {
Delegations []*Delegation `json:"delegations"`
TotalCount uint64 `json:"total_count"`
Limit uint64 `json:"limit"`
Offset uint64 `json:"offset"`
}
// VerificationResult contains token verification results.
type VerificationResult struct {
Valid bool `json:"valid"`
Token *UCANToken `json:"token,omitempty"`
Chain []*UCANToken `json:"chain,omitempty"`
Errors []string `json:"errors,omitempty"`
Capabilities []*Capability `json:"capabilities,omitempty"`
}
// CapabilityVerification contains capability verification results.
type CapabilityVerification struct {
Authorized bool `json:"authorized"`
Capability *Capability `json:"capability,omitempty"`
Token *UCANToken `json:"token,omitempty"`
Reason string `json:"reason,omitempty"`
Conditions []string `json:"conditions,omitempty"`
}
// ChainValidation contains token chain validation results.
type ChainValidation struct {
Valid bool `json:"valid"`
Chain []*UCANToken `json:"chain"`
Errors []string `json:"errors,omitempty"`
Root *UCANToken `json:"root,omitempty"`
}
// TokenChain represents a resolved token chain.
type TokenChain struct {
Token *UCANToken `json:"token"`
Parents []*UCANToken `json:"parents"`
Root *UCANToken `json:"root"`
Depth int `json:"depth"`
Valid bool `json:"valid"`
Errors []string `json:"errors,omitempty"`
}
// client implements the UCAN Client interface.
type client struct {
grpcConn *grpc.ClientConn
config *config.NetworkConfig
keyring keys.KeyringManager
// UCAN operations are primarily handled through the DWN plugin
// and don't require separate gRPC clients
}
// NewClient creates a new UCAN client.
func NewClient(grpcConn *grpc.ClientConn, cfg *config.NetworkConfig) Client {
return &client{
grpcConn: grpcConn,
config: cfg,
// keyring will be injected when needed
}
}
// WithKeyring sets the keyring for UCAN operations.
func (c *client) WithKeyring(keyring keys.KeyringManager) Client {
c.keyring = keyring
return c
}
// CreateToken creates a new UCAN token.
func (c *client) CreateToken(ctx context.Context, req *CreateTokenRequest) (*UCANToken, error) {
if c.keyring == nil {
return nil, fmt.Errorf("keyring required for token creation")
}
// Convert request to keyring format
ucanReq := &keys.UCANRequest{
AudienceDID: req.Audience,
Capabilities: capabilitiesToMap(req.Capabilities),
Facts: req.Facts,
NotBefore: req.NotBefore,
ExpiresAt: req.ExpiresAt,
}
// Create token using keyring (DWN plugin)
token, err := c.keyring.CreateOriginToken(ctx, ucanReq)
if err != nil {
return nil, errors.NewModuleError("ucan", "CreateToken", err)
}
// Convert to our format
return convertToUCANToken(token, req), nil
}
// AttenuateToken creates an attenuated UCAN token.
func (c *client) AttenuateToken(ctx context.Context, req *AttenuateTokenRequest) (*UCANToken, error) {
if c.keyring == nil {
return nil, fmt.Errorf("keyring required for token attenuation")
}
// Convert request to keyring format
attenuateReq := &keys.AttenuatedUCANRequest{
ParentToken: req.ParentToken,
AudienceDID: req.Audience,
Capabilities: capabilitiesToMap(req.Capabilities),
Facts: req.Facts,
ExpiresAt: req.ExpiresAt,
}
// Create attenuated token using keyring
token, err := c.keyring.CreateAttenuatedToken(ctx, attenuateReq)
if err != nil {
return nil, errors.NewModuleError("ucan", "AttenuateToken", err)
}
// Convert to our format
return convertToUCANToken(token, nil), nil
}
// ValidateToken validates a UCAN token.
func (c *client) ValidateToken(ctx context.Context, token string) (*TokenValidation, error) {
// TODO: Implement UCAN token validation using internal/ucan package
// Should parse JWT, validate signature, check expiration, verify capability chain
// Use ucan.ValidateToken() to perform cryptographic verification
// Return structured validation results with errors and warnings
return nil, errors.NewModuleError("ucan", "ValidateToken",
fmt.Errorf("token validation not yet implemented"))
}
// RevokeToken revokes a UCAN token.
func (c *client) RevokeToken(ctx context.Context, tokenID string) error {
// TODO: Implement UCAN token revocation mechanism
// Should add token to on-chain revocation list or registry
// Integrate with DWN module to store revocation records
// Notify dependent systems of token revocation
return errors.NewModuleError("ucan", "RevokeToken",
fmt.Errorf("token revocation not yet implemented"))
}
// CreateCapability creates a new capability.
func (c *client) CreateCapability(ctx context.Context, req *CreateCapabilityRequest) (*Capability, error) {
// TODO: Implement capability creation with proper validation
// Should validate resource URIs and action permissions
// Create capability following UCAN spec format
// Store capability in persistent storage for later use
return nil, errors.NewModuleError("ucan", "CreateCapability",
fmt.Errorf("capability creation not yet implemented"))
}
// ListCapabilities lists capabilities with filtering.
func (c *client) ListCapabilities(ctx context.Context, opts *ListCapabilitiesOptions) (*CapabilityListResponse, error) {
// TODO: Implement capability listing with filtering and pagination
// Should query stored capabilities by resource, action, owner
// Support pagination with limit/offset
// Return capabilities with metadata and expiration info
return nil, errors.NewModuleError("ucan", "ListCapabilities",
fmt.Errorf("capability listing not yet implemented"))
}
// RevokeCapability revokes a capability.
func (c *client) RevokeCapability(ctx context.Context, capabilityID string) error {
// TODO: Implement capability revocation mechanism
// Should invalidate capability and update revocation registry
// Cascade revocation to dependent capabilities
// Notify systems using the revoked capability
return errors.NewModuleError("ucan", "RevokeCapability",
fmt.Errorf("capability revocation not yet implemented"))
}
// CreateDelegation creates a new delegation.
func (c *client) CreateDelegation(ctx context.Context, req *CreateDelegationRequest) (*Delegation, error) {
// Delegation is essentially creating an attenuated token for someone else
attenuateReq := &AttenuateTokenRequest{
Audience: req.To,
Capabilities: req.Capabilities,
Facts: req.Facts,
ExpiresAt: req.ExpiresAt,
}
token, err := c.AttenuateToken(ctx, attenuateReq)
if err != nil {
return nil, errors.NewModuleError("ucan", "CreateDelegation", err)
}
// Convert to delegation format
delegation := &Delegation{
ID: fmt.Sprintf("delegation_%d", time.Now().UnixNano()),
From: token.Issuer,
To: req.To,
Token: token,
CreatedAt: token.IssuedAt,
ExpiresAt: token.ExpiresAt,
Revoked: false,
}
return delegation, nil
}
// ListDelegations lists delegations with filtering.
func (c *client) ListDelegations(ctx context.Context, opts *ListDelegationsOptions) (*DelegationListResponse, error) {
// TODO: Implement delegation listing with filtering
// Should query delegations by grantor, grantee, active status
// Support pagination and date range filtering
// Include delegation status and expiration information
return nil, errors.NewModuleError("ucan", "ListDelegations",
fmt.Errorf("delegation listing not yet implemented"))
}
// RevokeDelegation revokes a delegation.
func (c *client) RevokeDelegation(ctx context.Context, delegationID string) error {
// TODO: Implement delegation revocation mechanism
// Should revoke underlying UCAN token for delegation
// Update delegation status in storage
// Notify grantee of delegation revocation
return errors.NewModuleError("ucan", "RevokeDelegation",
fmt.Errorf("delegation revocation not yet implemented"))
}
// VerifyToken verifies a UCAN token and its chain.
func (c *client) VerifyToken(ctx context.Context, token string) (*VerificationResult, error) {
// TODO: Implement comprehensive UCAN token verification
// Should verify entire delegation chain from root to current token
// Check cryptographic signatures and capability bounds
// Validate against revocation lists and expiration times
// Use internal/ucan verification functions
return nil, errors.NewModuleError("ucan", "VerifyToken",
fmt.Errorf("token verification not yet implemented"))
}
// VerifyCapability verifies if a token grants access to a specific resource/action.
func (c *client) VerifyCapability(ctx context.Context, token string, resource string, action string) (*CapabilityVerification, error) {
// TODO: Implement capability-specific verification
// Should check if token contains capability for resource and action
// Verify capability conditions and caveats are satisfied
// Check resource URI patterns and action permissions
// Return detailed authorization result with reasoning
return nil, errors.NewModuleError("ucan", "VerifyCapability",
fmt.Errorf("capability verification not yet implemented"))
}
// ValidateTokenChain validates a chain of UCAN tokens.
func (c *client) ValidateTokenChain(ctx context.Context, tokenChain []string) (*ChainValidation, error) {
// TODO: Implement UCAN delegation chain validation
// Should verify each token in chain is properly attenuated
// Check parent-child relationships and capability inheritance
// Validate chronological order and expiration bounds
// Ensure no capability escalation in delegation chain
return nil, errors.NewModuleError("ucan", "ValidateTokenChain",
fmt.Errorf("token chain validation not yet implemented"))
}
// ResolveTokenChain resolves the full chain for a token.
func (c *client) ResolveTokenChain(ctx context.Context, token string) (*TokenChain, error) {
// TODO: Implement UCAN delegation chain resolution
// Should trace token back to root authority
// Build complete chain with parent tokens and proofs
// Resolve delegator DIDs and verify signatures
// Return structured chain with validation status
return nil, errors.NewModuleError("ucan", "ResolveTokenChain",
fmt.Errorf("token chain resolution not yet implemented"))
}
// Utility functions
// capabilitiesToMap converts capabilities to map format for keyring.
func capabilitiesToMap(capabilities []*Capability) []map[string]any {
var result []map[string]any
for _, cap := range capabilities {
capMap := map[string]any{
"can": cap.Actions,
"with": cap.Resource,
}
if len(cap.Conditions) > 0 {
capMap["conditions"] = cap.Conditions
}
if len(cap.Caveats) > 0 {
capMap["caveats"] = cap.Caveats
}
result = append(result, capMap)
}
return result
}
// convertToUCANToken converts keyring token to UCAN token format.
func convertToUCANToken(token *keys.UCANToken, req *CreateTokenRequest) *UCANToken {
ucanToken := &UCANToken{
Token: token.Token,
ID: fmt.Sprintf("ucan_%d", time.Now().UnixNano()),
Issuer: token.Issuer,
IssuedAt: time.Now(),
}
if req != nil {
ucanToken.Audience = req.Audience
ucanToken.Subject = req.Subject
ucanToken.Facts = req.Facts
ucanToken.Capabilities = req.Capabilities
ucanToken.Metadata = req.Metadata
if req.ExpiresAt != nil {
ucanToken.ExpiresAt = *req.ExpiresAt
} else {
ucanToken.ExpiresAt = time.Now().Add(time.Hour) // Default 1 hour
}
if req.NotBefore != nil {
ucanToken.NotBefore = *req.NotBefore
}
}
return ucanToken
}
// CreateDefaultCapability creates a basic capability.
func CreateDefaultCapability(resource string, actions []string) *Capability {
return &Capability{
Resource: resource,
Actions: actions,
}
}
// CreateVaultCapability creates a capability for vault operations.
func CreateVaultCapability(vaultID string) *Capability {
return &Capability{
Resource: fmt.Sprintf("vault://%s", vaultID),
Actions: []string{"read", "write", "sign", "export"},
}
}
// CreateServiceCapability creates a capability for service operations.
func CreateServiceCapability(serviceID string, actions []string) *Capability {
return &Capability{
Resource: fmt.Sprintf("service://%s", serviceID),
Actions: actions,
}
}
// ValidateCapability validates a capability structure.
func ValidateCapability(cap *Capability) error {
if cap.Resource == "" {
return fmt.Errorf("capability resource cannot be empty")
}
if len(cap.Actions) == 0 {
return fmt.Errorf("capability must have at least one action")
}
return nil
}