Skip to content

Commit

Permalink
feat: tests and cli
Browse files Browse the repository at this point in the history
  • Loading branch information
hugomrdias committed Sep 30, 2022
1 parent 0a43633 commit 1031e8d
Show file tree
Hide file tree
Showing 24 changed files with 437 additions and 257 deletions.
2 changes: 1 addition & 1 deletion packages/access-api/src/routes/raw.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function postRaw(request, env) {

const rsp = await server.request({
body: new Uint8Array(await request.arrayBuffer()),
headers: Object.fromEntries(request.headers.entries()),
headers: request.headers,
})
return new Response(rsp.body, { headers: rsp.headers })
}
2 changes: 1 addition & 1 deletion packages/access-api/src/routes/root.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export async function postRoot(request, env) {

const rsp = await server.request({
body: new Uint8Array(await request.arrayBuffer()),
headers: Object.fromEntries(request.headers.entries()),
headers: request.headers,
})
return new Response(rsp.body, { headers: rsp.headers })
}
31 changes: 31 additions & 0 deletions packages/access-api/src/service/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as Server from '@ucanto/server'
import * as Identity from '@web3-storage/access/capabilities/identity'
import * as Account from '@web3-storage/access/capabilities/account'
import { identityRegisterProvider } from './identity-register.js'
import { identityValidateProvider } from './identity-validate.js'
import { voucherClaimProvider } from './voucher-claim.js'
import { voucherRedeemProvider } from './voucher-redeem.js'
import { Failure } from '@ucanto/server'

/**
* @param {import('../bindings').RouteContext} ctx
Expand All @@ -23,6 +25,35 @@ export function service(ctx) {
claim: voucherClaimProvider(ctx),
redeem: voucherRedeemProvider(ctx),
},

account: {
// @ts-expect-error - types from query dont match handler output
info: Server.provide(Account.info, async ({ capability }) => {
const { results } = await ctx.db.fetchOne({
tableName: 'accounts',
fields: '*',
where: {
conditions: 'did =?1',
params: [capability.with],
},
})

if (!results) {
throw new Failure('Account not found...')
}
return {
did: results.did,
agent: results.agent,
email: results.email,
product: results.product,
updated_at: results.update_at,
inserted_at: results.inserted_at,
}
}),
all: Server.provide(Account.all, async ({ capability }) => {
return capability
}),
},
// @ts-ignore
testing: {
pass() {
Expand Down
5 changes: 2 additions & 3 deletions packages/access-api/src/ucanto/client-codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@ import { UTF8 } from '@ucanto/transport'
/** @type {import('./types.js').ClientCodec} */
export const clientCodec = {
async encode(invocations, options) {
/** @type {Record<string, string>} */
const headers = {}
const headers = new Headers()
const chain = await Delegation.delegate(invocations[0])

// TODO iterate over proofs and send them too
// for (const ucan of chain.iterate()) {
// //
// }
headers.authorization = `bearer ${UCAN.format(chain.data)}`

headers.set('authorization', `bearer ${UCAN.format(chain.data)}`)
return { headers, body: new Uint8Array() }
},

Expand Down
4 changes: 2 additions & 2 deletions packages/access-api/src/ucanto/server-codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function multiValueHeader(headers, name) {
}

/**
* @param {Record<string, string>} headers
* @param {Record<string, string> | Headers} headers
*/
async function parseHeaders(headers) {
const h = new Headers(headers)
Expand Down Expand Up @@ -130,7 +130,7 @@ export const serverCodec = {
*/
encode(result) {
return {
headers: HEADERS,
headers: new Headers(HEADERS),
body: UTF8.encode(JSON.stringify(result)),
}
},
Expand Down
19 changes: 8 additions & 11 deletions packages/access-api/test/identity-register.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as Identity from '@web3-storage/access/capabilities/identity'
import { Accounts } from '../src/kvs/accounts.js'
import { context, test } from './helpers/context.js'
// eslint-disable-next-line no-unused-vars
import * as Types from '@ucanto/interface'
import * as Ucanto from '@ucanto/interface'

test.beforeEach(async (t) => {
t.context = await context()
Expand All @@ -26,22 +26,20 @@ test('register', async (t) => {
if (out?.error || !out) {
return t.fail()
}
// @ts-ignore
const ucan = UCAN.parse(
// @ts-ignore
out.delegation.replace('http://localhost:8787/validate?ucan=', '')
)
const jwt =
/** @type UCAN.JWT<[import('@web3-storage/access/types').IdentityRegister]>} */ (
out.delegation.replace('http://localhost:8787/validate?ucan=', '')
)
const ucan = UCAN.parse(jwt)
const root = await UCAN.write(ucan)
const proof = Delegation.create({ root })

const register = Identity.register.invoke({
audience: service,
issuer,
// @ts-ignore
with: proof.capabilities[0].with,
nb: {
// @ts-ignore
as: proof.capabilities[0].as,
as: proof.capabilities[0].nb.as,
},
proofs: [proof],
})
Expand Down Expand Up @@ -73,8 +71,7 @@ test('identify', async (t) => {
if (out?.error || !out) {
return
}
/** @type {Types.UCAN.JWT<[import('@web3-storage/access/types').IdentityRegister]>} */
// @ts-ignore
/** @type {Ucanto.UCAN.JWT<[import('@web3-storage/access/types').IdentityRegister]>} */
const jwt = out.delegation.replace('http://localhost:8787/validate?ucan=', '')
const ucan = UCAN.parse(jwt)
const root = await UCAN.write(ucan)
Expand Down
53 changes: 9 additions & 44 deletions packages/access-api/test/identity-validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,56 +68,21 @@ test('should route correctly to identity/validate', async (t) => {
if (out?.error || !out) {
return t.fail()
}
const ucan = UCAN.parse(
// @ts-ignore
out.delegation.replace('http://localhost:8787/validate?ucan=', '')
)

const jwt =
/** @type UCAN.JWT<[import('@web3-storage/access/types').IdentityRegister]>} */ (
out.delegation.replace('http://localhost:8787/validate?ucan=', '')
)
const ucan = UCAN.parse(jwt)
t.is(ucan.audience.did(), issuer.did())
t.is(ucan.issuer.did(), service.did())
t.deepEqual(ucan.capabilities, [
{
can: 'identity/register',
with: 'mailto:hugo@dag.house',
as: issuer.did(),
nb: {
as: issuer.did(),
},
},
])
})

// test('should route correctly to identity/validate and fail with proof', async (t) => {
// const { mf } = t.context
// const kp = await ucans.EdKeypair.create()
// const rootUcan = await ucans.build({
// audience: kp.did(),
// issuer: serviceKp,
// capabilities: [
// {
// can: { namespace: 'identity', segments: ['validate'] },
// with: { scheme: 'mailto', hierPart: '*' },
// },
// ],
// lifetimeInSeconds: 100,
// })
// const ucan = await ucans.build({
// audience: serviceKp.did(),
// issuer: kp,
// capabilities: [
// {
// can: { namespace: 'identity', segments: ['validate'] },
// with: { scheme: 'mailto', hierPart: 'alice@mail.com' },
// },
// ],
// lifetimeInSeconds: 100,
// proofs: [ucans.encode(rootUcan)],
// })
// const res = await mf.dispatchFetch('http://localhost:8787', {
// method: 'POST',
// headers: {
// Authorization: `Bearer ${ucans.encode(ucan)}`,
// },
// })
// const rsp = await res.json()
// t.deepEqual(rsp, {
// ok: false,
// error: { code: 'Error', message: 'Invalid capability' },
// })
// })
3 changes: 1 addition & 2 deletions packages/access-api/test/voucher-claim.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,10 @@ test('should voucher/claim', async (t) => {
t.deepEqual(delegation.proofs[0].issuer.did(), service.did())
t.deepEqual(delegation.proofs[0].capabilities, [
{
// TODO proof should have account
with: service.did(),
can: 'voucher/redeem',
nb: {
account: service.did(),
account: 'did:*',
identity: 'mailto:*',
product: 'product:*',
},
Expand Down
52 changes: 44 additions & 8 deletions packages/access/src/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as CBOR from '@ucanto/transport/cbor'
import * as HTTP from '@ucanto/transport/http'
import { delegate } from '@ucanto/core'
import * as Voucher from './capabilities/voucher.js'
import * as Account from './capabilities/account.js'
import { Websocket } from './utils/ws.js'
import { stringToDelegation } from './encoding.js'
import { URI } from '@ucanto/validator'
Expand Down Expand Up @@ -45,9 +46,7 @@ const HOST = 'https://access-api.web3.storage'
*/
export async function buildConnection(principal, _fetch, url) {
const rsp = await _fetch(url + 'version')
// @ts-ignore
const { did } = await rsp.json()
// TODO how to parse any DID ????
const service = DID.parse(did)

const connection = Client.connect({
Expand All @@ -57,7 +56,6 @@ export async function buildConnection(principal, _fetch, url) {
channel: HTTP.open({
url,
method: 'POST',
// @ts-ignore
fetch: _fetch,
}),
})
Expand All @@ -80,6 +78,7 @@ export class Agent {
this.fetch = opts.fetch
this.connection = opts.connection
this.data = opts.data
this.issuer = opts.data.principal

// validate fetch implementation
if (!this.fetch) {
Expand Down Expand Up @@ -114,7 +113,7 @@ export class Agent {

const data = await opts.store.load()
const { connection, service } = await buildConnection(
data.agent,
data.principal,
_fetch,
url
)
Expand All @@ -129,7 +128,7 @@ export class Agent {
}

did() {
return this.data.agent.did()
return this.data.principal.did()
}

/**
Expand All @@ -140,19 +139,23 @@ export class Agent {
const accDelegation = await delegate({
// @ts-ignore
issuer: account,
audience: this.data.agent,
audience: this.data.principal,
capabilities: [
{
can: 'voucher/*',
with: account.did(),
},
{
can: 'account/*',
with: account.did(),
},
],
lifetimeInSeconds: 8_600_000,
})

const inv = await Voucher.claim
.invoke({
issuer: this.data.agent,
issuer: this.data.principal,
audience: this.service,
with: account.did(),
nb: {
Expand All @@ -172,7 +175,7 @@ export class Agent {

const accInv = await Voucher.redeem
.invoke({
issuer: this.data.agent,
issuer: this.data.principal,
audience: this.service,
with: this.service.did(),
nb: {
Expand Down Expand Up @@ -251,4 +254,37 @@ export class Agent {
peer(channel) {
return new Peer({ agent: this, channel })
}

/**
* @param {Ucanto.URI<"did:">} account
*/
async getAccountInfo(account) {
const proofs = isEmpty(this.data.delegations.getByResource(account))
if (!proofs) {
throw new TypeError('No proofs for "account/info".')
}

const inv = await Account.info
.invoke({
issuer: this.issuer,
audience: this.service,
with: account,
proofs,
})
.execute(this.connection)

return inv
}
}

/**
* @template T
* @param { Array<T | undefined> | undefined} arr
*/
function isEmpty(arr) {
if (!Array.isArray(arr) || arr.length === 0) {
return
}

return /** @type {T[]} */ (arr)
}
6 changes: 4 additions & 2 deletions packages/access/src/awake/peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class Peer {

// step3 - awake/res send
const ucan = await UCAN.issue({
issuer: this.agent.data.agent,
issuer: this.agent.data.principal,
audience: this.nextdid,
capabilities: [{ with: 'awake:', can: '*' }],
facts: [
Expand Down Expand Up @@ -188,7 +188,9 @@ export class Peer {

// Pin signature
const bytes = u8.fromString(this.nextdid.did() + this.pin.toString())
const signed = await this.agent.data.agent.sign(await sha256.encode(bytes))
const signed = await this.agent.data.principal.sign(
await sha256.encode(bytes)
)
this.channel.sendMsg(this.nextdid, {
did: this.did,
sig: u8.toString(signed, 'base64'),
Expand Down
8 changes: 7 additions & 1 deletion packages/access/src/capabilities/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { capability, URI } from '@ucanto/server'
import { store } from './store.js'
import { equalWith } from './utils.js'

export const all = capability({
can: 'account/*',
with: URI.match({ protocol: 'did:' }),
derives: equalWith,
})

/**
* `account/info` can be derived from any of the `store/*`
* capability that has matichng `with`. This allows store service
* to identify account based on any user request.
*/
export const info = store.derive({
export const info = all.or(store).derive({

This comment has been minimized.

Copy link
@Gozala

Gozala Oct 11, 2022

Contributor

I'm pretty sure this is what fixed the problem you're describing in storacha/ucanto#87 and not the provider that was added

to: capability({
can: 'account/info',
with: URI.match({ protocol: 'did:' }),
Expand Down
Loading

0 comments on commit 1031e8d

Please sign in to comment.