Skip to content
This repository has been archived by the owner on Feb 3, 2023. It is now read-only.

Commit

Permalink
feat(sigs): Add request signing. Code cleanup for alpha. (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfl authored Mar 15, 2022
1 parent afde925 commit 83ff41f
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 178 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Standard ignores
.*
!.gitignore
!.gitattributes
output.car
Expand Down
88 changes: 86 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,91 @@ Contact us to create your core.

- `domain` is the name (CNAME) of your custom domain. Contact us to create this.

## `curl` Workflow
## API Workflow

Coming soon -- the API has changed. See history for a legacy workflow using `curl`.
Let's publish some content and then query it back through the headless API using
`curl` to see how it all works.

### Publishing

First we publish a simple piece of content. To validate that we're allowed to do
this we sign the request. For now, because we're using keys packed in a format
native to IPFS, we use the `libp2p-crypto` library to unmarshal the key.

**Note!** If you need a publishing key please [contact us](mailto:[email protected]).

**Note!** This file is available in `test/docs.js`.

```javascript
const crypto = require('libp2p-crypto')
const fetch = require('node-fetch')
async function sample() {
// We get the secret key from the environment or other secret storage.
const base64_encoded_key = process.env['MY_SECRET_PUBLISHING_KEY']
// Keys used, for example, as a GitHub Secret are base64 encoded.
const marshalled_key = Buffer.from(base64_encoded_key, 'base64')
// IPFS-generatetd keys are stored marshalled into a protocol buffer.
const secret_key = await crypto.keys.unmarshalPrivateKey(marshalled_key)
// This is the content we want to publish.
const body = {"hello": "world"}
// We prepare a fetch options payload.
const options = {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(body),
}
// Sign the body and send in a header.
const body_buffer = Buffer.from(options.body)
const sig = await secret_key.sign(body_buffer)
options.headers['x-signature'] = Buffer.from(sig).toString('base64')
// Tell us what public key you're authenticated with. We'll validate this on our end.
const public_key = secret_key.public
const marshalled_pk = await crypto.keys.marshalPublicKey(public_key)
const base64_pub_key = marshalled_pk.toString('base64')
options.headers['x-public-key'] = base64_pub_key
// Set up your core and content address. If you need a core contact [email protected].
const core = 'test'
const address = 'my_content_name'
// Set up your publishing metadata header. See "API Reference", above, for values.
const metadata = {
published: true,
as: "dag", // ALPHA: "dag" is currently the only supported value.
}
options.headers['x-metadata'] = JSON.stringify(metadata)
const result = await fetch(`https://staging.pndo.xyz/v0/api/content/${core}/${address}`, options)
console.log(await result.text())
}

sample()
```

This yields:

```
{"metadata":{"published":true,"as":"dag","box":{"cid":"bafyreidykglsfhoixmivffc5uwhcgshx4j465xwqntbmu43nb2dzqwfvae","name":"/test/my_content_name"}}}
```

Which contains the content's name, address, and some metadata about it.

### Querying

Now we can query it back simply:

```bash
export CORE=test
export NAME=my_content_name
curl https://api.pndo.xyz/v0/api/content/$CORE/$NAME -v
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "The easiest way to publish web3 content.",
"main": "index.js",
"scripts": {
"test": "node ./test/all.js",
"test": "node ./test/test.js",
"build": "ncc build -o dist"
},
"repository": {
Expand Down
67 changes: 14 additions & 53 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ const { CID } = require('multiformats/cid')
const { base36 } = require('multiformats/bases/base36')

// For HTTP comms:
const http = require('http')
const https = require('https')
const fetch = require('node-fetch-retry')
const FormData = require('multi-part')

Expand Down Expand Up @@ -198,8 +196,7 @@ async function start(secret, globspec, namespec, core, domain, published, skip,
'path': file,
'as': as,
}),
'X-Public-Key': encodedPubKey, // TODO: publishing key?
'X-Signature': encodedPubKey, // TODO: publishing key?
'X-Public-Key': encodedPubKey,
},
retry: 3, // node-fetch-retry option -- number of attempts
pause: 500, // node-fetch-retry option -- millisecond delay
Expand All @@ -217,15 +214,12 @@ async function start(secret, globspec, namespec, core, domain, published, skip,
// If we're overriding the domain, use the override (for testing).
const urlbase = endpoint || domain

////////////////////////////////////////////////////////////////////
// TODO: Needs refactor. Uses v1 endpoint for speed.
////////////////////////////////////////////////////////////////////
let v1_promise = null
let v0_promise = null
if ('dag' == as) {
const cid = carfile.roots[0].toString()
const url_v1 = new URL(humanName, new URL('/v0/api/content/' + core + '/', urlbase))
//console.log(url_v1.toString())
v1_promise = fs.open(file)
const url_v0 = new URL(humanName, new URL('/v0/api/content/' + core + '/', urlbase))
//console.log(url_v0.toString())
v0_promise = fs.open(file)
.then(async fd => {
const opts = options //JSON.parse(JSON.stringify(options))
opts.headers['content-type'] = 'application/json'
Expand All @@ -234,7 +228,14 @@ async function start(secret, globspec, namespec, core, domain, published, skip,
opts.body = await fd.readFile()//createReadStream()
fd.close()

return fetch(url_v1, opts)
//console.log('body', opts.body.length)
const raw_sig = Buffer.from(await publishingKey.sign(opts.body))
//console.log(raw_sig)
const b64_sig = raw_sig.toString('base64')
//console.log(b64_sig)
opts.headers['x-signature'] = b64_sig

return fetch(url_v0, opts)
.then(r => r.json()).then(j => {
//fd.close()
j.metadata.box.name = '/' + core + '/' + humanName
Expand All @@ -244,48 +245,8 @@ async function start(secret, globspec, namespec, core, domain, published, skip,
})
})
}
////////////////////////////////////////////////////////////////////

// Make recalls to the previous API version fire-and-forget:

/*const url = new URL(contentName, new URL('/last/api/content/kbt/', urlbase))
let v0_request = null
/*options.port = url.port
options.hostname = url.hostname
options.protocol = url.protocol
options.path = url.pathname

v0_request = 'https:' == options.protocol.toLowerCase() ? https.request(options) : http.request(options)
if ('file' != as) {
v0_request.write(await body.readFile())
body.close()
} else {
v0_request.write(await body.fd.readFile())
body.fd.close()
}
v0_request.end()
/*fetch(url, options)
.then(response => response.json())
.then(json => {
json.metadata.box.name = '/' + core + '/' + humanName
json.metadata.box.key = encodedPubKey
return json
})
.catch(e => { console.log('failed at url: ', url, e) })
.finally(() => {
})
if (v0_request && v1_promise) {
// Fire Estuary and Wasabi, but resolve when one comes back.
return Promise.any([v0_request, v1_promise])
} else {
return v0_request || v1_promise
}/**/
return v1_promise
return v0_promise
})

return Promise.all(requestMap)
Expand Down
33 changes: 0 additions & 33 deletions test/all.js

This file was deleted.

53 changes: 53 additions & 0 deletions test/docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const crypto = require('libp2p-crypto')
const fetch = require('node-fetch')

async function sample() {
// We get the secret key from the environment or other secret storage.
const base64_encoded_key = process.env['MY_SECRET_PUBLISHING_KEY']

// Keys used, for example, as a GitHub Secret are base64 encoded.
const marshalled_key = Buffer.from(base64_encoded_key, 'base64')

// IPFS-generatetd keys are stored marshalled into a protocol buffer.
const secret_key = await crypto.keys.unmarshalPrivateKey(marshalled_key)

// This is the content we want to publish.
const body = {"hello": "world"}

// We prepare a fetch options payload.
const options = {
method: 'POST',
headers: {
'content-type': 'application/json',
'accept': 'application/json',
},
body: JSON.stringify(body),
}

// Sign the body and send in a header.
const body_buffer = Buffer.from(options.body)
const sig = await secret_key.sign(body_buffer)
options.headers['x-signature'] = Buffer.from(sig).toString('base64')

// Tell us what public key you're authenticated with. We'll validate this on our end.
const public_key = secret_key.public
const marshalled_pk = await crypto.keys.marshalPublicKey(public_key)
const base64_pub_key = marshalled_pk.toString('base64')
options.headers['x-public-key'] = base64_pub_key

// Set up your core and content address. If you need a core contact alex@kubelt.com.
const core = 'test'
const address = 'my_content_name'

// Set up your publishing metadata header. See "API Reference", above, for values.
const metadata = {
published: true,
as: "dag", // ALPHA: "dag" is currently the only supported value.
}
options.headers['x-metadata'] = JSON.stringify(metadata)

const result = await fetch(`https://staging.pndo.xyz/v0/api/content/${core}/${address}`, options)
console.log(await result.text())
}

sample()
23 changes: 0 additions & 23 deletions test/esttest.js

This file was deleted.

46 changes: 0 additions & 46 deletions test/req_test.js

This file was deleted.

Loading

0 comments on commit 83ff41f

Please sign in to comment.