feat(token) combined TokenPayload model for both Delegation and Invocation tokens
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||
"github.com/ucan-wg/go-ucan/capability/command"
|
||||
"github.com/ucan-wg/go-ucan/capability/policy"
|
||||
"github.com/ucan-wg/go-ucan/did"
|
||||
)
|
||||
|
||||
func BindnodeOptions() []bindnode.Option {
|
||||
return []bindnode.Option{
|
||||
CommandConverter(),
|
||||
DIDConverter(),
|
||||
MetaConverter(),
|
||||
PolicyConverter(),
|
||||
TimeConverter(),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrTypeAssertion = errors.New("failed to assert type")
|
||||
|
||||
func newErrTypeAssertion(where string) error {
|
||||
return fmt.Errorf("%w: %s", ErrTypeAssertion, where)
|
||||
}
|
||||
|
||||
func CommandConverter() bindnode.Option {
|
||||
return bindnode.TypedStringConverter(
|
||||
(*command.Command)(nil),
|
||||
func(s string) (interface{}, error) {
|
||||
return command.Parse(s)
|
||||
},
|
||||
func(i interface{}) (string, error) {
|
||||
cmd, ok := i.(*command.Command)
|
||||
if !ok {
|
||||
return "", newErrTypeAssertion("CommandConverter")
|
||||
}
|
||||
|
||||
return cmd.String(), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func DIDConverter() bindnode.Option {
|
||||
return bindnode.TypedStringConverter(
|
||||
(*did.DID)(nil),
|
||||
func(s string) (interface{}, error) {
|
||||
return did.Parse(s)
|
||||
},
|
||||
func(i interface{}) (string, error) {
|
||||
return i.(*did.DID).String(), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Keys []string
|
||||
Values map[string]any
|
||||
}
|
||||
|
||||
func MetaConverter() bindnode.Option {
|
||||
return bindnode.TypedAnyConverter(
|
||||
(map[string]any)(nil),
|
||||
func(n datamodel.Node) (interface{}, error) {
|
||||
return Meta{}, nil // TODO
|
||||
},
|
||||
func(i interface{}) (datamodel.Node, error) {
|
||||
if i == nil {
|
||||
return datamodel.Null, nil
|
||||
}
|
||||
|
||||
meta, ok := i.(Meta)
|
||||
if !ok {
|
||||
return nil, newErrTypeAssertion("MetaConverter")
|
||||
}
|
||||
|
||||
_ = meta
|
||||
|
||||
return datamodel.Null, nil // TODO
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func PolicyConverter() bindnode.Option {
|
||||
return bindnode.TypedAnyConverter(
|
||||
(*policy.Policy)(nil),
|
||||
func(n datamodel.Node) (interface{}, error) {
|
||||
return policy.FromIPLD(n)
|
||||
},
|
||||
func(i interface{}) (datamodel.Node, error) {
|
||||
if i == nil {
|
||||
return datamodel.Null, nil
|
||||
}
|
||||
|
||||
pol, ok := i.(*policy.Policy)
|
||||
if !ok {
|
||||
return nil, newErrTypeAssertion("PolicyConverter")
|
||||
}
|
||||
|
||||
return pol.ToIPLD()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TimeConverter() bindnode.Option {
|
||||
return bindnode.TypedIntConverter(
|
||||
(*time.Time)(nil),
|
||||
func(i int64) (interface{}, error) {
|
||||
return time.Unix(i, 0), nil
|
||||
},
|
||||
func(i interface{}) (int64, error) {
|
||||
if i == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
t, ok := i.(*time.Time)
|
||||
if !ok {
|
||||
return 0, newErrTypeAssertion("TimeConverter")
|
||||
}
|
||||
|
||||
return t.Unix(), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package token_test
|
||||
9
internal/token/errors.go
Normal file
9
internal/token/errors.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package token
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrFailedSchemaLoad = errors.New("failed to load IPLD Schema")
|
||||
|
||||
var ErrNoSchemaType = errors.New("schema does not contain type")
|
||||
|
||||
var ErrNodeNotToken = errors.New("IPLD node is not a Token")
|
||||
46
internal/token/schema.go
Normal file
46
internal/token/schema.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||
"github.com/ipld/go-ipld-prime/schema"
|
||||
)
|
||||
|
||||
const tokenTypeName = "Token"
|
||||
|
||||
//go:embed token.ipldsch
|
||||
var schemaBytes []byte
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
ts *schema.TypeSystem
|
||||
err error
|
||||
)
|
||||
|
||||
func mustLoadSchema() *schema.TypeSystem {
|
||||
once.Do(func() {
|
||||
ts, err = ipld.LoadSchemaBytes(schemaBytes)
|
||||
})
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("%w: %w", ErrFailedSchemaLoad, err))
|
||||
}
|
||||
|
||||
tknType := ts.TypeByName(tokenTypeName)
|
||||
if tknType == nil {
|
||||
panic(fmt.Errorf("%w: %s", ErrNoSchemaType, tokenTypeName))
|
||||
}
|
||||
|
||||
return ts
|
||||
}
|
||||
|
||||
func tokenType() schema.Type {
|
||||
return mustLoadSchema().TypeByName(tokenTypeName)
|
||||
}
|
||||
|
||||
func Prototype() schema.TypedPrototype {
|
||||
return bindnode.Prototype((*Token)(nil), tokenType())
|
||||
}
|
||||
83
internal/token/schema_test.go
Normal file
83
internal/token/schema_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package token_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagcbor"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/internal/token"
|
||||
)
|
||||
|
||||
//go:embed token.ipldsch
|
||||
var schemaBytes []byte
|
||||
|
||||
func TestSchemaRoundTrip(t *testing.T) {
|
||||
const delegationJson = `
|
||||
{
|
||||
"aud":"did:key:def456",
|
||||
"cmd":"/foo/bar",
|
||||
"exp":123456,
|
||||
"iss":"did:key:abc123",
|
||||
"meta":{
|
||||
"bar":"baaar",
|
||||
"foo":"fooo"
|
||||
},
|
||||
"nbf":123456,
|
||||
"nonce":{
|
||||
"/":{
|
||||
"bytes":"c3VwZXItcmFuZG9t"
|
||||
}
|
||||
},
|
||||
"pol":[
|
||||
["==", ".status", "draft"],
|
||||
["all", ".reviewer", [
|
||||
["like", ".email", "*@example.com"]]
|
||||
],
|
||||
["any", ".tags", [
|
||||
["or", [
|
||||
["==", ".", "news"],
|
||||
["==", ".", "press"]]
|
||||
]]
|
||||
]
|
||||
],
|
||||
"sub":""
|
||||
}
|
||||
`
|
||||
// format: dagJson --> IPLD node --> token --> dagCbor --> IPLD node --> dagJson
|
||||
// function: Unwrap() Wrap()
|
||||
|
||||
n1, err := ipld.DecodeUsingPrototype([]byte(delegationJson), dagjson.Decode, token.Prototype())
|
||||
require.NoError(t, err)
|
||||
|
||||
cborBytes, err := ipld.Encode(n1, dagcbor.Encode)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("cborBytes length", len(cborBytes))
|
||||
fmt.Println("cbor", string(cborBytes))
|
||||
|
||||
n2, err := ipld.DecodeUsingPrototype(cborBytes, dagcbor.Decode, token.Prototype())
|
||||
require.NoError(t, err)
|
||||
fmt.Println("read Cbor", n2)
|
||||
|
||||
t1, err := token.Unwrap(n2)
|
||||
require.NoError(t, err)
|
||||
|
||||
n3 := t1.Wrap()
|
||||
|
||||
readJson, err := ipld.Encode(n3, dagjson.Encode)
|
||||
require.NoError(t, err)
|
||||
fmt.Println("readJson length", len(readJson))
|
||||
fmt.Println("json: ", string(readJson))
|
||||
|
||||
require.JSONEq(t, delegationJson, string(readJson))
|
||||
}
|
||||
|
||||
func BenchmarkSchemaLoad(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = ipld.LoadSchemaBytes(schemaBytes)
|
||||
}
|
||||
}
|
||||
30
internal/token/token.go
Normal file
30
internal/token/token.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/node/bindnode"
|
||||
)
|
||||
|
||||
//go:generate go run ../cmd/token/...
|
||||
|
||||
func New() (*Token, error) {
|
||||
return &Token{}, nil
|
||||
}
|
||||
|
||||
func Unwrap(node datamodel.Node) (*Token, error) {
|
||||
iface := bindnode.Unwrap(node)
|
||||
if iface == nil {
|
||||
return nil, ErrNodeNotToken
|
||||
}
|
||||
|
||||
tkn, ok := iface.(*Token)
|
||||
if !ok {
|
||||
return nil, ErrNodeNotToken
|
||||
}
|
||||
|
||||
return tkn, nil
|
||||
}
|
||||
|
||||
func (t *Token) Wrap() datamodel.Node {
|
||||
return bindnode.Wrap(t, tokenType())
|
||||
}
|
||||
63
internal/token/token.ipldsch
Normal file
63
internal/token/token.ipldsch
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
type CID string
|
||||
|
||||
type Command string
|
||||
|
||||
type DID string
|
||||
|
||||
# Field requirements:
|
||||
#
|
||||
# | Name | Delegation | Invocation | Token |
|
||||
# | | Required | Nullable | Required | Nullable | |
|
||||
# | ----- | -------- | -------- | -------- | -------- | -------- |
|
||||
# | iss | Yes | No | Yes | No | |
|
||||
# | aud | Yes | No | No | N/A | Optional |
|
||||
# | sub | Yes | Yes | Yes | No | Nullable |
|
||||
# | cmd | Yes | No | Yes | No | |
|
||||
# | pol | Yes | No | X | X | Optional |
|
||||
# | nonce | Yes | No | No | N/A | Optional |
|
||||
# | meta | No | N/A | No | N/A | Optional |
|
||||
# | nbf | No | N/A | X | X | Optional |
|
||||
# | exp | Yes | Yes | Yes | Yes | |
|
||||
# | args | X | X | Yes | No | Optional |
|
||||
# | prf | X | X | Yes | No | Optional |
|
||||
# | iat | X | X | No | N/A | Optional |
|
||||
# | cause | X | X | No | N/A | Optional |
|
||||
|
||||
type Token struct {
|
||||
# Issuer DID (sender)
|
||||
issuer DID (rename "iss")
|
||||
# Audience DID (receiver)
|
||||
audience optional DID (rename "aud")
|
||||
# Principal that the chain is about (the Subject)
|
||||
subject nullable DID (rename "sub")
|
||||
|
||||
# The Command to eventually invoke
|
||||
command Command (rename "cmd")
|
||||
|
||||
# The delegation policy
|
||||
# It doesn't seem possible to represent it with a schema.
|
||||
policy optional Any (rename "pol")
|
||||
|
||||
# The invocation's arguments
|
||||
args optional {String: Any}
|
||||
|
||||
# Delegations that prove the chain of authority
|
||||
prf optional [CID]
|
||||
|
||||
# A unique, random nonce
|
||||
nonce optional Bytes
|
||||
|
||||
# Arbitrary Metadata
|
||||
meta optional {String : Any}
|
||||
|
||||
# "Not before" UTC Unix Timestamp in seconds (valid from), 53-bits integer
|
||||
notBefore optional Int (rename "nbf")
|
||||
# The timestamp at which the delegation becomes invalid
|
||||
expiration nullable Int (rename "exp")
|
||||
# The timestamp at which the invocation was created
|
||||
issuedAt optional Int
|
||||
|
||||
# An optional CID of the receipt that enqueued this invocation
|
||||
cause optional CID
|
||||
}
|
||||
31
internal/token/token_gen.go
Normal file
31
internal/token/token_gen.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by internal/cmd/token DO NOT EDIT.
|
||||
|
||||
package token
|
||||
|
||||
import "github.com/ipld/go-ipld-prime/datamodel"
|
||||
|
||||
type Map struct {
|
||||
Keys []string
|
||||
Values map[string]datamodel.Node
|
||||
}
|
||||
type List []datamodel.Node
|
||||
type Map__String__Any struct {
|
||||
Keys []string
|
||||
Values map[string]datamodel.Node
|
||||
}
|
||||
type List__CID []string
|
||||
type Token struct {
|
||||
Issuer string
|
||||
Audience *string
|
||||
Subject *string
|
||||
Command string
|
||||
Policy *datamodel.Node
|
||||
Args *Map__String__Any
|
||||
Prf *List__CID
|
||||
Nonce *[]uint8
|
||||
Meta *Map__String__Any
|
||||
NotBefore *int
|
||||
Expiration *int
|
||||
IssuedAt *int
|
||||
Cause *string
|
||||
}
|
||||
26
internal/token/token_test.go
Normal file
26
internal/token/token_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package token_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/codec/dagjson"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/internal/token"
|
||||
)
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tkn, err := token.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
node := tkn.Wrap()
|
||||
|
||||
json, err := ipld.Encode(node, dagjson.Encode)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log(string(json))
|
||||
|
||||
t.Fail()
|
||||
}
|
||||
Reference in New Issue
Block a user