Skip to content

Commit

Permalink
feat: validate caveat payloads (#11)
Browse files Browse the repository at this point in the history
This allows caveats (nb) to be used in server handler functions.

It adds a minimal validator framework that parses caveats to a Go type.

You can now define a capability with a Go type for caveats (and an IPLD
schema). This allows incoming capability invocation payloads to be
parsed to check for validity.

---------

Co-authored-by: hannahhoward <[email protected]>
Co-authored-by: Steve Moyer <[email protected]>
  • Loading branch information
3 people authored Sep 18, 2024
1 parent 6cf686f commit d098f6c
Show file tree
Hide file tree
Showing 96 changed files with 5,885 additions and 1,137 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.out
160 changes: 119 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,18 @@ Ucanto UCAN RPC in Golang.
## Install

```console
go get github.com/storacha-network/go-ucanto
go get github.com/storacha/go-ucanto
```

## Usage

### Client

```go
package main

import (
"net/url"
"ioutil"

"github.com/storacha-network/go-ucanto/client"
"github.com/storacha-network/go-ucanto/did"
ed25519 "github.com/storacha-network/go-ucanto/principal/ed25519/signer"
"github.com/storacha-network/go-ucanto/transport/car"
"github.com/storacha-network/go-ucanto/transport/http"
"github.com/storacha-network/go-ucanto/core/delegation"
"github.com/storacha-network/go-ucanto/core/invocation"
"github.com/storacha-network/go-ucanto/core/receipt"
"..."
)

// service URL & DID
Expand All @@ -33,9 +25,7 @@ servicePrincipal, _ := did.Parse("did:web:web3.storage")

// HTTP transport and CAR encoding
channel := http.NewHTTPChannel(serviceURL)
codec := car.NewCAROutboundCodec()

conn, _ := client.NewConnection(servicePrincipal, codec, channel)
conn, _ := client.NewConnection(servicePrincipal, channel)

// private key to sign UCANs with
priv, _ := ioutil.ReadFile("path/to/private.key")
Expand All @@ -44,21 +34,30 @@ signer, _ := ed25519.Parse(priv)
audience := servicePrincipal

type StoreAddCaveats struct {
Link ipld.Link
Size uint64
Link ipld.Link
Size int
}

func (c StoreAddCaveats) ToIPLD() (datamodel.Node, error) {
return ipld.WrapWithRecovery(&c, StoreAddType())
}

func (c *StoreAddCaveats) Build() (map[string]datamodel.Node, error) {
n := bindnode.Wrap(c, typ)
return n.Representation(), nil
func StoreAddType() ipldschema.Type {
ts, _ := ipldprime.LoadSchemaBytes([]byte(`
type StoreAdd struct {
link Link
size Int
}
`))
return ts.TypeByName("StoreAdd")
}

capability := ucan.NewCapability(
"store/add",
did.Parse("did:key:z6MkwDuRThQcyWjqNsK54yKAmzfsiH6BTkASyiucThMtHt1T").String(),
&StoreAddCaveats{
// TODO
},
"store/add",
did.Parse("did:key:z6MkwDuRThQcyWjqNsK54yKAmzfsiH6BTkASyiucThMtHt1T").String(),
StoreAddCaveats{
// TODO
},
)

// create invocation(s) to perform a task with granted capabilities
Expand All @@ -73,24 +72,24 @@ type OkModel struct {
Status string
}
type ErrModel struct {
Message string
Message string
}

// create new receipt reader, passing the IPLD schema for the result and the
// ok and error types
reader, _ := receipt.NewReceiptReader[OkModel, ErrModel]([]byte(`
type Result union {
| Ok "ok"
| Err "error"
} representation keyed
type Ok struct {
status String
}
type Err struct {
message String
}
type Result union {
| Ok "ok"
| Err "error"
} representation keyed
type Ok struct {
status String
}
type Err struct {
message String
}
`))

// get the receipt link for the invocation from the response
Expand All @@ -101,17 +100,96 @@ rcpt, _ := reader.Read(rcptlnk, res.Blocks())
fmt.Println(rcpt.Out().Ok())
```

### Server

```go
package main

import (
"..."
)

type TestEcho struct {
Echo string
}

func (c TestEcho) ToIPLD() (ipld.Node, error) {
return ipld.WrapWithRecovery(&c, EchoType())
}

func EchoType() ipldschema.Type {
ts, _ := ipldprime.LoadSchemaBytes([]byte(`
type TestEcho struct {
echo String
}
`))
return ts.TypeByName("TestEcho")
}

func createServer(signer principal.Signer) (server.ServerView, error) {
// Capability definition(s)
testecho := validator.NewCapability(
"test/echo",
schema.DIDString(),
schema.Struct[TestEcho](EchoType(), nil),
validator.DefaultDerives,
)

return server.NewServer(
signer,
// Handler definitions
server.WithServiceMethod(
testecho.Can(),
server.Provide(
testecho,
func(cap ucan.Capability[TestEcho], inv invocation.Invocation, ctx server.InvocationContext) (TestEcho, receipt.Effects, error) {
return TestEcho{Echo: cap.Nb().Echo}, nil, nil
},
),
),
)
}

func main() {
signer, _ := ed25519.Generate()
server, _ := createServer(signer)

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
res, _ := server.Request(uhttp.NewHTTPRequest(r.Body, r.Header))

for key, vals := range res.Headers() {
for _, v := range vals {
w.Header().Add(key, v)
}
}

if res.Status() != 0 {
w.WriteHeader(res.Status())
}

io.Copy(w, res.Body())
})

listener, _ := net.Listen("tcp", ":0")

port := listener.Addr().(*net.TCPAddr).Port
fmt.Printf("{\"id\":\"%s\",\"url\":\"http://127.0.0.1:%d\"}\n", signer.DID().String(), port)

http.Serve(listener, nil)
}
```

## API

[pkg.go.dev Reference](https://pkg.go.dev/github.com/storacha-network/go-ucanto)
[pkg.go.dev Reference](https://pkg.go.dev/github.com/storacha/go-ucanto)

## Related

* [Ucanto in Javascript](https://github.com/storacha-network/ucanto)
* [Ucanto in Javascript](https://github.com/storacha/ucanto)

## Contributing

Feel free to join in. All welcome. Please [open an issue](https://github.com/storacha-network/go-ucanto/issues)!
Feel free to join in. All welcome. Please [open an issue](https://github.com/storacha/go-ucanto/issues)!

## License

Expand Down
18 changes: 9 additions & 9 deletions client/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import (
"crypto/sha256"
"fmt"
"hash"

"github.com/storacha-network/go-ucanto/core/invocation"
"github.com/storacha-network/go-ucanto/core/ipld/block"
"github.com/storacha-network/go-ucanto/core/iterable"
"github.com/storacha-network/go-ucanto/core/message"
"github.com/storacha-network/go-ucanto/transport"
"github.com/storacha-network/go-ucanto/transport/car"
"github.com/storacha-network/go-ucanto/ucan"
"iter"

"github.com/storacha/go-ucanto/core/invocation"
"github.com/storacha/go-ucanto/core/ipld/block"
"github.com/storacha/go-ucanto/core/message"
"github.com/storacha/go-ucanto/transport"
"github.com/storacha/go-ucanto/transport/car"
"github.com/storacha/go-ucanto/ucan"
)

type Connection interface {
Expand Down Expand Up @@ -96,7 +96,7 @@ func (c *conn) Hasher() hash.Hash {
type ExecutionResponse interface {
// Blocks returns an iterator of all the IPLD blocks that are included in
// the response.
Blocks() iterable.Iterator[block.Block]
Blocks() iter.Seq2[block.Block, error]
// Get returns a link to a receipt, given an invocation link.
Get(inv ucan.Link) (ucan.Link, bool)
}
Expand Down
Loading

0 comments on commit d098f6c

Please sign in to comment.