add an early version of a UCAN client
This commit is contained in:
committed by
Michael Muré
parent
174bf01c64
commit
4c08b22c61
78
toolkit/client/client.go
Normal file
78
toolkit/client/client.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"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/pkg/container"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/invocation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
did did.DID
|
||||||
|
privKey crypto.PrivKey
|
||||||
|
|
||||||
|
pool *Pool
|
||||||
|
requester DelegationRequester
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(privKey crypto.PrivKey, requester DelegationRequester) (*Client, error) {
|
||||||
|
d, err := did.FromPrivKey(privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{
|
||||||
|
did: d,
|
||||||
|
privKey: privKey,
|
||||||
|
pool: NewPool(),
|
||||||
|
requester: requester,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrepareInvoke returns an invocation and the proof delegation, bundled in a container.Writer.
|
||||||
|
func (c *Client) PrepareInvoke(ctx context.Context, cmd command.Command, subject did.DID, opts ...invocation.Option) (container.Writer, error) {
|
||||||
|
var proof []cid.Cid
|
||||||
|
|
||||||
|
// do we already have a valid proof?
|
||||||
|
if proof = c.pool.FindProof(cmd, c.did, subject); len(proof) == 0 {
|
||||||
|
// we need to request a new proof
|
||||||
|
proofBundles, err := c.requester.RequestDelegation(ctx, cmd, c.did, subject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("requesting delegation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache the new proofs
|
||||||
|
for bundle, err := range proofBundles {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
proof = append(proof, bundle.Cid)
|
||||||
|
c.pool.AddBundle(bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inv, err := invocation.New(c.did, subject, cmd, proof, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
invSealed, invCid, err := inv.ToSealed(c.privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cont := container.NewWriter()
|
||||||
|
cont.AddSealed(invCid, invSealed)
|
||||||
|
for bundle, err := range c.pool.GetBundles(proof) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cont.AddSealed(bundle.Cid, bundle.Sealed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cont, nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -39,6 +40,7 @@ func (p *Pool) AddBundles(bundles iter.Seq[*delegation.Bundle]) {
|
|||||||
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
||||||
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
// Note: the implemented algorithm won't perform well with a large number of delegations.
|
||||||
func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Cid {
|
||||||
|
// TODO: move to some kind of background trim job?
|
||||||
p.trim()
|
p.trim()
|
||||||
|
|
||||||
p.mu.RLock()
|
p.mu.RLock()
|
||||||
@@ -55,6 +57,24 @@ func (p *Pool) FindProof(cmd command.Command, iss did.DID, aud did.DID) []cid.Ci
|
|||||||
}, cmd, iss, aud)
|
}, cmd, iss, aud)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pool) GetBundles(cids []cid.Cid) iter.Seq2[*delegation.Bundle, error] {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
|
||||||
|
return func(yield func(*delegation.Bundle, error) bool) {
|
||||||
|
for _, c := range cids {
|
||||||
|
if b, ok := p.dlgs[c]; ok {
|
||||||
|
if !yield(b, nil) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield(nil, fmt.Errorf("bundle not found"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// trim removes expired tokens
|
// trim removes expired tokens
|
||||||
func (p *Pool) trim() {
|
func (p *Pool) trim() {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
|
|||||||
19
toolkit/client/requester.go
Normal file
19
toolkit/client/requester.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"iter"
|
||||||
|
|
||||||
|
"github.com/ucan-wg/go-ucan/did"
|
||||||
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
|
"github.com/ucan-wg/go-ucan/token/delegation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DelegationRequester interface {
|
||||||
|
// RequestDelegation retrieve a delegation or chain of delegation for the given parameters.
|
||||||
|
// - cmd: the command to execute
|
||||||
|
// - issuer: the DID of the client, also the issuer of the invocation token
|
||||||
|
// - audience: the DID of the resource to operate on, also the subject (or audience if defined) of the invocation token
|
||||||
|
// Note: the returned delegation(s) don't have to match exactly the parameters, as long as they allow them.
|
||||||
|
RequestDelegation(ctx context.Context, cmd command.Command, audience did.DID, subject did.DID) (iter.Seq2[*delegation.Bundle, error], error)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user