Documentation
¶
Overview ¶
Package invocation implements the UCAN invocation specification with an immutable Token type as well as methods to convert the Token to and from the envelope-enclosed, signed and DAG-CBOR-encoded form that should most commonly be used for transport and storage.
Index ¶
- Constants
- Variables
- type Option
- func WithArgument(key string, val any) Option
- func WithArguments(args *args.Args) Option
- func WithAudience(aud did.DID) Option
- func WithCause(cause *cid.Cid) Option
- func WithEmptyNonce() Option
- func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option
- func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option
- func WithExpiration(exp time.Time) Option
- func WithExpirationIn(after time.Duration) Option
- func WithInvokedAt(iat time.Time) Option
- func WithInvokedAtIn(after time.Duration) Option
- func WithMeta(key string, val any) Option
- func WithNonce(nonce []byte) Option
- func WithoutInvokedAt() Option
- type Token
- func Decode(b []byte, decFn codec.Decoder) (*Token, error)
- func DecodeReader(r io.Reader, decFn codec.Decoder) (*Token, error)
- func FromDagCbor(data []byte) (*Token, error)
- func FromDagCborReader(r io.Reader) (*Token, error)
- func FromDagJson(data []byte) (*Token, error)
- func FromDagJsonReader(r io.Reader) (*Token, error)
- func FromIPLD(node datamodel.Node) (*Token, error)
- func FromSealed(data []byte) (*Token, cid.Cid, error)
- func FromSealedReader(r io.Reader) (*Token, cid.Cid, error)
- func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...Option) (*Token, error)
- func (t *Token) Arguments() args.ReadOnly
- func (t *Token) Audience() did.DID
- func (t *Token) Cause() *cid.Cid
- func (t *Token) Command() command.Command
- func (t *Token) Encode(privKey crypto.PrivKey, encFn codec.Encoder) ([]byte, error)
- func (t *Token) EncodeWriter(w io.Writer, privKey crypto.PrivKey, encFn codec.Encoder) error
- func (t *Token) ExecutionAllowed(loader delegation.Loader) error
- func (t *Token) ExecutionAllowedWithArgsHook(loader delegation.Loader, hook func(args args.ReadOnly) (*args.Args, error)) error
- func (t *Token) Expiration() *time.Time
- func (t *Token) InvokedAt() *time.Time
- func (t *Token) IsValidAt(ti time.Time) bool
- func (t *Token) IsValidNow() bool
- func (t *Token) Issuer() did.DID
- func (t *Token) Meta() meta.ReadOnly
- func (t *Token) Nonce() []byte
- func (t *Token) Proof() []cid.Cid
- func (t *Token) String() string
- func (t *Token) Subject() did.DID
- func (t *Token) ToDagCbor(privKey crypto.PrivKey) ([]byte, error)
- func (t *Token) ToDagCborWriter(w io.Writer, privKey crypto.PrivKey) error
- func (t *Token) ToDagJson(privKey crypto.PrivKey) ([]byte, error)
- func (t *Token) ToDagJsonWriter(w io.Writer, privKey crypto.PrivKey) error
- func (t *Token) ToSealed(privKey crypto.PrivKey) ([]byte, cid.Cid, error)
- func (t *Token) ToSealedWriter(w io.Writer, privKey crypto.PrivKey) (cid.Cid, error)
Examples ¶
Constants ¶
const Tag = "ucan/inv@1.0.0-rc.1"
Tag is the string used as a key within the SigPayload that identifies that the TokenPayload is an invocation.
Variables ¶
var ( // ErrNoProof is returned when no delegations were provided to prove // that the invocation should be executed. ErrNoProof = errors.New("at least one delegation must be provided to validate the invocation") // ErrLastNotRoot is returned if the last delegation token in the proof // chain is not a root delegation token. ErrLastNotRoot = errors.New("the last delegation token in proof chain must be a root token") // ErrBrokenChain is returned when the Audience of a delegation is // not the Issuer of the previous one. ErrBrokenChain = errors.New("delegation proof chain doesn't connect the invocation to the subject") // ErrWrongSub is returned when the Subject of a delegation is not the invocation audience. ErrWrongSub = errors.New("delegation subject need to match the invocation audience") // ErrCommandNotCovered is returned when a delegation command doesn't cover (identical or parent of) the // next delegation or invocation's command. ErrCommandNotCovered = errors.New("allowed command doesn't cover the next delegation or invocation") )
Principal alignment errors
var ( // ErrMissingDelegation ErrMissingDelegation = errors.New("loader missing delegation for proof chain") )
Loading errors
var ErrPolicyNotSatisfied = errors.New("the following UCAN policy is not satisfied")
ErrPolicyNotSatisfied is returned when the provided Arguments don't satisfy one or more of the aggregated Policy Statements
var ( // ErrTokenExpired is returned if a token is invalid at execution time ErrTokenInvalidNow = errors.New("token has expired") )
Time bound errors
Functions ¶
This section is empty.
Types ¶
type Option ¶
Option is a type that allows optional fields to be set during the creation of an invocation Token.
func WithArgument ¶
WithArgument adds a key/value pair to the Token's Arguments field.
func WithArguments ¶
WithArguments merges the provided arguments into the Token's existing arguments.
If duplicate keys are encountered, the new value is silently dropped without causing an error. Since duplicate keys can only be encountered due to previous calls to WithArgument or WithArguments, calling only this function to set the Token's arguments is equivalent to assigning the arguments to the Token.
func WithAudience ¶
WithAudience sets the Token's audience to the provided did.DID.
This can be used if the resource on which the token operates on is different from the subject. In that situation, the subject is akin to the "service" and the audience is akin to the resource.
If the provided did.DID is the same as the Token's subject, the audience is not set.
func WithCause ¶
func WithCause(cause *cid.Cid) Option
WithCause sets the Token's cause field to the provided cid.Cid.
func WithEmptyNonce ¶
func WithEmptyNonce() Option
WithEmptyNonce sets the Token's nonce to an empty byte slice as suggested by the UCAN spec for invocation tokens that represent idempotent operations.
func WithEncryptedMetaBytes ¶
WithEncryptedMetaBytes adds a key/value pair in the "meta" field. The []byte value is encrypted with the given aesKey.
func WithEncryptedMetaString ¶
WithEncryptedMetaString adds a key/value pair in the "meta" field. The string value is encrypted with the given aesKey.
func WithExpiration ¶
WithExpiration set's the Token's optional "expiration" field to the value of the provided time.Time.
func WithExpirationIn ¶
WithExpirationIn set's the Token's optional "expiration" field to Now() plus the given duration.
func WithInvokedAt ¶
WithInvokedAt sets the Token's invokedAt field to the provided time.Time.
If this Option is not provided, the invocation Token's iat field will be set to the value of time.Now(). If you want to create an invocation Token without this field being set, use the WithoutInvokedAt Option.
func WithInvokedAtIn ¶
WithInvokedAtIn sets the Token's invokedAt field to Now() plus the given duration.
func WithMeta ¶
WithMeta adds a key/value pair in the "meta" field.
WithMeta can be used multiple times in the same call. Accepted types for the value are: bool, string, int, int32, int64, []byte, and ipld.Node.
func WithNonce ¶
WithNonce sets the Token's nonce with the given value.
If this option is not used, a random 12-byte nonce is generated for this required field. If you truly want to create an invocation Token without a nonce, use the WithEmptyNonce Option which will set the nonce to an empty byte array.
func WithoutInvokedAt ¶
func WithoutInvokedAt() Option
WithoutInvokedAt clears the Token's invokedAt field.
type Token ¶
type Token struct {
// contains filtered or unexported fields
}
Token is an immutable type that holds the fields of a UCAN invocation.
func Decode ¶
Decode unmarshals the input data using the format specified by the provided codec.Decoder into a Token.
An error is returned if the conversion fails, or if the resulting Token is invalid.
func DecodeReader ¶
DecodeReader is the same as Decode, but accept an io.Reader.
func FromDagCbor ¶
FromDagCbor unmarshals the input data into a Token.
An error is returned if the conversion fails, or if the resulting Token is invalid.
func FromDagCborReader ¶
FromDagCborReader is the same as FromDagCbor, but accept an io.Reader.
func FromDagJson ¶
FromDagJson unmarshals the input data into a Token.
An error is returned if the conversion fails, or if the resulting Token is invalid.
func FromDagJsonReader ¶
FromDagJsonReader is the same as FromDagJson, but accept an io.Reader.
func FromSealed ¶
FromSealed decodes the provided binary data from the DAG-CBOR format, verifies that the envelope's signature is correct based on the public key taken from the issuer (iss) field and calculates the CID of the incoming data.
func FromSealedReader ¶
FromSealedReader is the same as Unseal but accepts an io.Reader.
func New ¶
func New(iss did.DID, cmd command.Command, sub did.DID, prf []cid.Cid, opts ...Option) (*Token, error)
New creates an invocation Token with the provided options.
If no nonce is provided, a random 12-byte nonce is generated. Use the WithNonce or WithEmptyNonce options to specify provide your own nonce or to leave the nonce empty respectively.
If no invokedAt is provided, the current time is used. Use the WithInvokedAt or WithInvokedAtIn Options to specify a different time or the WithoutInvokedAt Option to clear the Token's invokedAt field.
With the exception of the WithMeta option, all others will overwrite the previous contents of their target field.
You can read it as "(Issuer - I) executes (command) on (subject)".
Example ¶
package main import ( "bytes" "encoding/json" "errors" "fmt" "time" "github.com/ipfs/go-cid" "github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime/codec/dagcbor" "github.com/ipld/go-ipld-prime/codec/dagjson" "github.com/ipld/go-ipld-prime/node/basicnode" "github.com/libp2p/go-libp2p/core/crypto" "github.com/ucan-wg/go-ucan/did" "github.com/ucan-wg/go-ucan/pkg/command" "github.com/ucan-wg/go-ucan/token/invocation" ) func main() { privKey, iss, sub, cmd, args, prf, meta, err := setupExampleNew() if err != nil { fmt.Println("failed to create setup:", err.Error()) return } inv, err := invocation.New(iss, cmd, sub, prf, invocation.WithArgument("uri", args["uri"]), invocation.WithArgument("headers", args["headers"]), invocation.WithArgument("payload", args["payload"]), invocation.WithMeta("env", "development"), invocation.WithMeta("tags", meta["tags"]), invocation.WithExpirationIn(time.Minute), invocation.WithoutInvokedAt()) if err != nil { fmt.Println("failed to create invocation:", err.Error()) return } data, cid, err := inv.ToSealed(privKey) if err != nil { fmt.Println("failed to seal invocation:", err.Error()) return } json, err := prettyDAGJSON(data) if err != nil { fmt.Println("failed to pretty DAG-JSON:", err.Error()) return } fmt.Println("CID:", cid) fmt.Println("Token (pretty DAG-JSON):") fmt.Println(json) // Expected CID and DAG-JSON output: // CID: bafyreid2n5q45vk4osned7k5huocbe3mxbisonh5vujepqftc5ftr543ae // Token (pretty DAG-JSON): // [ // { // "/": { // "bytes": "gvyL7kdSkgmaDpDU/Qj9ohRwxYLCHER52HFMSFEqQqEcQC9qr4JCPP1f/WybvGGuVzYiA0Hx4JO+ohNz8BxUAA" // } // }, // { // "h": { // "/": { // "bytes": "NO0BcQ" // } // }, // "ucan/inv@1.0.0-rc.1": { // "args": { // "headers": { // "Content-Type": "application/json" // }, // "payload": { // "body": "UCAN is great", // "draft": true, // "title": "UCAN for Fun and Profit", // "topics": [ // "authz", // "journal" // ] // }, // "uri": "https://example.com/blog/posts" // }, // "cmd": "/crud/create", // "exp": 1729788921, // "iss": "did:key:z6MkhniGGyP88eZrq2dpMvUPdS2RQMhTUAWzcu6kVGUvEtCJ", // "meta": { // "env": "development", // "tags": [ // "blog", // "post", // "pr#123" // ] // }, // "nonce": { // "/": { // "bytes": "2xXPoZwWln1TfXIp" // } // }, // "prf": [ // { // "/": "bafyreigx3qxd2cndpe66j2mdssj773ecv7tqd7wovcnz5raguw6lj7sjoe" // }, // { // "/": "bafyreib34ira254zdqgehz6f2bhwme2ja2re3ltcalejv4x4tkcveujvpa" // }, // { // "/": "bafyreibkb66tpo2ixqx3fe5hmekkbuasrod6olt5bwm5u5pi726mduuwlq" // } // ], // "sub": "did:key:z6MktWuvPvBe5UyHnDGuEdw8aJ5qrhhwLG6jy7cQYM6ckP6P" // } // } // ] } func prettyDAGJSON(data []byte) (string, error) { var node ipld.Node node, err := ipld.Decode(data, dagcbor.Decode) if err != nil { return "", err } jsonData, err := ipld.Encode(node, dagjson.Encode) if err != nil { return "", err } var out bytes.Buffer if err := json.Indent(&out, jsonData, "", " "); err != nil { return "", err } return out.String(), nil } func setupExampleNew() (privKey crypto.PrivKey, iss, sub did.DID, cmd command.Command, args map[string]any, prf []cid.Cid, meta map[string]any, errs error) { var err error privKey, iss, err = did.GenerateEd25519() if err != nil { errs = errors.Join(errs, fmt.Errorf("failed to generate Issuer identity: %w", err)) } _, sub, err = did.GenerateEd25519() if err != nil { errs = errors.Join(errs, fmt.Errorf("failed to generate Subject identity: %w", err)) } cmd, err = command.Parse("/crud/create") if err != nil { errs = errors.Join(errs, fmt.Errorf("failed to parse command: %w", err)) } headers := map[string]string{ "Content-Type": "application/json", } payload := map[string]any{ "body": "UCAN is great", "draft": true, "title": "UCAN for Fun and Profit", "topics": []string{"authz", "journal"}, } args = map[string]any{ // you can also directly pass IPLD values "uri": basicnode.NewString("https://example.com/blog/posts"), "headers": headers, "payload": payload, } prf = make([]cid.Cid, 3) for i, v := range []string{ "zdpuAzx4sBrBCabrZZqXgvK3NDzh7Mf5mKbG11aBkkMCdLtCp", "zdpuApTCXfoKh2sB1KaUaVSGofCBNPUnXoBb6WiCeitXEibZy", "zdpuAoFdXRPw4n6TLcncoDhq1Mr6FGbpjAiEtqSBrTSaYMKkf", } { prf[i], err = cid.Parse(v) if err != nil { errs = errors.Join(errs, fmt.Errorf("failed to parse proof cid: %w", err)) } } meta = map[string]any{ "env": basicnode.NewString("development"), "tags": []string{"blog", "post", "pr#123"}, } return // WARNING: named return values }
Output:
func (*Token) Cause ¶
func (t *Token) Cause() *cid.Cid
Cause returns the Token's (optional) cause field which may specify which describes the Receipt that requested the invocation.
func (*Token) Encode ¶
Encode marshals a Token to the format specified by the provided codec.Encoder.
func (*Token) EncodeWriter ¶
EncodeWriter is the same as Encode, but accepts an io.Writer.
func (*Token) ExecutionAllowed ¶
func (t *Token) ExecutionAllowed(loader delegation.Loader) error
func (*Token) ExecutionAllowedWithArgsHook ¶
func (*Token) Expiration ¶
Expiration returns the time at which the Token expires.
func (*Token) InvokedAt ¶
InvokedAt returns the time.Time at which the invocation token was created.
func (*Token) IsValidAt ¶
IsValidNow verifies that the token can be used at the given time, based on expiration or "not before" fields. This does NOT do any other kind of verifications.
func (*Token) IsValidNow ¶
IsValidNow verifies that the token can be used at the current time, based on expiration or "not before" fields. This does NOT do any other kind of verifications.
func (*Token) Proof ¶
func (t *Token) Proof() []cid.Cid
Proof() returns the ordered list of cid.Cid which reference the delegation Tokens that authorize this invocation. Ordering is from the leaf Delegation (with aud matching the invocation's iss) to the root delegation.
func (*Token) ToDagCborWriter ¶
ToDagCborWriter is the same as ToDagCbor, but it accepts an io.Writer.
func (*Token) ToDagJsonWriter ¶
ToDagJsonWriter is the same as ToDagJson, but it accepts an io.Writer.