Skip to content

Commit

Permalink
feat: Impelment InferInvokedCapability per #99 (#100)
Browse files Browse the repository at this point in the history
* feat: unifiy principals & signers

Co-authored-by: Hugo Dias <[email protected]>

* fix: tests on core

* fix: transport package

* chore: update client to 0.9

* feat(validator): update to ucan 0.9

* feat(server): upgrade to 0.9

* fix(principal): invalid test

* chore: update dag-ucan dep

* feat: rename caveats to nb

* fix(server): type checks

* fix(validator): increase coverage

* fix(docs): rename caveats to nb

* feat: improve decoders

* fix: types

* fix: types for .create / .invoke of capability

* fix: nb inference 98

* feat: implement InferInvokedCapability type

* fix: remove conflicting type

Co-authored-by: Hugo Dias <[email protected]>
  • Loading branch information
Gozala and hugomrdias authored Sep 30, 2022
1 parent b752b39 commit fc5a2ac
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 97 deletions.
5 changes: 0 additions & 5 deletions packages/interface/src/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ export interface TheCapabilityParser<M extends Match<ParsedCapability>>
): IssuedInvocationView<M['value']>
}

export type InferInvokedCapability<C extends Capability> = C
// keyof C['nb'] extends never
// ? { can: C['can']; with: C['with']; nb?: never }
// : { can: C['can']; with: C['with']; nb: C['nb'] }

export type InferCreateOptions<R extends Resource, C extends {} | undefined> =
// If capability has no NB we want to prevent passing it into
// .create funciton so we make `nb` as optional `never` type so
Expand Down
7 changes: 7 additions & 0 deletions packages/interface/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@ import {
import * as UCAN from '@ipld/dag-ucan'
import {
CanIssue,
Match,
InvalidAudience,
Unauthorized,
UnavailableProof,
ParsedCapability,
CapabilityParser,
} from './capability.js'
import type * as Transport from './transport.js'
import type { Tuple, Block } from './transport.js'
Expand Down Expand Up @@ -387,3 +390,7 @@ export type URI<P extends Protocol = Protocol> = `${P}${string}` &
export interface PrincipalParser {
parse(did: UCAN.DID): UCAN.Verifier
}

export type InferInvokedCapability<
C extends CapabilityParser<Match<ParsedCapability>>
> = C extends CapabilityParser<Match<infer T>> ? T : never
95 changes: 95 additions & 0 deletions packages/validator/test/inference.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as Voucher from './voucher.js'
import { test, assert } from './test.js'
import { alice, bob, mallory, service as w3 } from './fixtures.js'
import { capability, URI, Link } from '../src/lib.js'
import * as API from './types.js'

test('execute capabilty', () =>
/**
* @param {API.ConnectionView<API.Service>} connection
*/
async connection => {
const claim = Voucher.Claim.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
identity: URI.from(`mailto:${alice.did}@web.mail`),
product: Link.parse('bafkqaaa'),
service: w3.did(),
},
})

const proof = Voucher.Redeem.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
product: 'test',
identity: 'whatever',
account: alice.did(),
},
})

const redeem = Voucher.Redeem.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
product: 'test',
identity: proof.capabilities[0].nb.identity,
account: alice.did(),
},
})

const r1 = await redeem.execute(connection)
if (!r1.error) {
r1.product.toLowerCase()
}

const r2 = await claim.execute(connection)
if (!r2.error) {
r2.service.toUpperCase()
}
})

test('can access fields on the proof', () =>
/**
* @param {API.Delegation<[API.VoucherRedeem]>} proof
*/
proof => {
const redeem = Voucher.Redeem.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
product: proof.capabilities[0].nb.product,
identity: proof.capabilities[0].nb.identity,
account: proof.capabilities[0].nb.account,
},
proofs: [proof],
})
})

test('use InferInvokedCapability', () =>
/**
* @param {API.ConnectionView<API.Service>} connection
* @param {API.InferInvokedCapability<typeof Voucher.Redeem>} capability
*/
async (connection, capability) => {
const redeem = Voucher.Redeem.invoke({
issuer: alice,
audience: w3,
with: capability.with,
nb: {
product: capability.nb.product,
identity: capability.nb.identity,
account: capability.nb.account,
},
})

const result = await redeem.execute(connection)
if (!result.error) {
result.product.toLocaleLowerCase()
}
})
91 changes: 1 addition & 90 deletions packages/validator/test/lib.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { test, assert } from './test.js'
import { access } from '../src/lib.js'
import { capability, URI, Text, Link, DID } from '../src/lib.js'
import { capability, URI, Link } from '../src/lib.js'
import { Failure } from '../src/error.js'
import * as ed25519 from '@ucanto/principal/ed25519'
import * as Client from '@ucanto/client'

import { alice, bob, mallory, service as w3 } from './fixtures.js'
import { UCAN, DID as Principal } from '@ucanto/core'
import { UnavailableProof } from '../src/error.js'
import { equalWith, canDelegateURI, fail } from './util.js'
import * as API from './types.js'

const storeAdd = capability({
Expand Down Expand Up @@ -1131,91 +1130,3 @@ test('resolve proof', async () => {
],
})
})

test('execute capabilty', async () => {
const Voucher = capability({
can: 'voucher/*',
with: DID.match({ method: 'key' }),
})

const Claim = Voucher.derive({
to: capability({
can: 'voucher/claim',
with: DID.match({ method: 'key' }),
nb: {
product: Link,
identity: URI.match({ protocol: 'mailto:' }),
service: DID,
},
derives: (child, parent) => {
return (
fail(equalWith(child, parent)) ||
fail(canDelegateURI(child.nb.identity, parent.nb.identity)) ||
fail(
canDelegateURI(
child.nb.product.toString(),
parent.nb.product.toString()
)
) ||
fail(canDelegateURI(child.nb.service, parent.nb.service)) ||
true
)
},
}),
derives: equalWith,
})

const claim = Claim.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
identity: URI.from(`mailto:${alice.did}@web.mail`),
product: Link.parse('bafkqaaa'),
service: w3.did(),
},
})

const Redeem = capability({
can: 'voucher/redeem',
with: URI.match({ protocol: 'did:' }),
nb: {
product: Text,
identity: Text,
account: URI.match({ protocol: 'did:' }),
},
})

const proof = Redeem.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
product: 'test',
identity: 'whatever',
account: alice.did(),
},
})

/**
* @param {API.ConnectionView<API.Service>} connection
* @param {API.Delegation<[API.VoucherRedeem]>} proof
*/

const demo = async (connection, proof) => {
const redeem = Redeem.invoke({
issuer: alice,
audience: w3,
with: alice.did(),
nb: {
product: 'test',
identity: proof.capabilities[0].nb.identity,
account: alice.did(),
},
})

const r = await redeem.execute(connection)

const result = await claim.execute(connection)
}
})
4 changes: 2 additions & 2 deletions packages/validator/test/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export interface VoucherRedeem

export interface Service {
voucher: {
claim: ServiceMethod<VoucherClaim, undefined, Failure>
redeem: ServiceMethod<VoucherRedeem, undefined, Failure>
claim: ServiceMethod<VoucherClaim, { service: DID }, Failure>
redeem: ServiceMethod<VoucherRedeem, { product: string }, Failure>
}
}
44 changes: 44 additions & 0 deletions packages/validator/test/voucher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { equalWith, canDelegateURI, fail } from './util.js'
import { capability, URI, Text, Link, DID } from '../src/lib.js'

export const Voucher = capability({
can: 'voucher/*',
with: DID.match({ method: 'key' }),
})

export const Claim = Voucher.derive({
to: capability({
can: 'voucher/claim',
with: DID.match({ method: 'key' }),
nb: {
product: Link,
identity: URI.match({ protocol: 'mailto:' }),
service: DID,
},
derives: (child, parent) => {
return (
fail(equalWith(child, parent)) ||
fail(canDelegateURI(child.nb.identity, parent.nb.identity)) ||
fail(
canDelegateURI(
child.nb.product.toString(),
parent.nb.product.toString()
)
) ||
fail(canDelegateURI(child.nb.service, parent.nb.service)) ||
true
)
},
}),
derives: equalWith,
})

export const Redeem = capability({
can: 'voucher/redeem',
with: URI.match({ protocol: 'did:' }),
nb: {
product: Text,
identity: Text,
account: URI.match({ protocol: 'did:' }),
},
})

0 comments on commit fc5a2ac

Please sign in to comment.