mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
533 lines
20 KiB
Go
533 lines
20 KiB
Go
// 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
|
|
}
|