Skip to content

Commit

Permalink
Inject plugins by passing through fns instead of global state var
Browse files Browse the repository at this point in the history
  • Loading branch information
dholms committed Jul 5, 2022
1 parent ef01a24 commit 61ed38e
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 335 deletions.
40 changes: 23 additions & 17 deletions packages/core/src/attenuation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as token from "./token.js"
import Plugins from "./plugins.js"
import { Capability } from "./capability/index.js"
import { Ucan } from "./types.js"
import { ResourcePointer } from "./capability/resource-pointer.js"
Expand Down Expand Up @@ -119,19 +120,20 @@ export type OwnershipScope
* out different ways of delegating a capability from the attenuations.
* It also makes it possible to return early if a valid delegation chain has been found.
*/
export async function* delegationChains(
semantics: DelegationSemantics,
ucan: Ucan,
isRevoked: (ucan: Ucan) => Promise<boolean> = async () => false
): AsyncIterable<DelegationChain | Error> {
export const delegationChains = (plugins: Plugins) =>
async function* (
semantics: DelegationSemantics,
ucan: Ucan,
isRevoked: (ucan: Ucan) => Promise<boolean> = async () => false,
): AsyncIterable<DelegationChain | Error> {

if (await isRevoked(ucan)) {
yield new Error(`UCAN Revoked: ${token.encode(ucan)}`)
return
}

yield* capabilitiesFromParenthood(ucan)
yield* capabilitiesFromDelegation(semantics, ucan, isRevoked)
yield* capabilitiesFromDelegation(plugins, semantics, ucan, isRevoked)
}


Expand Down Expand Up @@ -265,14 +267,15 @@ function* capabilitiesFromParenthood(ucan: Ucan): Iterable<DelegationChain> {


async function* capabilitiesFromDelegation(
plugins: Plugins,
semantics: DelegationSemantics,
ucan: Ucan,
isRevoked: (ucan: Ucan) => Promise<boolean>
isRevoked: (ucan: Ucan) => Promise<boolean>,
): AsyncIterable<DelegationChain | Error> {

let proofIndex = 0

for await (const proof of token.validateProofs(ucan)) {
for await (const proof of token.validateProofs(plugins)(ucan)) {
if (proof instanceof Error) {
yield proof
continue
Expand All @@ -283,15 +286,15 @@ async function* capabilitiesFromDelegation(
switch (capability.with.scheme.toLowerCase()) {
case "my": continue // cannot be delegated, only introduced by parenthood.
case "as": {
yield* handleAsDelegation(semantics, capability, ucan, proof, isRevoked)
yield* handleAsDelegation(plugins, semantics, capability, ucan, proof, isRevoked)
break
}
case "prf": {
yield* handlePrfDelegation(semantics, capability, ucan, proof, proofIndex, isRevoked)
yield* handlePrfDelegation(plugins, semantics, capability, ucan, proof, proofIndex, isRevoked)
break
}
default: {
yield* handleNormalDelegation(semantics, capability, ucan, proof, isRevoked)
yield* handleNormalDelegation(plugins, semantics, capability, ucan, proof, isRevoked)
}
}
} catch (e) {
Expand All @@ -313,11 +316,12 @@ async function* capabilitiesFromDelegation(


async function* handleAsDelegation(
plugins: Plugins,
semantics: DelegationSemantics,
capability: Capability,
ucan: Ucan,
proof: Ucan,
isRevoked: (ucan: Ucan) => Promise<boolean>
isRevoked: (ucan: Ucan) => Promise<boolean>,
): AsyncIterable<DelegatedOwnership | Error> {
const split = capability.with.hierPart.split(":")
const scheme = split[ split.length - 1 ]
Expand All @@ -326,7 +330,7 @@ async function* handleAsDelegation(
? SUPERUSER
: { scheme, ability: capability.can }

for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) {
for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) {
if (delegationChain instanceof Error) {
yield delegationChain
continue
Expand All @@ -352,12 +356,13 @@ async function* handleAsDelegation(


async function* handlePrfDelegation(
plugins: Plugins,
semantics: DelegationSemantics,
capability: Capability,
ucan: Ucan,
proof: Ucan,
proofIndex: number,
isRevoked: (ucan: Ucan) => Promise<boolean>
isRevoked: (ucan: Ucan) => Promise<boolean>,
): AsyncIterable<DelegatedCapability | Error> {
if (
capability.with.hierPart !== SUPERUSER
Expand All @@ -367,7 +372,7 @@ async function* handlePrfDelegation(
// we only process the delegation if proofIndex === 2
return
}
for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) {
for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) {
if (delegationChain instanceof Error) {
yield delegationChain
continue
Expand All @@ -385,13 +390,14 @@ async function* handlePrfDelegation(


async function* handleNormalDelegation(
plugins: Plugins,
semantics: DelegationSemantics,
capability: Capability,
ucan: Ucan,
proof: Ucan,
isRevoked: (ucan: Ucan) => Promise<boolean>
isRevoked: (ucan: Ucan) => Promise<boolean>,
): AsyncIterable<DelegatedCapability | Error> {
for await (const delegationChain of delegationChains(semantics, proof, isRevoked)) {
for await (const delegationChain of delegationChains(plugins)(semantics, proof, isRevoked)) {
if (delegationChain instanceof Error) {
yield delegationChain
continue
Expand Down
52 changes: 29 additions & 23 deletions packages/core/src/builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as token from "./token.js"
import * as util from "./util.js"
import Plugins from "./plugins.js"

import { Keypair, Fact, UcanPayload, isKeypair, Ucan, DidableKey } from "./types.js"
import { Capability, isCapability } from "./capability/index.js"
Expand Down Expand Up @@ -40,6 +41,21 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa
&& util.hasProp(obj, "expiration") && typeof obj.expiration === "number"
}


/**
* Create an empty builder.
* Before finalising the builder, you need to at least call
* - `issuedBy`
* - `toAudience` and
* - `withLifetimeInSeconds` or `withExpiration`.
* To finalise the builder, call its `build` or `buildPayload` method.
*/

export const createBuilder = (plugins: Plugins) =>
(): Builder<Record<string, never>> => {
return new Builder(plugins, {}, { capabilities: [], facts: [], proofs: [], addNonce: false })
}

/**
* A builder API for UCANs.
*
Expand All @@ -59,26 +75,16 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa
*/
export class Builder<State extends Partial<BuildableState>> {

private plugins: Plugins
private state: State // portion of the state that's required to be set before building
private defaultable: DefaultableState // portion of the state that has sensible defaults

private constructor(state: State, defaultable: DefaultableState) {
constructor(plugins: Plugins, state: State, defaultable: DefaultableState) {
this.plugins = plugins
this.state = state
this.defaultable = defaultable
}

/**
* Create an empty builder.
* Before finalising the builder, you need to at least call
* - `issuedBy`
* - `toAudience` and
* - `withLifetimeInSeconds` or `withExpiration`.
* To finalise the builder, call its `build` or `buildPayload` method.
*/
static create(): Builder<Record<string, never>> {
return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false })
}

/**
* @param issuer The issuer as a DID string ("did:key:...").
*
Expand All @@ -88,7 +94,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (!isKeypair(issuer)) {
throw new TypeError(`Expected a Keypair, but got ${issuer}`)
}
return new Builder({ ...this.state, issuer }, this.defaultable)
return new Builder(this.plugins, { ...this.state, issuer }, this.defaultable)
}

/**
Expand All @@ -103,7 +109,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (typeof audience !== "string") {
throw new TypeError(`Expected audience DID as string, but got ${audience}`)
}
return new Builder({ ...this.state, audience }, this.defaultable)
return new Builder(this.plugins, { ...this.state, audience }, this.defaultable)
}

/**
Expand All @@ -130,7 +136,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (this.defaultable.notBefore != null && expiration < this.defaultable.notBefore) {
throw new Error(`Can't set expiration to ${expiration} which is before 'notBefore': ${this.defaultable.notBefore}`)
}
return new Builder({ ...this.state, expiration }, this.defaultable)
return new Builder(this.plugins, { ...this.state, expiration }, this.defaultable)
}

/**
Expand All @@ -143,7 +149,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (util.hasProp(this.state, "expiration") && typeof this.state.expiration === "number" && this.state.expiration < notBeforeTimestamp) {
throw new Error(`Can't set 'notBefore' to ${notBeforeTimestamp} which is after expiration: ${this.state.expiration}`)
}
return new Builder(this.state, { ...this.defaultable, notBefore: notBeforeTimestamp })
return new Builder(this.plugins, this.state, { ...this.defaultable, notBefore: notBeforeTimestamp })
}

/**
Expand All @@ -156,7 +162,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (!util.isRecord(fact) || facts.some(fct => !util.isRecord(fct))) {
throw new TypeError(`Expected fact(s) to be a record, but got ${fact}`)
}
return new Builder(this.state, {
return new Builder(this.plugins, this.state, {
...this.defaultable,
facts: [ ...this.defaultable.facts, fact, ...facts ]
})
Expand All @@ -166,7 +172,7 @@ export class Builder<State extends Partial<BuildableState>> {
* Will ensure that the built UCAN includes a number used once.
*/
withNonce(): Builder<State> {
return new Builder(this.state, { ...this.defaultable, addNonce: true })
return new Builder(this.plugins, this.state, { ...this.defaultable, addNonce: true })
}

/**
Expand All @@ -178,7 +184,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (!isCapability(capability)) {
throw new TypeError(`Expected capability, but got ${JSON.stringify(capability, null, " ")}`)
}
return new Builder(this.state, {
return new Builder(this.plugins, this.state, {
...this.defaultable,
capabilities: [ ...this.defaultable.capabilities, capability, ...capabilities ]
})
Expand Down Expand Up @@ -221,7 +227,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (!capabilityCanBeDelegated(semantics, requiredCapability, proof)) {
throw new Error(`Can't add capability to UCAN: Given proof doesn't give required rights to delegate.`)
}
return new Builder(this.state, {
return new Builder(this.plugins, this.state, {
...this.defaultable,
capabilities: [ ...this.defaultable.capabilities, requiredCapability ],
proofs: this.defaultable.proofs.find(p => token.encode(p) === token.encode(ucan)) == null
Expand All @@ -236,7 +242,7 @@ export class Builder<State extends Partial<BuildableState>> {
if (result != null) {
const ucan = result.ucan
const ucanEncoded = token.encode(ucan)
return new Builder(this.state, {
return new Builder(this.plugins, this.state, {
...this.defaultable,
capabilities: [ ...this.defaultable.capabilities, requiredCapability ],
proofs: this.defaultable.proofs.find(proof => token.encode(proof) === ucanEncoded) == null
Expand Down Expand Up @@ -282,7 +288,7 @@ export class Builder<State extends Partial<BuildableState>> {
throw new Error(`Builder is missing one of the required properties before it can be built: issuer, audience and expiration.`)
}
const payload = this.buildPayload()
return await token.signWithKeypair(payload, this.state.issuer)
return await token.signWithKeypair(this.plugins)(payload, this.state.issuer)
}

}
5 changes: 5 additions & 0 deletions packages/core/src/capability/ability.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Superuser, SUPERUSER } from "./super-user.js"
import * as util from "../util.js"

// RE-EXPORTS


export { Superuser, SUPERUSER }


// 💎

Expand Down
9 changes: 4 additions & 5 deletions packages/core/src/capability/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import * as ability from "./ability.js"
import * as resourcePointer from "./resource-pointer.js"
import * as superUser from "./super-user.js"
import * as util from "../util.js"

import { Ability, isAbility } from "./ability.js"
import { ResourcePointer, isResourcePointer } from "./resource-pointer.js"
import { Superuser, SUPERUSER } from "./super-user.js"
import { Superuser } from "./super-user.js"


// RE-EXPORTS


export { ability, resourcePointer, superUser }
export { ability, resourcePointer }



Expand Down Expand Up @@ -53,15 +52,15 @@ export function isEncodedCapability(obj: unknown): obj is EncodedCapability {
export function as(did: string, resource: Superuser | string): Capability {
return {
with: resourcePointer.as(did, resource),
can: SUPERUSER
can: ability.SUPERUSER
}
}


export function my(resource: Superuser | string): Capability {
return {
with: resourcePointer.my(resource),
can: SUPERUSER
can: ability.SUPERUSER
}
}

Expand Down
36 changes: 35 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import Plugins from "./plugins.js"
import * as token from "./token.js"
import * as verifyLib from "./verify.js"
import * as attenuation from "./attenuation.js"
import * as builder from "./builder.js"
import * as store from "./store.js"

export * from "./attenuation.js"
export * from "./builder.js"
export * from "./store.js"
Expand All @@ -7,5 +14,32 @@ export * from "./verify.js"
export * from "./plugins.js"

export * as capability from "./capability/index.js"
export * as ability from "./capability/ability.js"

export { Capability, EncodedCapability, isCapability } from "./capability/index.js"

export const injectPlugins = (plugins: Plugins) => {
const build = token.build(plugins)
const sign = token.sign(plugins)
const signWithKeypair = token.signWithKeypair(plugins)
const validate = token.validate(plugins)
const validateProofs = token.validateProofs(plugins)
const verify = verifyLib.verify(plugins)
const createBuilder = builder.createBuilder(plugins)
const storeFromTokens = store.storeFromTokens(plugins)
const emptyStore = store.emptyStore(plugins)
const delegationChains = attenuation.delegationChains(plugins)

export { Capability, EncodedCapability, isCapability } from "./capability/index.js"
return {
build,
sign,
signWithKeypair,
validate,
validateProofs,
verify,
createBuilder,
storeFromTokens,
emptyStore,
delegationChains,
}
}
Loading

0 comments on commit 61ed38e

Please sign in to comment.