Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(capabilities): implement access/authorize and ./update caps #387

Merged
merged 2 commits into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions packages/capabilities/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
},
"exports": {
".": "./src/index.js",
"./types": "./dist/src/types.d.ts",
"./*": "./src/*.js"
"./*": "./src/*.js",
"./types": "./dist/src/types.d.ts"
},
"typesVersions": {
"*": {
Expand All @@ -34,20 +34,23 @@
"store": [
"dist/src/store"
],
"types": [
"dist/src/types"
],
"top": [
"dist/src/top"
],
"upload": [
"dist/src/upload"
],
"voucher": [
"dist/src/voucher"
],
"access": [
"dist/src/access"
],
"utils": [
"dist/src/utils"
],
"voucher": [
"dist/src/voucher"
"types": [
"dist/src/types"
]
}
},
Expand Down
102 changes: 102 additions & 0 deletions packages/capabilities/src/access.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Access Capabilities
*
* These can be imported directly with:
* ```js
* import * as Access from '@web3-storage/capabilities/access'
* ```
*
* @module
*/
import { capability, URI, DID } from '@ucanto/validator'
// @ts-ignore
// eslint-disable-next-line no-unused-vars
import * as Types from '@ucanto/interface'
import { equalWith, fail, equal } from './utils.js'
import { top } from './top.js'

export { top }

/**
* Account identifier.
*/
export const As = DID.match({ method: 'mailto' })

/**
* Capability can only be delegated (but not invoked) allowing audience to
* derived any `access/` prefixed capability for the agent identified
* by did:key in the `with` field.
*/
export const access = top.derive({
to: capability({
can: 'access/*',
with: URI.match({ protocol: 'did:' }),
derives: equalWith,
}),
derives: equalWith,
})

const base = top.or(access)

/**
* Capability can be invoked by an agent to request a `./update` for an account.
*
* `with` field identifies requesting agent, which MAY be different from iss field identifying issuing agent.
*/
export const authorize = base.derive({
to: capability({
can: 'access/authorize',
with: URI.match({ protocol: 'did:' }),
nb: {
/**
* Value MUST be a did:mailto identifier of the account
* that the agent wishes to represent via did:key in the `with` field.
* It MUST be a valid did:mailto identifier.
*/
as: As,
},
derives: (child, parent) => {
return (
fail(equalWith(child, parent)) ||
fail(equal(child.nb.as, parent.nb.as, 'as')) ||
true
)
},
}),
/**
* `access/authorize` can be derived from the `access/*` & `*` capability
* as long as the `with` fields match.
*/
derives: equalWith,
})

/**
* Issued by trusted authority (usually the one handling invocation that contains this proof)
* to the account (aud) to update invocation local state of the document.
*
* @see https://github.com/web3-storage/specs/blob/main/w3-account.md#update
*
* @example
* ```js
* {
iss: "did:web:web3.storage",
aud: "did:mailto:[email protected]",
att: [{
with: "did:web:web3.storage",
can: "./update",
nb: { key: "did:key:zAgent" }
}],
exp: null
sig: "..."
}
* ```
*/
export const session = capability({
can: './update',
// Should be web3.storage DID
with: URI.match({ protocol: 'did:' }),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be DID.match({ method: 'web' }) instead or perhaps even a Schema.literal('did:web:web3.storage') ?

nb: {
// Agent DID so it can sign UCANs as did:mailto if it matches this delegation `aud`
key: DID.match({ method: 'key' }),
},
})
4 changes: 4 additions & 0 deletions packages/capabilities/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as Top from './top.js'
import * as Store from './store.js'
import * as Upload from './upload.js'
import * as Voucher from './voucher.js'
import * as Access from './access.js'
import * as Utils from './utils.js'

export { Space, Top, Store, Upload, Voucher, Utils }
Expand All @@ -24,4 +25,7 @@ export const abilitiesAsStrings = [
Store.list.can,
Voucher.claim.can,
Voucher.redeem.can,
Access.access.can,
Access.authorize.can,
Access.session.can,
]
13 changes: 12 additions & 1 deletion packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import { top } from './top.js'
import { add, list, remove, store } from './store.js'
import * as UploadCaps from './upload.js'
import { claim, redeem } from './voucher.js'
import * as AccessCaps from './access.js'

// Access
export type Access = InferInvokedCapability<typeof AccessCaps.access>
export type AccessAuthorize = InferInvokedCapability<
typeof AccessCaps.authorize
>
export type AccessSession = InferInvokedCapability<typeof AccessCaps.session>

// Space
export type Space = InferInvokedCapability<typeof space>
Expand Down Expand Up @@ -47,5 +55,8 @@ export type AbilitiesArray = [
StoreRemove['can'],
StoreList['can'],
VoucherClaim['can'],
VoucherRedeem['can']
VoucherRedeem['can'],
Access['can'],
AccessAuthorize['can'],
AccessSession['can']
]
Loading