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