feat(invocation): provide New constructor and encoding to wire-format
This commit is contained in:
@@ -13,6 +13,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ucan-wg/go-ucan/did"
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/meta"
|
"github.com/ucan-wg/go-ucan/pkg/meta"
|
||||||
@@ -20,25 +22,70 @@ import (
|
|||||||
|
|
||||||
// Token is an immutable type that holds the fields of a UCAN invocation.
|
// Token is an immutable type that holds the fields of a UCAN invocation.
|
||||||
type Token struct {
|
type Token struct {
|
||||||
// Issuer DID (invoker)
|
// The DID of the Invoker
|
||||||
issuer did.DID
|
issuer did.DID
|
||||||
// Audience DID (receiver/executor)
|
// The DID of Subject being invoked
|
||||||
audience did.DID
|
|
||||||
// Subject DID (subject being invoked)
|
|
||||||
subject did.DID
|
subject did.DID
|
||||||
// The Command to invoke
|
// The DID of the intended Executor if different from the Subject
|
||||||
|
audience did.DID
|
||||||
|
|
||||||
|
// The Command
|
||||||
command command.Command
|
command command.Command
|
||||||
// TODO: args
|
// The Command's Arguments
|
||||||
// TODO: prf
|
arguments map[string]datamodel.Node
|
||||||
// A unique, random nonce
|
// Delegations that prove the chain of authority
|
||||||
nonce []byte
|
proof []cid.Cid
|
||||||
|
|
||||||
// Arbitrary Metadata
|
// Arbitrary Metadata
|
||||||
meta *meta.Meta
|
meta *meta.Meta
|
||||||
|
|
||||||
|
// A unique, random nonce
|
||||||
|
nonce []byte
|
||||||
// The timestamp at which the Invocation becomes invalid
|
// The timestamp at which the Invocation becomes invalid
|
||||||
expiration *time.Time
|
expiration *time.Time
|
||||||
// The timestamp at which the Invocation was created
|
// The timestamp at which the Invocation was created
|
||||||
invokedAt *time.Time
|
invokedAt *time.Time
|
||||||
// TODO: cause
|
|
||||||
|
// An optional CID of the Receipt that enqueued the Task
|
||||||
|
cause *cid.Cid
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// With the exception of the WithMeta option, all other will overwrite
|
||||||
|
// the previous contents of their target field.
|
||||||
|
func New(iss, sub did.DID, cmd command.Command, prf []cid.Cid, opts ...Option) (*Token, error) {
|
||||||
|
nonce := make([]byte, 12)
|
||||||
|
|
||||||
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iat := time.Now()
|
||||||
|
|
||||||
|
tkn := Token{
|
||||||
|
issuer: iss,
|
||||||
|
subject: sub,
|
||||||
|
command: cmd,
|
||||||
|
proof: prf,
|
||||||
|
nonce: nonce,
|
||||||
|
invokedAt: &iat,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&tkn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tkn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issuer returns the did.DID representing the Token's issuer.
|
// Issuer returns the did.DID representing the Token's issuer.
|
||||||
@@ -46,28 +93,27 @@ func (t *Token) Issuer() did.DID {
|
|||||||
return t.issuer
|
return t.issuer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Subject returns the did.DID representing the Token's subject.
|
||||||
|
func (t *Token) Subject() did.DID {
|
||||||
|
return t.subject
|
||||||
|
}
|
||||||
|
|
||||||
// Audience returns the did.DID representing the Token's audience.
|
// Audience returns the did.DID representing the Token's audience.
|
||||||
func (t *Token) Audience() did.DID {
|
func (t *Token) Audience() did.DID {
|
||||||
return t.audience
|
return t.audience
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subject returns the did.DID representing the Token's subject.
|
|
||||||
//
|
|
||||||
// This field may be did.Undef for delegations that are [Powerlined] but
|
|
||||||
// must be equal to the value returned by the Issuer method for root
|
|
||||||
// tokens.
|
|
||||||
func (t *Token) Subject() did.DID {
|
|
||||||
return t.subject
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command returns the capability's command.Command.
|
// Command returns the capability's command.Command.
|
||||||
func (t *Token) Command() command.Command {
|
func (t *Token) Command() command.Command {
|
||||||
return t.command
|
return t.command
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nonce returns the random Nonce encapsulated in this Token.
|
func (t *Token) Arguments() map[string]datamodel.Node {
|
||||||
func (t *Token) Nonce() []byte {
|
return t.arguments
|
||||||
return t.nonce
|
}
|
||||||
|
|
||||||
|
func (t *Token) Proof() []cid.Cid {
|
||||||
|
return t.proof
|
||||||
}
|
}
|
||||||
|
|
||||||
// Meta returns the Token's metadata.
|
// Meta returns the Token's metadata.
|
||||||
@@ -75,11 +121,24 @@ func (t *Token) Meta() meta.ReadOnly {
|
|||||||
return t.meta.ReadOnly()
|
return t.meta.ReadOnly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nonce returns the random Nonce encapsulated in this Token.
|
||||||
|
func (t *Token) Nonce() []byte {
|
||||||
|
return t.nonce
|
||||||
|
}
|
||||||
|
|
||||||
// Expiration returns the time at which the Token expires.
|
// Expiration returns the time at which the Token expires.
|
||||||
func (t *Token) Expiration() *time.Time {
|
func (t *Token) Expiration() *time.Time {
|
||||||
return t.expiration
|
return t.expiration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Token) InvokedAt() *time.Time {
|
||||||
|
return t.invokedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) Cause() *cid.Cid {
|
||||||
|
return t.cause
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Token) validate() error {
|
func (t *Token) validate() error {
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,32 @@
|
|||||||
|
|
||||||
type DID string
|
type DID string
|
||||||
|
|
||||||
# The Invocation Payload attaches sender, receiver, and provenance to the Task.
|
# The Invocation Payload attaches sender, receiver, and provenance to the Task.
|
||||||
type Payload struct {
|
type Payload struct {
|
||||||
# Issuer DID (sender)
|
# The DID of the invoker
|
||||||
iss DID
|
iss DID
|
||||||
# Audience DID (receiver)
|
# The Subject being invoked
|
||||||
aud DID
|
sub DID
|
||||||
# Principal that the chain is about (the Subject)
|
# The DID of the intended Executor if different from the Subject
|
||||||
sub optional DID
|
aud optional DID
|
||||||
|
|
||||||
# The Command to eventually invoke
|
# The Command
|
||||||
cmd String
|
cmd String
|
||||||
|
# The Command's Arguments
|
||||||
# A unique, random nonce
|
args { String : Any}
|
||||||
nonce Bytes
|
# Delegations that prove the chain of authority
|
||||||
|
prf [ Link ]
|
||||||
|
|
||||||
# Arbitrary Metadata
|
# Arbitrary Metadata
|
||||||
meta {String : Any}
|
meta optional { String : Any }
|
||||||
|
|
||||||
|
# A unique, random nonce
|
||||||
|
nonce optional Bytes
|
||||||
# The timestamp at which the Invocation becomes invalid
|
# The timestamp at which the Invocation becomes invalid
|
||||||
exp nullable Int
|
exp nullable Int
|
||||||
|
# The Timestamp at which the Invocation was created
|
||||||
|
iat optional Int
|
||||||
|
|
||||||
|
# An optional CID of the Receipt that enqueued the Task
|
||||||
|
cause optional Link
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,29 +193,58 @@ func FromIPLD(node datamodel.Node) (*Token, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
func (t *Token) toIPLD(privKey crypto.PrivKey) (datamodel.Node, error) {
|
||||||
var sub *string
|
var aud *string
|
||||||
|
|
||||||
if t.subject != did.Undef {
|
if t.audience != did.Undef {
|
||||||
s := t.subject.String()
|
a := t.audience.String()
|
||||||
sub = &s
|
aud = &a
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
var exp *int64
|
var exp *int64
|
||||||
if t.expiration != nil {
|
if t.expiration != nil {
|
||||||
u := t.expiration.Unix()
|
u := t.expiration.Unix()
|
||||||
exp = &u
|
exp = &u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var iat *int64
|
||||||
|
if t.invokedAt != nil {
|
||||||
|
i := t.invokedAt.Unix()
|
||||||
|
iat = &i
|
||||||
|
}
|
||||||
|
|
||||||
|
argsKey := make([]string, len(t.arguments))
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for k := range t.arguments {
|
||||||
|
argsKey[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
args := struct {
|
||||||
|
Keys []string
|
||||||
|
Values map[string]datamodel.Node
|
||||||
|
}{
|
||||||
|
Keys: argsKey,
|
||||||
|
Values: t.arguments,
|
||||||
|
}
|
||||||
|
|
||||||
|
prf := make([]cid.Cid, len(t.proof))
|
||||||
|
for i, c := range t.proof {
|
||||||
|
prf[i] = c
|
||||||
|
}
|
||||||
|
|
||||||
model := &tokenPayloadModel{
|
model := &tokenPayloadModel{
|
||||||
Iss: t.issuer.String(),
|
Iss: t.issuer.String(),
|
||||||
Aud: t.audience.String(),
|
Aud: aud,
|
||||||
Sub: sub,
|
Sub: t.subject.String(),
|
||||||
Cmd: t.command.String(),
|
Cmd: t.command.String(),
|
||||||
|
Args: args,
|
||||||
|
Prf: prf,
|
||||||
|
Meta: t.meta,
|
||||||
Nonce: t.nonce,
|
Nonce: t.nonce,
|
||||||
Meta: *t.meta,
|
|
||||||
Exp: exp,
|
Exp: exp,
|
||||||
|
Iat: iat,
|
||||||
|
Cause: t.cause,
|
||||||
}
|
}
|
||||||
|
|
||||||
return envelope.ToIPLD(privKey, model)
|
return envelope.ToIPLD(privKey, model)
|
||||||
|
|||||||
134
token/invocation/options.go
Normal file
134
token/invocation/options.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package invocation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option is a type that allows optional fields to be set during the
|
||||||
|
// creation of a Token.
|
||||||
|
type Option func(*Token) error
|
||||||
|
|
||||||
|
// WithArgument adds a key/value pair to the Token's Arguments field.
|
||||||
|
func WithArgument(key string, val datamodel.Node) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.arguments[key] = val
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithArguments sets the Token's Arguments field to the provided map.
|
||||||
|
//
|
||||||
|
// Note that this will overwrite any existing Arguments whether provided
|
||||||
|
// by a previous call to this function or by one or more calls to
|
||||||
|
// WithArgument.
|
||||||
|
func WithArguments(args map[string]datamodel.Node) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.arguments = args
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAudience sets the Token's audience to the provided did.DID.
|
||||||
|
//
|
||||||
|
// If the provided did.DID is the same as the Token's subject, the
|
||||||
|
// audience is not set.
|
||||||
|
func WithAudience(aud did.DID) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
if t.subject.String() != aud.String() {
|
||||||
|
t.audience = aud
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 WithMeta(key string, val any) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
return t.meta.Add(key, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 require.
|
||||||
|
func WithNonce(nonce []byte) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.nonce = nonce
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEmptyNonce sets the Token's nonce to an empty byte slice as
|
||||||
|
// suggested by the UCAN spec for invocation tokens that represent
|
||||||
|
// idem
|
||||||
|
func WithEmptyNonce() Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.nonce = []byte{}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExpiration set's the Token's optional "expiration" field to the
|
||||||
|
// value of the provided time.Time.
|
||||||
|
func WithExpiration(exp time.Time) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.expiration = &exp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExpirationIn set's the Token's optional "expiration" field to
|
||||||
|
// Now() plus the given duration.
|
||||||
|
func WithExpirationIn(exp time.Duration) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
expTime := time.Now().Add(exp)
|
||||||
|
t.expiration = &expTime
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInvokedAt sets the Token's invokedAt field to the provided
|
||||||
|
// time.Time.
|
||||||
|
func WithInvokedAt(iat time.Time) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.invokedAt = &iat
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithInvokedAtIn sets the Token's invokedAt field to Now() plus the
|
||||||
|
// given duration.
|
||||||
|
func WithInvokedAtIn(after time.Duration) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
iat := time.Now().Add(after)
|
||||||
|
t.invokedAt = &iat
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCause sets the Token's cause field to the provided cid.Cid.
|
||||||
|
func WithCause(cause *cid.Cid) Option {
|
||||||
|
return func(t *Token) error {
|
||||||
|
t.cause = cause
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipld/go-ipld-prime"
|
"github.com/ipld/go-ipld-prime"
|
||||||
|
"github.com/ipld/go-ipld-prime/datamodel"
|
||||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||||
"github.com/ipld/go-ipld-prime/schema"
|
"github.com/ipld/go-ipld-prime/schema"
|
||||||
|
|
||||||
@@ -46,26 +48,36 @@ var _ envelope.Tokener = (*tokenPayloadModel)(nil)
|
|||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
type tokenPayloadModel struct {
|
type tokenPayloadModel struct {
|
||||||
// Issuer DID (sender)
|
// The DID of the Invoker
|
||||||
Iss string
|
Iss string
|
||||||
// Audience DID (receiver)
|
// The DID of Subject being invoked
|
||||||
Aud string
|
Sub string
|
||||||
// Principal that the chain is about (the Subject)
|
// The DID of the intended Executor if different from the Subject
|
||||||
// optional: can be nil
|
Aud *string
|
||||||
Sub *string
|
|
||||||
|
|
||||||
// The Command to eventually invoke
|
// The Command
|
||||||
Cmd string
|
Cmd string
|
||||||
|
// The Command's Arguments
|
||||||
|
Args struct {
|
||||||
|
Keys []string
|
||||||
|
Values map[string]datamodel.Node
|
||||||
|
}
|
||||||
|
// Delegations that prove the chain of authority
|
||||||
|
Prf []cid.Cid
|
||||||
|
|
||||||
|
// Arbitrary Metadata
|
||||||
|
Meta *meta.Meta
|
||||||
|
|
||||||
// A unique, random nonce
|
// A unique, random nonce
|
||||||
Nonce []byte
|
Nonce []byte
|
||||||
|
|
||||||
// Arbitrary Metadata
|
|
||||||
Meta meta.Meta
|
|
||||||
|
|
||||||
// The timestamp at which the Invocation becomes invalid
|
// The timestamp at which the Invocation becomes invalid
|
||||||
// optional: can be nil
|
// optional: can be nil
|
||||||
Exp *int64
|
Exp *int64
|
||||||
|
// The timestamp at which the Invocation was created
|
||||||
|
Iat *int64
|
||||||
|
|
||||||
|
// An optional CID of the Receipt that enqueued the Task
|
||||||
|
Cause *cid.Cid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *tokenPayloadModel) Prototype() schema.TypedPrototype {
|
func (e *tokenPayloadModel) Prototype() schema.TypedPrototype {
|
||||||
|
|||||||
Reference in New Issue
Block a user