feat(ucan): add delegation and invocation builders
This commit is contained in:
271
internal/crypto/ucan/delegation.go
Normal file
271
internal/crypto/ucan/delegation.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/MetaMask/go-did-it"
|
||||
"github.com/MetaMask/go-did-it/crypto"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
)
|
||||
|
||||
// DelegationBuilder provides a fluent API for creating UCAN delegations.
|
||||
// A delegation grants authority from issuer to audience to perform a command on a subject.
|
||||
type DelegationBuilder struct {
|
||||
issuer did.DID
|
||||
audience did.DID
|
||||
subject did.DID
|
||||
cmd command.Command
|
||||
pol policy.Policy
|
||||
opts []delegation.Option
|
||||
err error
|
||||
isPowerline bool
|
||||
}
|
||||
|
||||
// NewDelegationBuilder creates a builder for constructing UCAN delegations.
|
||||
func NewDelegationBuilder() *DelegationBuilder {
|
||||
return &DelegationBuilder{
|
||||
pol: policy.Policy{},
|
||||
opts: make([]delegation.Option, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Issuer sets the delegation issuer (the entity granting authority).
|
||||
func (b *DelegationBuilder) Issuer(iss did.DID) *DelegationBuilder {
|
||||
b.issuer = iss
|
||||
return b
|
||||
}
|
||||
|
||||
// IssuerString parses and sets the issuer from a DID string.
|
||||
func (b *DelegationBuilder) IssuerString(issStr string) *DelegationBuilder {
|
||||
iss, err := did.Parse(issStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid issuer DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.issuer = iss
|
||||
return b
|
||||
}
|
||||
|
||||
// Audience sets the delegation audience (the entity receiving authority).
|
||||
func (b *DelegationBuilder) Audience(aud did.DID) *DelegationBuilder {
|
||||
b.audience = aud
|
||||
return b
|
||||
}
|
||||
|
||||
// AudienceString parses and sets the audience from a DID string.
|
||||
func (b *DelegationBuilder) AudienceString(audStr string) *DelegationBuilder {
|
||||
aud, err := did.Parse(audStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid audience DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.audience = aud
|
||||
return b
|
||||
}
|
||||
|
||||
// Subject sets the delegation subject (the resource being delegated).
|
||||
// For root delegations, subject should equal issuer.
|
||||
// For powerline delegations, use Powerline() instead.
|
||||
func (b *DelegationBuilder) Subject(sub did.DID) *DelegationBuilder {
|
||||
b.subject = sub
|
||||
b.isPowerline = false
|
||||
return b
|
||||
}
|
||||
|
||||
// SubjectString parses and sets the subject from a DID string.
|
||||
func (b *DelegationBuilder) SubjectString(subStr string) *DelegationBuilder {
|
||||
sub, err := did.Parse(subStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid subject DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.subject = sub
|
||||
b.isPowerline = false
|
||||
return b
|
||||
}
|
||||
|
||||
// Powerline creates a powerline delegation (subject = nil).
|
||||
// Powerline automatically delegates all future delegations regardless of subject.
|
||||
// Use with caution - this is a very powerful pattern.
|
||||
func (b *DelegationBuilder) Powerline() *DelegationBuilder {
|
||||
b.subject = nil
|
||||
b.isPowerline = true
|
||||
return b
|
||||
}
|
||||
|
||||
// AsRoot sets the subject equal to the issuer (root delegation).
|
||||
// Root delegations are typically used to create the initial grant of authority.
|
||||
func (b *DelegationBuilder) AsRoot() *DelegationBuilder {
|
||||
b.subject = b.issuer
|
||||
b.isPowerline = false
|
||||
return b
|
||||
}
|
||||
|
||||
// Command sets the command being delegated.
|
||||
func (b *DelegationBuilder) Command(cmd command.Command) *DelegationBuilder {
|
||||
b.cmd = cmd
|
||||
return b
|
||||
}
|
||||
|
||||
// CommandString parses and sets the command from a string.
|
||||
func (b *DelegationBuilder) CommandString(cmdStr string) *DelegationBuilder {
|
||||
cmd, err := command.Parse(cmdStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid command: %w", err)
|
||||
return b
|
||||
}
|
||||
b.cmd = cmd
|
||||
return b
|
||||
}
|
||||
|
||||
// Policy sets the policy constraints on the delegation.
|
||||
func (b *DelegationBuilder) Policy(pol policy.Policy) *DelegationBuilder {
|
||||
b.pol = pol
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresAt sets when the delegation expires.
|
||||
func (b *DelegationBuilder) ExpiresAt(exp time.Time) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithExpiration(exp))
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresIn sets the delegation to expire after a duration from now.
|
||||
func (b *DelegationBuilder) ExpiresIn(d time.Duration) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithExpirationIn(d))
|
||||
return b
|
||||
}
|
||||
|
||||
// NotBefore sets when the delegation becomes valid.
|
||||
func (b *DelegationBuilder) NotBefore(nbf time.Time) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithNotBefore(nbf))
|
||||
return b
|
||||
}
|
||||
|
||||
// NotBeforeIn sets the delegation to become valid after a duration from now.
|
||||
func (b *DelegationBuilder) NotBeforeIn(d time.Duration) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithNotBeforeIn(d))
|
||||
return b
|
||||
}
|
||||
|
||||
// Meta adds metadata to the delegation.
|
||||
func (b *DelegationBuilder) Meta(key string, value any) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithMeta(key, value))
|
||||
return b
|
||||
}
|
||||
|
||||
// Nonce sets a custom nonce. If not called, a random 12-byte nonce is generated.
|
||||
func (b *DelegationBuilder) Nonce(nonce []byte) *DelegationBuilder {
|
||||
b.opts = append(b.opts, delegation.WithNonce(nonce))
|
||||
return b
|
||||
}
|
||||
|
||||
// Build creates the delegation token.
|
||||
func (b *DelegationBuilder) Build() (*delegation.Token, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
if b.issuer == nil {
|
||||
return nil, fmt.Errorf("issuer is required")
|
||||
}
|
||||
if b.audience == nil {
|
||||
return nil, fmt.Errorf("audience is required")
|
||||
}
|
||||
if b.cmd == "" {
|
||||
return nil, fmt.Errorf("command is required")
|
||||
}
|
||||
|
||||
if b.isPowerline {
|
||||
return delegation.Powerline(b.issuer, b.audience, b.cmd, b.pol, b.opts...)
|
||||
}
|
||||
|
||||
// If subject not set and not powerline, default to root delegation
|
||||
if b.subject == nil {
|
||||
b.subject = b.issuer
|
||||
}
|
||||
|
||||
return delegation.New(b.issuer, b.audience, b.cmd, b.pol, b.subject, b.opts...)
|
||||
}
|
||||
|
||||
// BuildSealed creates and signs the delegation, returning DAG-CBOR bytes and CID.
|
||||
func (b *DelegationBuilder) BuildSealed(privKey crypto.PrivateKeySigningBytes) ([]byte, cid.Cid, error) {
|
||||
tkn, err := b.Build()
|
||||
if err != nil {
|
||||
return nil, cid.Cid{}, err
|
||||
}
|
||||
return tkn.ToSealed(privKey)
|
||||
}
|
||||
|
||||
// --- Sonr-specific Delegation Builders ---
|
||||
|
||||
// NewVaultDelegation creates a delegation for vault operations.
|
||||
// cmd should be one of: VaultRead, VaultWrite, VaultSign, VaultExport, VaultImport, VaultDelete, VaultAdmin, or Vault (all).
|
||||
func NewVaultDelegation(issuer, audience did.DID, cmd command.Command, vaultCID string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
pol := VaultPolicy(vaultCID)
|
||||
return delegation.Root(issuer, audience, cmd, pol, opts...)
|
||||
}
|
||||
|
||||
// NewVaultReadDelegation creates a delegation to read from a specific vault.
|
||||
func NewVaultReadDelegation(issuer, audience did.DID, vaultCID string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewVaultDelegation(issuer, audience, VaultRead, vaultCID, opts...)
|
||||
}
|
||||
|
||||
// NewVaultWriteDelegation creates a delegation to write to a specific vault.
|
||||
func NewVaultWriteDelegation(issuer, audience did.DID, vaultCID string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewVaultDelegation(issuer, audience, VaultWrite, vaultCID, opts...)
|
||||
}
|
||||
|
||||
// NewVaultSignDelegation creates a delegation to sign with keys in a specific vault.
|
||||
func NewVaultSignDelegation(issuer, audience did.DID, vaultCID string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewVaultDelegation(issuer, audience, VaultSign, vaultCID, opts...)
|
||||
}
|
||||
|
||||
// NewVaultFullAccessDelegation creates a delegation for all vault operations on a specific vault.
|
||||
func NewVaultFullAccessDelegation(issuer, audience did.DID, vaultCID string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewVaultDelegation(issuer, audience, Vault, vaultCID, opts...)
|
||||
}
|
||||
|
||||
// NewDIDDelegation creates a delegation for DID operations.
|
||||
// cmd should be one of: DIDCreate, DIDUpdate, DIDDeactivate, or DID (all).
|
||||
func NewDIDDelegation(issuer, audience did.DID, cmd command.Command, didPattern string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
pol := DIDPolicy(didPattern)
|
||||
return delegation.Root(issuer, audience, cmd, pol, opts...)
|
||||
}
|
||||
|
||||
// NewDIDUpdateDelegation creates a delegation to update DIDs matching a pattern.
|
||||
func NewDIDUpdateDelegation(issuer, audience did.DID, didPattern string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewDIDDelegation(issuer, audience, DIDUpdate, didPattern, opts...)
|
||||
}
|
||||
|
||||
// NewDIDFullAccessDelegation creates a delegation for all DID operations on DIDs matching a pattern.
|
||||
func NewDIDFullAccessDelegation(issuer, audience did.DID, didPattern string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewDIDDelegation(issuer, audience, DID, didPattern, opts...)
|
||||
}
|
||||
|
||||
// NewDWNDelegation creates a delegation for DWN (Decentralized Web Node) operations.
|
||||
// cmd should be one of: DWNRecordsWrite, DWNRecordsRead, DWNRecordsDelete, or DWN (all).
|
||||
func NewDWNDelegation(issuer, audience did.DID, cmd command.Command, recordType string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
pol := RecordTypePolicy(recordType)
|
||||
return delegation.Root(issuer, audience, cmd, pol, opts...)
|
||||
}
|
||||
|
||||
// NewDWNReadDelegation creates a delegation to read DWN records of a specific type.
|
||||
func NewDWNReadDelegation(issuer, audience did.DID, recordType string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewDWNDelegation(issuer, audience, DWNRecordsRead, recordType, opts...)
|
||||
}
|
||||
|
||||
// NewDWNWriteDelegation creates a delegation to write DWN records of a specific type.
|
||||
func NewDWNWriteDelegation(issuer, audience did.DID, recordType string, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return NewDWNDelegation(issuer, audience, DWNRecordsWrite, recordType, opts...)
|
||||
}
|
||||
|
||||
// NewRootCapabilityDelegation creates a root delegation granting all capabilities.
|
||||
// Use with extreme caution - this grants full authority.
|
||||
func NewRootCapabilityDelegation(issuer, audience did.DID, opts ...delegation.Option) (*delegation.Token, error) {
|
||||
return delegation.Root(issuer, audience, Root, EmptyPolicy(), opts...)
|
||||
}
|
||||
259
internal/crypto/ucan/invocation.go
Normal file
259
internal/crypto/ucan/invocation.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/MetaMask/go-did-it"
|
||||
"github.com/MetaMask/go-did-it/crypto"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
// InvocationBuilder provides a fluent API for creating UCAN invocations.
|
||||
// An invocation exercises a delegated capability to perform an action.
|
||||
type InvocationBuilder struct {
|
||||
issuer did.DID
|
||||
subject did.DID
|
||||
cmd command.Command
|
||||
proofs []cid.Cid
|
||||
opts []invocation.Option
|
||||
err error
|
||||
}
|
||||
|
||||
// NewInvocationBuilder creates a builder for constructing UCAN invocations.
|
||||
func NewInvocationBuilder() *InvocationBuilder {
|
||||
return &InvocationBuilder{
|
||||
proofs: make([]cid.Cid, 0),
|
||||
opts: make([]invocation.Option, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Issuer sets the invocation issuer (the entity invoking the capability).
|
||||
func (b *InvocationBuilder) Issuer(iss did.DID) *InvocationBuilder {
|
||||
b.issuer = iss
|
||||
return b
|
||||
}
|
||||
|
||||
// IssuerString parses and sets the issuer from a DID string.
|
||||
func (b *InvocationBuilder) IssuerString(issStr string) *InvocationBuilder {
|
||||
iss, err := did.Parse(issStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid issuer DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.issuer = iss
|
||||
return b
|
||||
}
|
||||
|
||||
// Subject sets the invocation subject (the resource being acted upon).
|
||||
func (b *InvocationBuilder) Subject(sub did.DID) *InvocationBuilder {
|
||||
b.subject = sub
|
||||
return b
|
||||
}
|
||||
|
||||
// SubjectString parses and sets the subject from a DID string.
|
||||
func (b *InvocationBuilder) SubjectString(subStr string) *InvocationBuilder {
|
||||
sub, err := did.Parse(subStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid subject DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.subject = sub
|
||||
return b
|
||||
}
|
||||
|
||||
// Command sets the command being invoked.
|
||||
func (b *InvocationBuilder) Command(cmd command.Command) *InvocationBuilder {
|
||||
b.cmd = cmd
|
||||
return b
|
||||
}
|
||||
|
||||
// CommandString parses and sets the command from a string.
|
||||
func (b *InvocationBuilder) CommandString(cmdStr string) *InvocationBuilder {
|
||||
cmd, err := command.Parse(cmdStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid command: %w", err)
|
||||
return b
|
||||
}
|
||||
b.cmd = cmd
|
||||
return b
|
||||
}
|
||||
|
||||
// Proof adds a delegation CID to the proof chain.
|
||||
// Proofs should be ordered from leaf (matching invocation's issuer as audience)
|
||||
// to root delegation.
|
||||
func (b *InvocationBuilder) Proof(delegationCID cid.Cid) *InvocationBuilder {
|
||||
b.proofs = append(b.proofs, delegationCID)
|
||||
return b
|
||||
}
|
||||
|
||||
// ProofString parses and adds a delegation CID from a string.
|
||||
func (b *InvocationBuilder) ProofString(cidStr string) *InvocationBuilder {
|
||||
c, err := cid.Parse(cidStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid proof CID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.proofs = append(b.proofs, c)
|
||||
return b
|
||||
}
|
||||
|
||||
// Proofs sets all proofs at once, replacing any previously added.
|
||||
func (b *InvocationBuilder) Proofs(cids []cid.Cid) *InvocationBuilder {
|
||||
b.proofs = cids
|
||||
return b
|
||||
}
|
||||
|
||||
// Arg adds an argument to the invocation.
|
||||
func (b *InvocationBuilder) Arg(key string, value any) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithArgument(key, value))
|
||||
return b
|
||||
}
|
||||
|
||||
// Audience sets the invocation's audience (executor if different from subject).
|
||||
func (b *InvocationBuilder) Audience(aud did.DID) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithAudience(aud))
|
||||
return b
|
||||
}
|
||||
|
||||
// AudienceString parses and sets the audience from a DID string.
|
||||
func (b *InvocationBuilder) AudienceString(audStr string) *InvocationBuilder {
|
||||
aud, err := did.Parse(audStr)
|
||||
if err != nil {
|
||||
b.err = fmt.Errorf("invalid audience DID: %w", err)
|
||||
return b
|
||||
}
|
||||
b.opts = append(b.opts, invocation.WithAudience(aud))
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresAt sets when the invocation expires.
|
||||
func (b *InvocationBuilder) ExpiresAt(exp time.Time) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithExpiration(exp))
|
||||
return b
|
||||
}
|
||||
|
||||
// ExpiresIn sets the invocation to expire after a duration from now.
|
||||
func (b *InvocationBuilder) ExpiresIn(d time.Duration) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithExpirationIn(d))
|
||||
return b
|
||||
}
|
||||
|
||||
// Meta adds metadata to the invocation.
|
||||
func (b *InvocationBuilder) Meta(key string, value any) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithMeta(key, value))
|
||||
return b
|
||||
}
|
||||
|
||||
// Nonce sets a custom nonce. If not called, a random 12-byte nonce is generated.
|
||||
func (b *InvocationBuilder) Nonce(nonce []byte) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithNonce(nonce))
|
||||
return b
|
||||
}
|
||||
|
||||
// EmptyNonce sets an empty nonce for idempotent operations.
|
||||
func (b *InvocationBuilder) EmptyNonce() *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithEmptyNonce())
|
||||
return b
|
||||
}
|
||||
|
||||
// Cause sets the receipt CID that enqueued this invocation.
|
||||
func (b *InvocationBuilder) Cause(receiptCID cid.Cid) *InvocationBuilder {
|
||||
b.opts = append(b.opts, invocation.WithCause(&receiptCID))
|
||||
return b
|
||||
}
|
||||
|
||||
// Build creates the invocation token.
|
||||
func (b *InvocationBuilder) Build() (*invocation.Token, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
if b.issuer == nil {
|
||||
return nil, fmt.Errorf("issuer is required")
|
||||
}
|
||||
if b.subject == nil {
|
||||
return nil, fmt.Errorf("subject is required")
|
||||
}
|
||||
if b.cmd == "" {
|
||||
return nil, fmt.Errorf("command is required")
|
||||
}
|
||||
|
||||
return invocation.New(b.issuer, b.cmd, b.subject, b.proofs, b.opts...)
|
||||
}
|
||||
|
||||
// BuildSealed creates and signs the invocation, returning DAG-CBOR bytes and CID.
|
||||
func (b *InvocationBuilder) BuildSealed(privKey crypto.PrivateKeySigningBytes) ([]byte, cid.Cid, error) {
|
||||
tkn, err := b.Build()
|
||||
if err != nil {
|
||||
return nil, cid.Cid{}, err
|
||||
}
|
||||
return tkn.ToSealed(privKey)
|
||||
}
|
||||
|
||||
// --- Sonr-specific Invocation Builders ---
|
||||
|
||||
// VaultReadInvocation creates an invocation to read from a vault.
|
||||
func VaultReadInvocation(issuer, subject did.DID, proofs []cid.Cid, vaultCID string, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{invocation.WithArgument("vault", vaultCID)}, opts...)
|
||||
return invocation.New(issuer, VaultRead, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// VaultWriteInvocation creates an invocation to write to a vault.
|
||||
func VaultWriteInvocation(issuer, subject did.DID, proofs []cid.Cid, vaultCID string, data any, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{
|
||||
invocation.WithArgument("vault", vaultCID),
|
||||
invocation.WithArgument("data", data),
|
||||
}, opts...)
|
||||
return invocation.New(issuer, VaultWrite, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// VaultSignInvocation creates an invocation to sign with a key in a vault.
|
||||
func VaultSignInvocation(issuer, subject did.DID, proofs []cid.Cid, vaultCID string, keyID string, payload []byte, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{
|
||||
invocation.WithArgument("vault", vaultCID),
|
||||
invocation.WithArgument("key_id", keyID),
|
||||
invocation.WithArgument("payload", payload),
|
||||
}, opts...)
|
||||
return invocation.New(issuer, VaultSign, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// DIDUpdateInvocation creates an invocation to update a DID document.
|
||||
func DIDUpdateInvocation(issuer, subject did.DID, proofs []cid.Cid, targetDID string, updates map[string]any, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{
|
||||
invocation.WithArgument("did", targetDID),
|
||||
invocation.WithArgument("updates", updates),
|
||||
}, opts...)
|
||||
return invocation.New(issuer, DIDUpdate, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// DWNRecordsWriteInvocation creates an invocation to write a DWN record.
|
||||
func DWNRecordsWriteInvocation(issuer, subject did.DID, proofs []cid.Cid, recordType string, data any, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{
|
||||
invocation.WithArgument("record_type", recordType),
|
||||
invocation.WithArgument("data", data),
|
||||
}, opts...)
|
||||
return invocation.New(issuer, DWNRecordsWrite, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// DWNRecordsReadInvocation creates an invocation to read DWN records.
|
||||
func DWNRecordsReadInvocation(issuer, subject did.DID, proofs []cid.Cid, recordType string, filter map[string]any, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := []invocation.Option{
|
||||
invocation.WithArgument("record_type", recordType),
|
||||
}
|
||||
if filter != nil {
|
||||
allOpts = append(allOpts, invocation.WithArgument("filter", filter))
|
||||
}
|
||||
allOpts = append(allOpts, opts...)
|
||||
return invocation.New(issuer, DWNRecordsRead, subject, proofs, allOpts...)
|
||||
}
|
||||
|
||||
// RevocationInvocation creates an invocation to revoke a delegation.
|
||||
func RevocationInvocation(issuer, subject did.DID, proofs []cid.Cid, delegationCID cid.Cid, opts ...invocation.Option) (*invocation.Token, error) {
|
||||
allOpts := append([]invocation.Option{
|
||||
invocation.WithArgument("ucan", delegationCID),
|
||||
}, opts...)
|
||||
return invocation.New(issuer, UCANRevoke, subject, proofs, allOpts...)
|
||||
}
|
||||
213
internal/crypto/ucan/policy.go
Normal file
213
internal/crypto/ucan/policy.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
// PolicyBuilder provides a fluent API for constructing UCAN policies.
|
||||
// Policies are arrays of statements that form an implicit AND.
|
||||
type PolicyBuilder struct {
|
||||
constructors []policy.Constructor
|
||||
}
|
||||
|
||||
// NewPolicy creates a new PolicyBuilder.
|
||||
func NewPolicy() *PolicyBuilder {
|
||||
return &PolicyBuilder{
|
||||
constructors: make([]policy.Constructor, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Build constructs the final Policy from all added statements.
|
||||
func (b *PolicyBuilder) Build() (Policy, error) {
|
||||
return policy.Construct(b.constructors...)
|
||||
}
|
||||
|
||||
// MustBuild constructs the final Policy, panicking on error.
|
||||
func (b *PolicyBuilder) MustBuild() Policy {
|
||||
return policy.MustConstruct(b.constructors...)
|
||||
}
|
||||
|
||||
// Equal adds an equality constraint: selector == value
|
||||
func (b *PolicyBuilder) Equal(selector string, value any) *PolicyBuilder {
|
||||
node, err := toIPLDNode(value)
|
||||
if err != nil {
|
||||
// Store error for Build() to return
|
||||
b.constructors = append(b.constructors, func() (policy.Statement, error) {
|
||||
return nil, err
|
||||
})
|
||||
return b
|
||||
}
|
||||
b.constructors = append(b.constructors, policy.Equal(selector, node))
|
||||
return b
|
||||
}
|
||||
|
||||
// NotEqual adds an inequality constraint: selector != value
|
||||
func (b *PolicyBuilder) NotEqual(selector string, value any) *PolicyBuilder {
|
||||
node, err := toIPLDNode(value)
|
||||
if err != nil {
|
||||
b.constructors = append(b.constructors, func() (policy.Statement, error) {
|
||||
return nil, err
|
||||
})
|
||||
return b
|
||||
}
|
||||
b.constructors = append(b.constructors, policy.NotEqual(selector, node))
|
||||
return b
|
||||
}
|
||||
|
||||
// GreaterThan adds a comparison constraint: selector > value
|
||||
func (b *PolicyBuilder) GreaterThan(selector string, value int64) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.GreaterThan(selector, literal.Int(value)))
|
||||
return b
|
||||
}
|
||||
|
||||
// GreaterThanOrEqual adds a comparison constraint: selector >= value
|
||||
func (b *PolicyBuilder) GreaterThanOrEqual(selector string, value int64) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.GreaterThanOrEqual(selector, literal.Int(value)))
|
||||
return b
|
||||
}
|
||||
|
||||
// LessThan adds a comparison constraint: selector < value
|
||||
func (b *PolicyBuilder) LessThan(selector string, value int64) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.LessThan(selector, literal.Int(value)))
|
||||
return b
|
||||
}
|
||||
|
||||
// LessThanOrEqual adds a comparison constraint: selector <= value
|
||||
func (b *PolicyBuilder) LessThanOrEqual(selector string, value int64) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.LessThanOrEqual(selector, literal.Int(value)))
|
||||
return b
|
||||
}
|
||||
|
||||
// Like adds a glob pattern constraint: selector matches pattern
|
||||
// Use * for wildcard, \* for literal asterisk.
|
||||
func (b *PolicyBuilder) Like(selector, pattern string) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.Like(selector, pattern))
|
||||
return b
|
||||
}
|
||||
|
||||
// Not negates a statement
|
||||
func (b *PolicyBuilder) Not(stmt policy.Constructor) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.Not(stmt))
|
||||
return b
|
||||
}
|
||||
|
||||
// And adds a logical AND of multiple statements
|
||||
func (b *PolicyBuilder) And(stmts ...policy.Constructor) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.And(stmts...))
|
||||
return b
|
||||
}
|
||||
|
||||
// Or adds a logical OR of multiple statements
|
||||
func (b *PolicyBuilder) Or(stmts ...policy.Constructor) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.Or(stmts...))
|
||||
return b
|
||||
}
|
||||
|
||||
// All adds a universal quantifier: all elements at selector must satisfy statement
|
||||
func (b *PolicyBuilder) All(selector string, stmt policy.Constructor) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.All(selector, stmt))
|
||||
return b
|
||||
}
|
||||
|
||||
// Any adds an existential quantifier: at least one element at selector must satisfy statement
|
||||
func (b *PolicyBuilder) Any(selector string, stmt policy.Constructor) *PolicyBuilder {
|
||||
b.constructors = append(b.constructors, policy.Any(selector, stmt))
|
||||
return b
|
||||
}
|
||||
|
||||
// toIPLDNode converts a Go value to an IPLD node for policy evaluation.
|
||||
func toIPLDNode(value any) (ipld.Node, error) {
|
||||
// Handle IPLD nodes directly
|
||||
if node, ok := value.(ipld.Node); ok {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Use literal package for conversion
|
||||
return literal.Any(value)
|
||||
}
|
||||
|
||||
// --- Sonr-specific Policy Helpers ---
|
||||
|
||||
// VaultPolicy creates a policy that restricts operations to a specific vault.
|
||||
// The vault is identified by its CID.
|
||||
func VaultPolicy(vaultCID string) Policy {
|
||||
return NewPolicy().Equal(".vault", vaultCID).MustBuild()
|
||||
}
|
||||
|
||||
// DIDPolicy creates a policy that restricts operations to DIDs matching a pattern.
|
||||
// Use glob patterns: "did:sonr:*" matches all Sonr DIDs.
|
||||
func DIDPolicy(didPattern string) Policy {
|
||||
return NewPolicy().Like(".did", didPattern).MustBuild()
|
||||
}
|
||||
|
||||
// ChainPolicy creates a policy that restricts operations to a specific chain.
|
||||
func ChainPolicy(chainID string) Policy {
|
||||
return NewPolicy().Equal(".chain_id", chainID).MustBuild()
|
||||
}
|
||||
|
||||
// AccountPolicy creates a policy that restricts operations to a specific account address.
|
||||
func AccountPolicy(address string) Policy {
|
||||
return NewPolicy().Equal(".address", address).MustBuild()
|
||||
}
|
||||
|
||||
// RecordTypePolicy creates a policy for DWN operations on specific record types.
|
||||
func RecordTypePolicy(recordType string) Policy {
|
||||
return NewPolicy().Equal(".record_type", recordType).MustBuild()
|
||||
}
|
||||
|
||||
// CombinePolicies merges multiple policies into one (implicit AND).
|
||||
func CombinePolicies(policies ...Policy) Policy {
|
||||
combined := make(Policy, 0)
|
||||
for _, p := range policies {
|
||||
combined = append(combined, p...)
|
||||
}
|
||||
return combined
|
||||
}
|
||||
|
||||
// EmptyPolicy returns an empty policy (no constraints).
|
||||
func EmptyPolicy() Policy {
|
||||
return Policy{}
|
||||
}
|
||||
|
||||
// --- Policy Constructor Helpers ---
|
||||
|
||||
// These return policy.Constructor for use with And/Or/Not/All/Any
|
||||
|
||||
// EqualTo creates an equality constructor for nested policy building.
|
||||
func EqualTo(selector string, value any) policy.Constructor {
|
||||
return func() (policy.Statement, error) {
|
||||
node, err := toIPLDNode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy.Equal(selector, node)()
|
||||
}
|
||||
}
|
||||
|
||||
// NotEqualTo creates an inequality constructor for nested policy building.
|
||||
func NotEqualTo(selector string, value any) policy.Constructor {
|
||||
return func() (policy.Statement, error) {
|
||||
node, err := toIPLDNode(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return policy.NotEqual(selector, node)()
|
||||
}
|
||||
}
|
||||
|
||||
// Matches creates a glob pattern constructor for nested policy building.
|
||||
func Matches(selector, pattern string) policy.Constructor {
|
||||
return policy.Like(selector, pattern)
|
||||
}
|
||||
|
||||
// GreaterThanValue creates a comparison constructor.
|
||||
func GreaterThanValue(selector string, value int64) policy.Constructor {
|
||||
return policy.GreaterThan(selector, literal.Int(value))
|
||||
}
|
||||
|
||||
// LessThanValue creates a comparison constructor.
|
||||
func LessThanValue(selector string, value int64) policy.Constructor {
|
||||
return policy.LessThan(selector, literal.Int(value))
|
||||
}
|
||||
261
internal/crypto/ucan/types.go
Normal file
261
internal/crypto/ucan/types.go
Normal file
@@ -0,0 +1,261 @@
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
)
|
||||
|
||||
// ValidationErrorCode represents UCAN validation error types.
|
||||
// These codes match the TypeScript ValidationErrorCode type in src/ucan.ts.
|
||||
type ValidationErrorCode string
|
||||
|
||||
const (
|
||||
ErrCodeExpired ValidationErrorCode = "EXPIRED"
|
||||
ErrCodeNotYetValid ValidationErrorCode = "NOT_YET_VALID"
|
||||
ErrCodeInvalidSignature ValidationErrorCode = "INVALID_SIGNATURE"
|
||||
ErrCodePrincipalMisaligned ValidationErrorCode = "PRINCIPAL_MISALIGNMENT"
|
||||
ErrCodePolicyViolation ValidationErrorCode = "POLICY_VIOLATION"
|
||||
ErrCodeRevoked ValidationErrorCode = "REVOKED"
|
||||
ErrCodeInvalidProofChain ValidationErrorCode = "INVALID_PROOF_CHAIN"
|
||||
ErrCodeUnknownCommand ValidationErrorCode = "UNKNOWN_COMMAND"
|
||||
ErrCodeMalformedToken ValidationErrorCode = "MALFORMED_TOKEN"
|
||||
)
|
||||
|
||||
// ValidationError represents a UCAN validation failure.
|
||||
type ValidationError struct {
|
||||
Code ValidationErrorCode `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Details map[string]any `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewValidationError creates a validation error with the given code and message.
|
||||
func NewValidationError(code ValidationErrorCode, message string) *ValidationError {
|
||||
return &ValidationError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewValidationErrorWithDetails creates a validation error with additional details.
|
||||
func NewValidationErrorWithDetails(code ValidationErrorCode, message string, details map[string]any) *ValidationError {
|
||||
return &ValidationError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Details: details,
|
||||
}
|
||||
}
|
||||
|
||||
// Capability represents the semantically-relevant claim of a delegation.
|
||||
// It combines subject, command, and policy into a single authorization unit.
|
||||
type Capability struct {
|
||||
// Subject DID (resource owner) - nil for powerline delegations
|
||||
Subject string `json:"sub"`
|
||||
// Command being delegated
|
||||
Command string `json:"cmd"`
|
||||
// Policy constraints on invocation arguments
|
||||
Policy policy.Policy `json:"pol"`
|
||||
}
|
||||
|
||||
// ValidationResult represents the outcome of UCAN validation.
|
||||
type ValidationResult struct {
|
||||
Valid bool `json:"valid"`
|
||||
Capability *Capability `json:"capability,omitempty"`
|
||||
Error *ValidationError `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ValidationSuccess creates a successful validation result.
|
||||
func ValidationSuccess(cap *Capability) *ValidationResult {
|
||||
return &ValidationResult{
|
||||
Valid: true,
|
||||
Capability: cap,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidationFailure creates a failed validation result.
|
||||
func ValidationFailure(err *ValidationError) *ValidationResult {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
// CryptoAlgorithm represents supported signature algorithms.
|
||||
type CryptoAlgorithm string
|
||||
|
||||
const (
|
||||
AlgorithmEd25519 CryptoAlgorithm = "Ed25519"
|
||||
AlgorithmP256 CryptoAlgorithm = "P-256"
|
||||
AlgorithmSecp256k1 CryptoAlgorithm = "secp256k1"
|
||||
)
|
||||
|
||||
// ExecutionResult represents the outcome of an invocation execution.
|
||||
type ExecutionResult[T any, E any] struct {
|
||||
Ok *T `json:"ok,omitempty"`
|
||||
Err *E `json:"err,omitempty"`
|
||||
}
|
||||
|
||||
// IsSuccess returns true if the result is successful.
|
||||
func (r *ExecutionResult[T, E]) IsSuccess() bool {
|
||||
return r.Ok != nil
|
||||
}
|
||||
|
||||
// IsError returns true if the result is an error.
|
||||
func (r *ExecutionResult[T, E]) IsError() bool {
|
||||
return r.Err != nil
|
||||
}
|
||||
|
||||
// Success creates a successful execution result.
|
||||
func Success[T any, E any](value T) *ExecutionResult[T, E] {
|
||||
return &ExecutionResult[T, E]{Ok: &value}
|
||||
}
|
||||
|
||||
// Failure creates a failed execution result.
|
||||
func Failure[T any, E any](err E) *ExecutionResult[T, E] {
|
||||
return &ExecutionResult[T, E]{Err: &err}
|
||||
}
|
||||
|
||||
// ReceiptPayload represents the result of an invocation execution.
|
||||
// This matches the TypeScript ReceiptPayload in src/ucan.ts.
|
||||
type ReceiptPayload struct {
|
||||
// Executor DID
|
||||
Issuer string `json:"iss"`
|
||||
// CID of executed invocation
|
||||
Ran cid.Cid `json:"ran"`
|
||||
// Execution result
|
||||
Out *ExecutionResult[any, any] `json:"out"`
|
||||
// Effects - CIDs of Tasks to enqueue
|
||||
Effects []cid.Cid `json:"fx,omitempty"`
|
||||
// Optional metadata
|
||||
Meta map[string]any `json:"meta,omitempty"`
|
||||
// Issuance timestamp (Unix seconds)
|
||||
IssuedAt *int64 `json:"iat,omitempty"`
|
||||
}
|
||||
|
||||
// RevocationPayload represents a UCAN revocation request.
|
||||
// This matches the TypeScript RevocationPayload in src/ucan.ts.
|
||||
type RevocationPayload struct {
|
||||
// Revoker DID - must be issuer in delegation chain
|
||||
Issuer string `json:"iss"`
|
||||
// Subject of delegation being revoked
|
||||
Subject string `json:"sub"`
|
||||
// Revocation command (always "/ucan/revoke")
|
||||
Command string `json:"cmd"`
|
||||
// Revocation arguments
|
||||
Args RevocationArgs `json:"args"`
|
||||
// Proof chain
|
||||
Proof []cid.Cid `json:"prf"`
|
||||
// Nonce
|
||||
Nonce []byte `json:"nonce"`
|
||||
// Expiration (Unix seconds or null)
|
||||
Expiration *int64 `json:"exp"`
|
||||
}
|
||||
|
||||
// RevocationArgs contains the arguments for a revocation invocation.
|
||||
type RevocationArgs struct {
|
||||
// CID of delegation to revoke
|
||||
UCAN cid.Cid `json:"ucan"`
|
||||
}
|
||||
|
||||
// Task represents a subset of Invocation fields that uniquely determine work.
|
||||
// The Task ID is the CID of these fields.
|
||||
type Task struct {
|
||||
// Subject DID
|
||||
Subject string `json:"sub"`
|
||||
// Command to execute
|
||||
Command string `json:"cmd"`
|
||||
// Command arguments
|
||||
Args map[string]any `json:"args"`
|
||||
// Nonce for uniqueness
|
||||
Nonce []byte `json:"nonce"`
|
||||
}
|
||||
|
||||
// --- Sonr-specific Types ---
|
||||
|
||||
// VaultCapability represents authorization for vault operations.
|
||||
type VaultCapability struct {
|
||||
// VaultCID identifies the specific vault
|
||||
VaultCID string `json:"vault_cid"`
|
||||
// Operations allowed (read, write, sign, export, import, delete, admin)
|
||||
Operations []string `json:"operations"`
|
||||
}
|
||||
|
||||
// DIDCapability represents authorization for DID operations.
|
||||
type DIDCapability struct {
|
||||
// DIDPattern is a glob pattern matching allowed DIDs
|
||||
DIDPattern string `json:"did_pattern"`
|
||||
// Operations allowed (create, update, deactivate)
|
||||
Operations []string `json:"operations"`
|
||||
}
|
||||
|
||||
// DWNCapability represents authorization for DWN operations.
|
||||
type DWNCapability struct {
|
||||
// RecordType specifies the allowed record type
|
||||
RecordType string `json:"record_type"`
|
||||
// Operations allowed (read, write, delete)
|
||||
Operations []string `json:"operations"`
|
||||
}
|
||||
|
||||
// AccountCapability represents authorization for account operations.
|
||||
type AccountCapability struct {
|
||||
// ChainID specifies the blockchain
|
||||
ChainID string `json:"chain_id"`
|
||||
// Address specifies the account (or "*" for all)
|
||||
Address string `json:"address"`
|
||||
// Operations allowed
|
||||
Operations []string `json:"operations"`
|
||||
}
|
||||
|
||||
// SealedToken represents a signed UCAN token with its CID and raw bytes.
|
||||
type SealedToken struct {
|
||||
// CID is the content identifier of the sealed token
|
||||
CID cid.Cid `json:"cid"`
|
||||
// Data is the DAG-CBOR encoded envelope
|
||||
Data []byte `json:"data"`
|
||||
// Type indicates if this is a delegation or invocation
|
||||
Type string `json:"type"` // "delegation" or "invocation"
|
||||
}
|
||||
|
||||
// SealedDelegation is a type alias for a sealed delegation token.
|
||||
type SealedDelegation = SealedToken
|
||||
|
||||
// SealedInvocation is a type alias for a sealed invocation token.
|
||||
type SealedInvocation = SealedToken
|
||||
|
||||
// ProofChain represents an ordered list of delegation CIDs.
|
||||
// Ordered from leaf (matching invocation issuer) to root delegation.
|
||||
type ProofChain []cid.Cid
|
||||
|
||||
// NewProofChain creates a new proof chain from CIDs.
|
||||
func NewProofChain(cids ...cid.Cid) ProofChain {
|
||||
return ProofChain(cids)
|
||||
}
|
||||
|
||||
// Add appends a CID to the proof chain.
|
||||
func (p *ProofChain) Add(c cid.Cid) {
|
||||
*p = append(*p, c)
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the proof chain is empty.
|
||||
func (p ProofChain) IsEmpty() bool {
|
||||
return len(p) == 0
|
||||
}
|
||||
|
||||
// Root returns the root delegation CID (last in chain).
|
||||
func (p ProofChain) Root() (cid.Cid, bool) {
|
||||
if len(p) == 0 {
|
||||
return cid.Cid{}, false
|
||||
}
|
||||
return p[len(p)-1], true
|
||||
}
|
||||
|
||||
// Leaf returns the leaf delegation CID (first in chain).
|
||||
func (p ProofChain) Leaf() (cid.Cid, bool) {
|
||||
if len(p) == 0 {
|
||||
return cid.Cid{}, false
|
||||
}
|
||||
return p[0], true
|
||||
}
|
||||
195
internal/crypto/ucan/ucan.go
Normal file
195
internal/crypto/ucan/ucan.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// Package ucan provides UCAN v1.0.0-rc.1 compliant authorization
|
||||
// for the Sonr network using the official go-ucan library.
|
||||
//
|
||||
// This package wraps github.com/ucan-wg/go-ucan to provide:
|
||||
// - Delegation creation and validation
|
||||
// - Invocation creation and validation
|
||||
// - Policy evaluation
|
||||
// - Sonr-specific capability types (vault, did, dwn)
|
||||
//
|
||||
// UCAN Envelope Format (DAG-CBOR):
|
||||
//
|
||||
// [
|
||||
// Signature, // Varsig-encoded signature
|
||||
// {
|
||||
// "h": VarsigHeader, // Algorithm metadata
|
||||
// "ucan/dlg@1.0.0-rc.1": DelegationPayload // or "ucan/inv@1.0.0-rc.1"
|
||||
// }
|
||||
// ]
|
||||
package ucan
|
||||
|
||||
import (
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||
)
|
||||
|
||||
// Re-export key types from go-ucan for convenience.
|
||||
// Users should import this package instead of go-ucan directly
|
||||
// for Sonr-specific functionality.
|
||||
type (
|
||||
// Delegation is an immutable UCAN delegation token.
|
||||
Delegation = delegation.Token
|
||||
|
||||
// Invocation is an immutable UCAN invocation token.
|
||||
Invocation = invocation.Token
|
||||
|
||||
// Command is a validated UCAN command string (e.g., "/vault/read").
|
||||
Command = command.Command
|
||||
|
||||
// Policy is a list of policy statements that constrain invocation arguments.
|
||||
Policy = policy.Policy
|
||||
|
||||
// Statement is a single policy statement (equality, like, and, or, etc.).
|
||||
Statement = policy.Statement
|
||||
|
||||
// DelegationOption configures optional fields when creating a delegation.
|
||||
DelegationOption = delegation.Option
|
||||
|
||||
// InvocationOption configures optional fields when creating an invocation.
|
||||
InvocationOption = invocation.Option
|
||||
)
|
||||
|
||||
// Re-export constructors
|
||||
var (
|
||||
// NewDelegation creates a delegation: "(issuer) allows (audience) to perform (cmd+pol) on (subject)".
|
||||
NewDelegation = delegation.New
|
||||
|
||||
// NewRootDelegation creates a root delegation where subject == issuer.
|
||||
NewRootDelegation = delegation.Root
|
||||
|
||||
// NewPowerlineDelegation creates a powerline delegation (subject = nil).
|
||||
// Powerline automatically delegates all future delegations regardless of subject.
|
||||
NewPowerlineDelegation = delegation.Powerline
|
||||
|
||||
// NewInvocation creates an invocation: "(issuer) executes (command) on (subject)".
|
||||
NewInvocation = invocation.New
|
||||
|
||||
// ParseCommand validates and parses a command string.
|
||||
ParseCommand = command.Parse
|
||||
|
||||
// MustParseCommand parses a command string, panicking on error.
|
||||
MustParseCommand = command.MustParse
|
||||
|
||||
// TopCommand returns "/" - the most powerful capability (grants everything).
|
||||
TopCommand = command.Top
|
||||
|
||||
// NewCommand creates a command from segments (e.g., NewCommand("vault", "read") -> "/vault/read").
|
||||
NewCommand = command.New
|
||||
)
|
||||
|
||||
// Re-export delegation options
|
||||
var (
|
||||
// WithExpiration sets the delegation's expiration time.
|
||||
WithExpiration = delegation.WithExpiration
|
||||
|
||||
// WithExpirationIn sets expiration to now + duration.
|
||||
WithExpirationIn = delegation.WithExpirationIn
|
||||
|
||||
// WithNotBefore sets when the delegation becomes valid.
|
||||
WithNotBefore = delegation.WithNotBefore
|
||||
|
||||
// WithNotBeforeIn sets not-before to now + duration.
|
||||
WithNotBeforeIn = delegation.WithNotBeforeIn
|
||||
|
||||
// WithDelegationMeta adds metadata to the delegation.
|
||||
WithDelegationMeta = delegation.WithMeta
|
||||
|
||||
// WithDelegationNonce sets a custom nonce (default: random 12 bytes).
|
||||
WithDelegationNonce = delegation.WithNonce
|
||||
)
|
||||
|
||||
// Re-export invocation options
|
||||
var (
|
||||
// WithArgument adds a single argument to the invocation.
|
||||
WithArgument = invocation.WithArgument
|
||||
|
||||
// WithAudience sets the invocation's audience (executor if different from subject).
|
||||
WithAudience = invocation.WithAudience
|
||||
|
||||
// WithInvocationMeta adds metadata to the invocation.
|
||||
WithInvocationMeta = invocation.WithMeta
|
||||
|
||||
// WithInvocationNonce sets a custom nonce.
|
||||
WithInvocationNonce = invocation.WithNonce
|
||||
|
||||
// WithEmptyNonce sets an empty nonce for idempotent operations.
|
||||
WithEmptyNonce = invocation.WithEmptyNonce
|
||||
|
||||
// WithInvocationExpiration sets the invocation's expiration time.
|
||||
WithInvocationExpiration = invocation.WithExpiration
|
||||
|
||||
// WithInvocationExpirationIn sets expiration to now + duration.
|
||||
WithInvocationExpirationIn = invocation.WithExpirationIn
|
||||
|
||||
// WithIssuedAt sets when the invocation was created.
|
||||
WithIssuedAt = invocation.WithIssuedAt
|
||||
|
||||
// WithCause sets the receipt CID that enqueued this task.
|
||||
WithCause = invocation.WithCause
|
||||
)
|
||||
|
||||
// Standard Sonr commands following UCAN v1.0.0-rc.1 command format.
|
||||
// Commands must be lowercase, start with '/', and have no trailing slash.
|
||||
const (
|
||||
// Vault commands
|
||||
CmdVaultRead = "/vault/read"
|
||||
CmdVaultWrite = "/vault/write"
|
||||
CmdVaultSign = "/vault/sign"
|
||||
CmdVaultExport = "/vault/export"
|
||||
CmdVaultImport = "/vault/import"
|
||||
CmdVaultDelete = "/vault/delete"
|
||||
CmdVaultAdmin = "/vault/admin"
|
||||
CmdVault = "/vault" // Superuser - grants all vault commands
|
||||
|
||||
// DID commands
|
||||
CmdDIDCreate = "/did/create"
|
||||
CmdDIDUpdate = "/did/update"
|
||||
CmdDIDDeactivate = "/did/deactivate"
|
||||
CmdDID = "/did" // Superuser - grants all DID commands
|
||||
|
||||
// DWN commands
|
||||
CmdDWNRecordsWrite = "/dwn/records/write"
|
||||
CmdDWNRecordsRead = "/dwn/records/read"
|
||||
CmdDWNRecordsDelete = "/dwn/records/delete"
|
||||
CmdDWN = "/dwn" // Superuser - grants all DWN commands
|
||||
|
||||
// UCAN meta commands
|
||||
CmdUCANRevoke = "/ucan/revoke"
|
||||
|
||||
// Root command - grants everything
|
||||
CmdRoot = "/"
|
||||
)
|
||||
|
||||
// Pre-parsed Sonr commands for convenience
|
||||
var (
|
||||
VaultRead = command.MustParse(CmdVaultRead)
|
||||
VaultWrite = command.MustParse(CmdVaultWrite)
|
||||
VaultSign = command.MustParse(CmdVaultSign)
|
||||
VaultExport = command.MustParse(CmdVaultExport)
|
||||
VaultImport = command.MustParse(CmdVaultImport)
|
||||
VaultDelete = command.MustParse(CmdVaultDelete)
|
||||
VaultAdmin = command.MustParse(CmdVaultAdmin)
|
||||
Vault = command.MustParse(CmdVault)
|
||||
|
||||
DIDCreate = command.MustParse(CmdDIDCreate)
|
||||
DIDUpdate = command.MustParse(CmdDIDUpdate)
|
||||
DIDDeactivate = command.MustParse(CmdDIDDeactivate)
|
||||
DID = command.MustParse(CmdDID)
|
||||
|
||||
DWNRecordsWrite = command.MustParse(CmdDWNRecordsWrite)
|
||||
DWNRecordsRead = command.MustParse(CmdDWNRecordsRead)
|
||||
DWNRecordsDelete = command.MustParse(CmdDWNRecordsDelete)
|
||||
DWN = command.MustParse(CmdDWN)
|
||||
|
||||
UCANRevoke = command.MustParse(CmdUCANRevoke)
|
||||
Root = command.Top()
|
||||
)
|
||||
|
||||
// CommandSubsumes checks if parent command subsumes child command.
|
||||
// A command subsumes another if the child is a path extension of parent.
|
||||
// Example: "/vault" subsumes "/vault/read" and "/vault/write"
|
||||
func CommandSubsumes(parent, child Command) bool {
|
||||
return parent.Covers(child)
|
||||
}
|
||||
Reference in New Issue
Block a user