github.com/storacha/go-ucanto@v0.7.2/core/delegation/delegate.go (about)

     1  package delegation
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/storacha/go-ucanto/core/dag/blockstore"
     7  	"github.com/storacha/go-ucanto/core/ipld/block"
     8  	"github.com/storacha/go-ucanto/core/ipld/codec/cbor"
     9  	"github.com/storacha/go-ucanto/core/ipld/hash/sha256"
    10  	"github.com/storacha/go-ucanto/ucan"
    11  	udm "github.com/storacha/go-ucanto/ucan/datamodel/ucan"
    12  )
    13  
    14  // Option is an option configuring a UCAN delegation.
    15  type Option func(cfg *delegationConfig) error
    16  
    17  type delegationConfig struct {
    18  	exp   *int
    19  	noexp bool
    20  	nbf   int
    21  	nnc   string
    22  	fct   []ucan.FactBuilder
    23  	prf   Proofs
    24  }
    25  
    26  // WithExpiration configures the expiration time in UTC seconds since Unix
    27  // epoch.
    28  func WithExpiration(exp int) Option {
    29  	return func(cfg *delegationConfig) error {
    30  		cfg.exp = &exp
    31  		cfg.noexp = false
    32  		return nil
    33  	}
    34  }
    35  
    36  // WithNoExpiration configures the UCAN to never expire.
    37  //
    38  // WARNING: this will cause the delegation to be valid FOREVER, unless revoked.
    39  func WithNoExpiration() Option {
    40  	return func(cfg *delegationConfig) error {
    41  		cfg.exp = nil
    42  		cfg.noexp = true
    43  		return nil
    44  	}
    45  }
    46  
    47  // WithNotBefore configures the time in UTC seconds since Unix epoch when the
    48  // UCAN will become valid.
    49  func WithNotBefore(nbf int) Option {
    50  	return func(cfg *delegationConfig) error {
    51  		cfg.nbf = nbf
    52  		return nil
    53  	}
    54  }
    55  
    56  // WithNonce configures the nonce value for the UCAN.
    57  func WithNonce(nnc string) Option {
    58  	return func(cfg *delegationConfig) error {
    59  		cfg.nnc = nnc
    60  		return nil
    61  	}
    62  }
    63  
    64  // WithFacts configures the facts for the UCAN.
    65  func WithFacts(fct []ucan.FactBuilder) Option {
    66  	return func(cfg *delegationConfig) error {
    67  		cfg.fct = fct
    68  		return nil
    69  	}
    70  }
    71  
    72  // WithProof configures the proof(s) for the UCAN. If the `issuer` of this
    73  // `Delegation` is not the resource owner / service provider, for the delegated
    74  // capabilities, the `proofs` must contain valid `Proof`s containing
    75  // delegations to the `issuer`.
    76  func WithProof(prf ...Proof) Option {
    77  	return func(cfg *delegationConfig) error {
    78  		cfg.prf = prf
    79  		return nil
    80  	}
    81  }
    82  
    83  // Delegate creates a new signed token with a given `options.issuer`. If
    84  // expiration is not set it defaults to 30 seconds from now. Returns UCAN in
    85  // primary IPLD representation.
    86  func Delegate[C ucan.CaveatBuilder](issuer ucan.Signer, audience ucan.Principal, capabilities []ucan.Capability[C], options ...Option) (Delegation, error) {
    87  	cfg := delegationConfig{}
    88  	for _, opt := range options {
    89  		if err := opt(&cfg); err != nil {
    90  			return nil, err
    91  		}
    92  	}
    93  
    94  	bs, err := blockstore.NewBlockStore()
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	links, err := cfg.prf.WriteInto(bs)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	opts := []ucan.Option{
   105  		ucan.WithFacts(cfg.fct),
   106  		ucan.WithNonce(cfg.nnc),
   107  		ucan.WithNotBefore(cfg.nbf),
   108  		ucan.WithProof(links...),
   109  	}
   110  	if cfg.noexp {
   111  		opts = append(opts, ucan.WithNoExpiration())
   112  	}
   113  	if cfg.exp != nil {
   114  		opts = append(opts, ucan.WithExpiration(*cfg.exp))
   115  	}
   116  
   117  	data, err := ucan.Issue(issuer, audience, capabilities, opts...)
   118  	if err != nil {
   119  		return nil, fmt.Errorf("issuing UCAN: %w", err)
   120  	}
   121  
   122  	rt, err := block.Encode(data.Model(), udm.Type(), cbor.Codec, sha256.Hasher)
   123  	if err != nil {
   124  		return nil, fmt.Errorf("encoding UCAN: %w", err)
   125  	}
   126  
   127  	err = bs.Put(rt)
   128  	if err != nil {
   129  		return nil, fmt.Errorf("adding delegation root to store: %w", err)
   130  	}
   131  
   132  	return NewDelegation(rt, bs)
   133  }