From 61ed38e4ca8b21a23a9e1b313aa125acc4520b64 Mon Sep 17 00:00:00 2001 From: dholms Date: Tue, 5 Jul 2022 14:02:52 -0500 Subject: [PATCH] Inject plugins by passing through fns instead of global state var --- packages/core/src/attenuation.ts | 40 ++++--- packages/core/src/builder.ts | 52 +++++---- packages/core/src/capability/ability.ts | 5 + packages/core/src/capability/index.ts | 9 +- packages/core/src/index.ts | 36 +++++- packages/core/src/plugins.ts | 79 ++++++------- packages/core/src/store.ts | 34 ++++-- packages/core/src/token.ts | 76 +++++++------ packages/core/src/verify.ts | 8 +- packages/core/tests/attenuation.test.ts | 33 +++--- packages/core/tests/builder.test.ts | 33 +++--- packages/core/tests/capability/email.ts | 8 +- packages/core/tests/capability/wnfs.test.ts | 22 ++-- packages/core/tests/capability/wnfs.ts | 7 +- packages/core/tests/compatibility.test.ts | 8 +- packages/core/tests/setup.ts | 19 +++- packages/core/tests/store.test.ts | 55 ++++----- packages/core/tests/token.test.ts | 42 +++---- packages/core/tests/verify.test.ts | 118 +++++++++----------- packages/plugins/src/default-plugins.ts | 8 +- packages/ucans/src/index.ts | 19 +++- 21 files changed, 376 insertions(+), 335 deletions(-) diff --git a/packages/core/src/attenuation.ts b/packages/core/src/attenuation.ts index 26ee68b..85c570b 100644 --- a/packages/core/src/attenuation.ts +++ b/packages/core/src/attenuation.ts @@ -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" @@ -119,11 +120,12 @@ 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 = async () => false -): AsyncIterable { +export const delegationChains = (plugins: Plugins) => + async function* ( + semantics: DelegationSemantics, + ucan: Ucan, + isRevoked: (ucan: Ucan) => Promise = async () => false, + ): AsyncIterable { if (await isRevoked(ucan)) { yield new Error(`UCAN Revoked: ${token.encode(ucan)}`) @@ -131,7 +133,7 @@ export async function* delegationChains( } yield* capabilitiesFromParenthood(ucan) - yield* capabilitiesFromDelegation(semantics, ucan, isRevoked) + yield* capabilitiesFromDelegation(plugins, semantics, ucan, isRevoked) } @@ -265,14 +267,15 @@ function* capabilitiesFromParenthood(ucan: Ucan): Iterable { async function* capabilitiesFromDelegation( + plugins: Plugins, semantics: DelegationSemantics, ucan: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { 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 @@ -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) { @@ -313,11 +316,12 @@ async function* capabilitiesFromDelegation( async function* handleAsDelegation( + plugins: Plugins, semantics: DelegationSemantics, capability: Capability, ucan: Ucan, proof: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { const split = capability.with.hierPart.split(":") const scheme = split[ split.length - 1 ] @@ -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 @@ -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 + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { if ( capability.with.hierPart !== SUPERUSER @@ -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 @@ -385,13 +390,14 @@ async function* handlePrfDelegation( async function* handleNormalDelegation( + plugins: Plugins, semantics: DelegationSemantics, capability: Capability, ucan: Ucan, proof: Ucan, - isRevoked: (ucan: Ucan) => Promise + isRevoked: (ucan: Ucan) => Promise, ): AsyncIterable { - 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 diff --git a/packages/core/src/builder.ts b/packages/core/src/builder.ts index a582869..bc5e0ae 100644 --- a/packages/core/src/builder.ts +++ b/packages/core/src/builder.ts @@ -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" @@ -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> => { + return new Builder(plugins, {}, { capabilities: [], facts: [], proofs: [], addNonce: false }) +} + /** * A builder API for UCANs. * @@ -59,26 +75,16 @@ function isCapabilityLookupCapableState(obj: unknown): obj is CapabilityLookupCa */ export class Builder> { + 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> { - return new Builder({}, { capabilities: [], facts: [], proofs: [], addNonce: false }) - } - /** * @param issuer The issuer as a DID string ("did:key:..."). * @@ -88,7 +94,7 @@ export class Builder> { 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) } /** @@ -103,7 +109,7 @@ export class Builder> { 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) } /** @@ -130,7 +136,7 @@ export class Builder> { 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) } /** @@ -143,7 +149,7 @@ export class Builder> { 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 }) } /** @@ -156,7 +162,7 @@ export class Builder> { 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 ] }) @@ -166,7 +172,7 @@ export class Builder> { * Will ensure that the built UCAN includes a number used once. */ withNonce(): Builder { - return new Builder(this.state, { ...this.defaultable, addNonce: true }) + return new Builder(this.plugins, this.state, { ...this.defaultable, addNonce: true }) } /** @@ -178,7 +184,7 @@ export class Builder> { 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 ] }) @@ -221,7 +227,7 @@ export class Builder> { 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 @@ -236,7 +242,7 @@ export class Builder> { 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 @@ -282,7 +288,7 @@ export class Builder> { 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) } } diff --git a/packages/core/src/capability/ability.ts b/packages/core/src/capability/ability.ts index 873c5b4..b3c8b70 100644 --- a/packages/core/src/capability/ability.ts +++ b/packages/core/src/capability/ability.ts @@ -1,6 +1,11 @@ import { Superuser, SUPERUSER } from "./super-user.js" import * as util from "../util.js" +// RE-EXPORTS + + +export { Superuser, SUPERUSER } + // 💎 diff --git a/packages/core/src/capability/index.ts b/packages/core/src/capability/index.ts index 148306d..b35504d 100644 --- a/packages/core/src/capability/index.ts +++ b/packages/core/src/capability/index.ts @@ -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 } @@ -53,7 +52,7 @@ 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 } } @@ -61,7 +60,7 @@ export function as(did: string, resource: Superuser | string): Capability { export function my(resource: Superuser | string): Capability { return { with: resourcePointer.my(resource), - can: SUPERUSER + can: ability.SUPERUSER } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index db0ee1e..429bae9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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" @@ -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" \ No newline at end of file + return { + build, + sign, + signWithKeypair, + validate, + validateProofs, + verify, + createBuilder, + storeFromTokens, + emptyStore, + delegationChains, + } +} \ No newline at end of file diff --git a/packages/core/src/plugins.ts b/packages/core/src/plugins.ts index f04b8e4..e8de469 100644 --- a/packages/core/src/plugins.ts +++ b/packages/core/src/plugins.ts @@ -11,60 +11,53 @@ export type DidMethodPlugin = { verifySignature: (did: string, data: Uint8Array, sig: Uint8Array) => Promise } -export type Plugins = { - keys: DidKeyPlugin[] - methods: Record -} - -let plugins: Plugins | null = null +export class Plugins { -export const loadPlugins = (toLoad: Plugins): void => { - plugins = toLoad -} + constructor( + public keys: DidKeyPlugin[], + public methods: Record + ) {} -export const verifyIssuerAlg = (did: string, jwtAlg: string): boolean => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - const didMethod = parseDidMethod(did) - if(didMethod === "key") { - const bytes = parsePrefixedBytes(did) - for (const keyPlugin of plugins.keys) { - if(hasPrefix(bytes, keyPlugin.prefix)) { - return jwtAlg === keyPlugin.jwtAlg + verifyIssuerAlg(did: string, jwtAlg: string): boolean { + const didMethod = parseDidMethod(did) + if(didMethod === "key") { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of this.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return jwtAlg === keyPlugin.jwtAlg + } + } + } else { + const maybePlugin = this.methods[didMethod] + if(maybePlugin) { + return maybePlugin.checkJwtAlg(did, jwtAlg) } } - } else { - const maybePlugin = plugins.methods[didMethod] - if(maybePlugin) { - return maybePlugin.checkJwtAlg(did, jwtAlg) - } + throw new Error(`DID method not supported by plugins: ${did}`) } - throw new Error(`DID method not supported by plugins: ${did}`) -} -export const verifySignature = async (did: string, data: Uint8Array, sig: Uint8Array): Promise => { - if(plugins === null) { - throw new Error("No plugins loaded") - } - const didMethod = parseDidMethod(did) - if(didMethod === "key") { - const bytes = parsePrefixedBytes(did) - for (const keyPlugin of plugins.keys) { - if(hasPrefix(bytes, keyPlugin.prefix)) { - return keyPlugin.verifySignature(did, data, sig) + async verifySignature(did: string, data: Uint8Array, sig: Uint8Array): Promise { + const didMethod = parseDidMethod(did) + if(didMethod === "key") { + const bytes = parsePrefixedBytes(did) + for (const keyPlugin of this.keys) { + if(hasPrefix(bytes, keyPlugin.prefix)) { + return keyPlugin.verifySignature(did, data, sig) + } + } + } else { + const maybePlugin = this.methods[didMethod] + if (maybePlugin) { + return maybePlugin.verifySignature(did, data, sig) } } - } else { - const maybePlugin = plugins.methods[didMethod] - if (maybePlugin) { - return maybePlugin.verifySignature(did, data, sig) - } + throw new Error(`DID method not supported by plugins: ${did}`) } - throw new Error(`DID method not supported by plugins: ${did}`) } -export const hasPrefix = ( +export default Plugins + +const hasPrefix = ( prefixedKey: Uint8Array, prefix: Uint8Array ): boolean => { diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index aa9560d..509fa0e 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -1,4 +1,5 @@ import * as token from "./token.js" +import Plugins from "./plugins.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, rootIssuer } from "./attenuation.js" import { Ucan } from "./types.js" import { Capability } from "./capability/index.js" @@ -11,25 +12,36 @@ export interface IndexByAudience { }> } +export const storeFromTokens = (plugins: Plugins) => + async ( + knownSemantics: DelegationSemantics, + tokens: Iterable | AsyncIterable, + ): Promise => { + const store = new Store(plugins, knownSemantics, {}) + for await (const encodedUcan of tokens) { + const ucan = await token.validate(plugins)(encodedUcan) + await store.add(ucan) + } + return store +} + +export const emptyStore = (plugins: Plugins) => + (knownSemantics: DelegationSemantics) => { + return new Store(plugins, knownSemantics, {}) +} + export class Store { + private plugins: Plugins private index: IndexByAudience private knownSemantics: DelegationSemantics - constructor(knownSemantics: DelegationSemantics, index: IndexByAudience) { + constructor(plugins: Plugins, knownSemantics: DelegationSemantics, index: IndexByAudience) { + this.plugins = plugins this.index = index this.knownSemantics = knownSemantics } - static async fromTokens(knownSemantics: DelegationSemantics, tokens: Iterable | AsyncIterable): Promise { - const store = new Store(knownSemantics, {}) - for await (const encodedUcan of tokens) { - const ucan = await token.validate(encodedUcan) - await store.add(ucan) - } - return store - } - async add(ucan: Ucan): Promise { const audience = ucan.payload.aud const byAudience = this.index[ audience ] ?? [] @@ -40,7 +52,7 @@ export class Store { } const chains = [] - for await (const delegationChain of delegationChains(this.knownSemantics, ucan)) { + for await (const delegationChain of delegationChains(this.plugins)(this.knownSemantics, ucan)) { if (delegationChain instanceof Error) { console.warn(`Delegation chain error while storing UCAN:`, delegationChain) continue diff --git a/packages/core/src/token.ts b/packages/core/src/token.ts index 9bad659..b5420ca 100644 --- a/packages/core/src/token.ts +++ b/packages/core/src/token.ts @@ -3,7 +3,7 @@ import * as uint8arrays from "uint8arrays" // @IMPORT import * as semver from "./semver.js" import * as capability from "./capability/index.js" import * as util from "./util.js" -import * as plugins from "./plugins.js" +import Plugins from "./plugins.js" import { Capability, isCapability, isEncodedCapability } from "./capability/index.js" import { Fact, Keypair, DidableKey } from "./types.js" @@ -43,28 +43,28 @@ const VERSION = { major: 0, minor: 8, patch: 1 } * `prf`, Proofs, nested tokens with equal or greater privileges. * */ -export async function build(params: { - // from/to - issuer: DidableKey - audience: string - - // capabilities - capabilities?: Array - - // time bounds - lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds - expiration?: number - notBefore?: number - - // proofs / other info - facts?: Array - proofs?: Array - addNonce?: boolean -} -): Promise { +export const build = (plugins: Plugins) => + ( params: { + // from/to + issuer: DidableKey + audience: string + + // capabilities + capabilities?: Array + + // time bounds + lifetimeInSeconds?: number // expiration overrides lifetimeInSeconds + expiration?: number + notBefore?: number + + // proofs / other info + facts?: Array + proofs?: Array + addNonce?: boolean + }): Promise => { const keypair = params.issuer const payload = buildPayload({ ...params, issuer: keypair.did() }) - return signWithKeypair(payload, keypair) + return signWithKeypair(plugins)(payload, keypair) } /** @@ -124,11 +124,11 @@ export function buildPayload(params: { /** * Encloses a UCAN payload as to form a finalised UCAN. */ -export async function sign( - payload: UcanPayload, - jwtAlg: string, - signFn: (data: Uint8Array) => Promise -): Promise { +export const sign = (plugins: Plugins) => + async (payload: UcanPayload, + jwtAlg: string, + signFn: (data: Uint8Array) => Promise, + ): Promise => { const header: UcanHeader = { alg: jwtAlg, typ: TYPE, @@ -163,11 +163,11 @@ export async function sign( /** * `sign` with a `Keypair`. */ -export async function signWithKeypair( - payload: UcanPayload, - keypair: Keypair, -): Promise { - return sign( +export const signWithKeypair = (plugins: Plugins) => + ( payload: UcanPayload, + keypair: Keypair, + ): Promise => { + return sign(plugins)( payload, keypair.jwtAlg, data => keypair.sign(data), @@ -318,7 +318,8 @@ export interface ValidateOptions { * @returns the parsed & validated UCAN (one layer) * @throws Error if the UCAN is invalid */ -export async function validate(encodedUcan: string, opts?: Partial): Promise { +export const validate = (plugins: Plugins) => + async (encodedUcan: string, opts?: Partial): Promise => { const { checkIssuer = true, checkSignature = true, checkIsExpired = true, checkIsTooEarly = true } = opts ?? {} const { header, payload } = parse(encodedUcan) @@ -383,15 +384,16 @@ export interface ValidateProofsOptions extends ValidateOptions { * @return an async iterator of the given ucan's proofs parsed & validated, or an `Error` * for each proof that couldn't be validated or parsed. */ -export async function* validateProofs( - ucan: Ucan, - opts?: Partial -): AsyncIterable { + +export const validateProofs = (plugins: Plugins) => + async function* ( ucan: Ucan, + opts?: Partial + ): AsyncIterable { const { checkAddressing = true, checkTimeBoundsSubset = true, checkVersionMonotonic = true } = opts || {} for (const prf of ucan.payload.prf) { try { - const proof = await validate(prf, opts) + const proof = await validate(plugins)(prf, opts) if (checkAddressing && ucan.payload.iss !== proof.payload.aud) { throw new Error(`Invalid Proof: Issuer ${ucan.payload.iss} doesn't match parent's audience ${proof.payload.aud}`) diff --git a/packages/core/src/verify.ts b/packages/core/src/verify.ts index e6a5c1e..8797dfc 100644 --- a/packages/core/src/verify.ts +++ b/packages/core/src/verify.ts @@ -1,4 +1,5 @@ import * as token from "./token.js" +import Plugins from "./plugins.js" import { capabilityCanBeDelegated, DelegationSemantics, DelegationChain, delegationChains, equalCanDelegate, rootIssuer } from "./attenuation.js" import { Capability, isCapability } from "./capability/index.js" import { Fact, Ucan } from "./types.js" @@ -53,7 +54,8 @@ export interface VerifyOptions { * * @throws TypeError if the passed arguments don't match what is expected */ -export async function verify(ucan: string, options: VerifyOptions): Promise> { +export const verify = (plugins: Plugins) => + async (ucan: string, options: VerifyOptions): Promise> => { const { audience, requiredCapabilities } = options const semantics = options.semantics ?? equalCanDelegate const isRevoked = options.isRevoked ?? (async () => false) @@ -86,7 +88,7 @@ export async function verify(ucan: string, options: VerifyOptions): Promise { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { // alice -> bob, bob -> mallory // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) expect(await all(emailCapabilities(ucan))).toEqual([ @@ -43,16 +40,16 @@ describe("attenuation.emailCapabilities", () => { // alice -> bob, bob -> mallory // alice delegates nothing to bob // and bob delegates his email to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) // we implicitly expect the originator to become bob @@ -66,26 +63,26 @@ describe("attenuation.emailCapabilities", () => { // alice -> mallory, bob -> mallory, mallory -> alice // both alice and bob delegate their email access to mallory // mallory then creates a UCAN with capability to send both - const leafUcanAlice = await token.build({ + const leafUcanAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const leafUcanBob = await token.build({ + const leafUcanBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("bob@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: [ emailCapability("alice@email.com"), emailCapability("bob@email.com") ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + proofs: [ ucans.encode(leafUcanAlice), ucans.encode(leafUcanBob) ] }) const chains = await all(emailCapabilities(ucan)) @@ -120,23 +117,23 @@ describe("attenuation.emailCapabilities", () => { const aliceEmail = emailCapability("alice@email.com") - const leafUcanAlice = await token.build({ + const leafUcanAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: [ aliceEmail ] }) - const leafUcanBob = await token.build({ + const leafUcanBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ aliceEmail ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: [ aliceEmail ], - proofs: [ token.encode(leafUcanAlice), token.encode(leafUcanBob) ] + proofs: [ ucans.encode(leafUcanAlice), ucans.encode(leafUcanBob) ] }) expect(await all(emailCapabilities(ucan))).toEqual([ diff --git a/packages/core/tests/builder.test.ts b/packages/core/tests/builder.test.ts index b4bec59..5205750 100644 --- a/packages/core/tests/builder.test.ts +++ b/packages/core/tests/builder.test.ts @@ -1,17 +1,12 @@ -import * as token from "../src/token" -import { Builder } from "../src/builder" import { emailCapability } from "./capability/email" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { EMAIL_SEMANTICS } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { delegationChains } from "../src/attenuation" import { first } from "../src/util" -import { loadTestPlugins } from "./setup.js" +import * as ucans from "./setup" describe("Builder", () => { - - beforeAll(loadTestPlugins) it("builds with a simple example", async () => { const fact1 = { test: true } @@ -21,7 +16,7 @@ describe("Builder", () => { const expiration = Math.floor(Date.now() / 1000) + 30 const notBefore = Math.floor(Date.now() / 1000) - 30 - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withExpiration(expiration) @@ -41,7 +36,7 @@ describe("Builder", () => { }) it("builds with lifetimeInSeconds", async () => { - const payload = Builder.create() + const payload = ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(300) @@ -51,14 +46,14 @@ describe("Builder", () => { }) it("prevents duplicate proofs", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const publicCapability = await first(delegationChains(wnfsPublicSemantics, ucan)) + const publicCapability = await first(ucans.delegationChains(wnfsPublicSemantics, ucan)) if (publicCapability == null) { throw "no capabilities" @@ -68,7 +63,7 @@ describe("Builder", () => { throw publicCapability } - const payload = Builder.create() + const payload = ucans.createBuilder() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) @@ -76,31 +71,31 @@ describe("Builder", () => { .delegateCapability(wnfsCapability("alice.fission.name/public/Documents", "OVERWRITE"), publicCapability, wnfsPublicSemantics) .buildPayload() - expect(payload.prf).toEqual([ token.encode(ucan) ]) + expect(payload.prf).toEqual([ ucans.encode(ucan) ]) }) it("throws when it's not ready to be built", () => { expect(() => { - Builder.create() + ucans.createBuilder() .buildPayload() }).toThrow() // issuer missing expect(() => { - Builder.create() + ucans.createBuilder() .toAudience(bob.did()) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // audience missing expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(alice) .withLifetimeInSeconds(1) .buildPayload() }).toThrow() // expiration missing expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .buildPayload() @@ -108,14 +103,14 @@ describe("Builder", () => { }) it("throws when trying to delegate unproven capabilities", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(emailCapability("alice@email.com")) .build() - const delegationChain = await first(delegationChains(EMAIL_SEMANTICS, ucan)) + const delegationChain = await first(ucans.delegationChains(EMAIL_SEMANTICS, ucan)) if (delegationChain == null) { throw "no capabilities" @@ -126,7 +121,7 @@ describe("Builder", () => { } expect(() => { - Builder.create() + ucans.createBuilder() .issuedBy(bob) .toAudience(mallory.did()) .withLifetimeInSeconds(30) diff --git a/packages/core/tests/capability/email.ts b/packages/core/tests/capability/email.ts index 63ad868..eb09d94 100644 --- a/packages/core/tests/capability/email.ts +++ b/packages/core/tests/capability/email.ts @@ -1,10 +1,10 @@ import { Ucan } from "../../src/types" -import { DelegationSemantics, delegationChains, rootIssuer } from "../../src/attenuation" +import { DelegationSemantics } from "../../src/attenuation" import { Ability } from "../../src/capability/ability" import { Capability } from "../../src/capability" import { SUPERUSER } from "../../src/capability/super-user" import { ResourcePointer } from "../../src/capability/resource-pointer" - +import * as ucans from "../setup" // 🌸 @@ -70,12 +70,12 @@ export function emailCapability(emailAddress: string): Capability { export async function * emailCapabilities(ucan: Ucan): AsyncIterable<{ capability: EmailCapability; rootIssuer: string }> { - for await (const delegationChain of delegationChains(EMAIL_SEMANTICS, ucan)) { + for await (const delegationChain of ucans.delegationChains(EMAIL_SEMANTICS, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } yield { - rootIssuer: rootIssuer(delegationChain), + rootIssuer: ucans.rootIssuer(delegationChain), capability: delegationChain.capability } } diff --git a/packages/core/tests/capability/wnfs.test.ts b/packages/core/tests/capability/wnfs.test.ts index 01888b6..c00fbb8 100644 --- a/packages/core/tests/capability/wnfs.test.ts +++ b/packages/core/tests/capability/wnfs.test.ts @@ -1,17 +1,13 @@ -import * as token from "../../src/token" import { Capability } from "../../src/capability" import { wnfsCapability, wnfsPrivateCapabilities, wnfsPublicCapabilities } from "./wnfs" import { alice, bob, mallory } from "../fixtures" -import { loadTestPlugins } from "../setup.js" import { all } from "../../src/util" - +import * as ucans from "../setup" describe("wnfs public capability", () => { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/public/Apps/", "OVERWRITE") ], @@ -70,8 +66,6 @@ describe("wnfs public capability", () => { describe("wnfs private capability", () => { - beforeAll(loadTestPlugins) - it("works with a simple example", async () => { const { ucan } = await makeSimpleDelegation( [ wnfsCapability("//boris.fission.name/private/abc", "OVERWRITE") ], @@ -195,17 +189,17 @@ describe("wnfs private capability", () => { * The arguments are the capabilities delegated in the first and second arrow, respectively. */ async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabilities: Capability[]) { - const leaf = await token.build({ + const leaf = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: aliceCapabilities }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: bobCapabilities, - proofs: [ token.encode(leaf) ] + proofs: [ ucans.encode(leaf) ] }) return { leaf, ucan } @@ -220,23 +214,23 @@ async function makeSimpleDelegation(aliceCapabilities: Capability[], bobCapabili * the second argument are the capabilities delegated in the last arrow. */ async function makeComplexDelegation(proofs: { alice: Capability[]; bob: Capability[] }, final: Capability[]) { - const leafAlice = await token.build({ + const leafAlice = await ucans.build({ issuer: alice, audience: mallory.did(), capabilities: proofs.alice, }) - const leafBob = await token.build({ + const leafBob = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: proofs.bob, }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: alice.did(), capabilities: final, - proofs: [ token.encode(leafAlice), token.encode(leafBob) ], + proofs: [ ucans.encode(leafAlice), ucans.encode(leafBob) ], }) return { leafAlice, leafBob, ucan } diff --git a/packages/core/tests/capability/wnfs.ts b/packages/core/tests/capability/wnfs.ts index 68b189f..94f9b00 100644 --- a/packages/core/tests/capability/wnfs.ts +++ b/packages/core/tests/capability/wnfs.ts @@ -1,9 +1,10 @@ import { Ability, isAbility } from "../../src/capability/ability" import { Capability } from "../../src/capability" -import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, delegationChains, rootIssuer } from "../../src/attenuation" +import { DelegationSemantics, DelegatedCapability, DelegatedOwnership, rootIssuer } from "../../src/attenuation" import { SUPERUSER } from "../../src/capability/super-user" import { Ucan } from "../../src/types" import { ResourcePointer } from "../../src/capability/resource-pointer" +import * as ucans from "../setup" export const WNFS_ABILITY_LEVELS = { @@ -118,7 +119,7 @@ export const wnfsPublicSemantics: DelegationSemantics = { } export async function * wnfsPublicCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPublicSemantics, ucan)) { + for await (const delegationChain of ucans.delegationChains(wnfsPublicSemantics, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } @@ -189,7 +190,7 @@ const wnfsPrivateSemantics: DelegationSemantics = { } export async function * wnfsPrivateCapabilities(ucan: Ucan) { - for await (const delegationChain of delegationChains(wnfsPrivateSemantics, ucan)) { + for await (const delegationChain of ucans.delegationChains(wnfsPrivateSemantics, ucan)) { if (delegationChain instanceof Error || "ownershipDID" in delegationChain) { continue } diff --git a/packages/core/tests/compatibility.test.ts b/packages/core/tests/compatibility.test.ts index 7030df4..4b23022 100644 --- a/packages/core/tests/compatibility.test.ts +++ b/packages/core/tests/compatibility.test.ts @@ -1,6 +1,5 @@ import * as uint8arrays from "uint8arrays" -import * as token from "../src/token" -import { loadTestPlugins } from "./setup.js" +import * as ucans from "./setup" const oldUcan = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsInVhdiI6IjEuMC4wIn0.eyJhdWQiOiJkaWQ6a2V5OnoxM1YzU29nMllhVUtoZEdDbWd4OVVadVcxbzFTaEZKWWM2RHZHWWU3TlR0Njg5Tm9MMXRrZUd3NGMydGFQa2dBdWloUjh0cmg2azg2VHRVaTNIR2ZrNEh1NDg3czNiTWY4V1MzWjJoU3VwRktiNmhnV3VwajFIRzhheUxRdDFmeWJSdThjTGdBMkNKanFRYm16YzRFOEFKU0tKeDNndVFYa2F4c3R2Um5RRGN1eDFkZzhVR1BRS3haN2lLeUFKWkFuQlcyWXJUM2o0TVQxdTJNcWZQWG9RYU01WFZQMk04clBFN0FCSEREOXdMbWlKdjkzUUFDRFR5MllnZkVSS3JualNWaTdFb3RNOFR3NHg3M1pNUXJEQnZRRW01Zm9tTWZVaTZVSmJUTmVaaldDTUJQYllNbXRKUDZQZlRpaWZYZG0zdXprVFg5NnExUkVFOExodkU2Rzg2cUR0Wjg5MzdFYUdXdXFpNkRHVDFvc2FRMUVnR3NFN3Jac2JSdDFLNnRXeTZpYktlNTlKZWtnTWFlNW9XNER2IiwiZXhwIjozMjc0NDE5MTQyMywiZmN0IjpbXSwiaXNzIjoiZGlkOmtleTp6MTNWM1NvZzJZYVVLaGRHQ21neDlVWnVXMW8xU2hGSlljNkR2R1llN05UdDY4OU5vTDJWanZBR2JXdTFrdmZWUWFyVTVWMXBTUnNjOWFwR2h2dDdaODJmUWg1QWE1NW41Zm0zZGs2SnFuTXczZGU4WG91dWZUV2Z1eHpEVkhrSFNGV0sxOW1SWWI4d205d1VwZkxtUWl4QVdtMndFWVZqU2dENEd6YzhVUDlDSjFxMkY4ZXlpVXViMThGbld4Y2djUWhqdXB3OTNxUlMzWDlXUDViemlSYjE4TTZ0Vm8zaUJ4ZUozb2lrRTNaa3RScEtTZDlkcHU5WWNXZFhoeDZDQmY5NTZ1UXhkTDZoTkppNmVMbmZ1eFY2NEhpZU1rZFVoTTJSeThRd3lqZjQ4ZnZWMVhFVU1zeEM5YWFjNEtCcGJONDJHR3U4UmFkRDU3cjZuMWFOc2IyTjU3RkNOYnFIMXVLdHhNTmVHZHJ2QWlUUGRzVjJBRmppczJvN243ajhMNW41YmJ4TFl4VThNVHB3QVphdFpkSiIsIm5iZiI6MTY0MDE5MTQ1NywicHJmIjoiZXlKaGJHY2lPaUpTVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0lzSW5WaGRpSTZJakV1TUM0d0luMC5leUpoZFdRaU9pSmthV1E2YTJWNU9ub3hNMVl6VTI5bk1sbGhWVXRvWkVkRGJXZDRPVlZhZFZjeGJ6RlRhRVpLV1dNMlJIWkhXV1UzVGxSME5qZzVUbTlNTWxacWRrRkhZbGQxTVd0MlpsWlJZWEpWTlZZeGNGTlNjMk01WVhCSGFIWjBOMW80TW1aUmFEVkJZVFUxYmpWbWJUTmthelpLY1c1TmR6TmtaVGhZYjNWMVpsUlhablY0ZWtSV1NHdElVMFpYU3pFNWJWSlpZamgzYlRsM1ZYQm1URzFSYVhoQlYyMHlkMFZaVm1wVFowUTBSM3BqT0ZWUU9VTktNWEV5UmpobGVXbFZkV0l4T0VadVYzaGpaMk5SYUdwMWNIYzVNM0ZTVXpOWU9WZFFOV0o2YVZKaU1UaE5OblJXYnpOcFFuaGxTak52YVd0Rk0xcHJkRkp3UzFOa09XUndkVGxaWTFka1dHaDROa05DWmprMU5uVlJlR1JNTm1oT1NtazJaVXh1Wm5WNFZqWTBTR2xsVFd0a1ZXaE5NbEo1T0ZGM2VXcG1ORGhtZGxZeFdFVlZUWE40UXpsaFlXTTBTMEp3WWs0ME1rZEhkVGhTWVdSRU5UZHlObTR4WVU1ellqSk9OVGRHUTA1aWNVZ3hkVXQwZUUxT1pVZGtjblpCYVZSUVpITldNa0ZHYW1sek1tODNiamRxT0V3MWJqVmlZbmhNV1hoVk9FMVVjSGRCV21GMFdtUktJaXdpWlhod0lqb3pNamMwTkRFNU1UUXlNeXdpWm1OMElqcGJYU3dpYVhOeklqb2laR2xrT210bGVUcDZNVE5XTTFOdlp6SlpZVlZMYUdSSFEyMW5lRGxWV25WWE1XOHhVMmhHU2xsak5rUjJSMWxsTjA1VWREWTRPVTV2VERKaE5VcE9hMlI0VmpabWJYVm9WbU5SWkRkSVIycHhkRXBRYVc1WlZWQTRRMUp4Y21veVkyVm5hVTFyT1RKUlNIazJRbWRXT1hveVVGQnJWMkZZU0RkUlRsQmlRekphZEUxNWFXbGFjWGRLUkVOd05sZG9VbkZVUzJodVFtaENUbWQ1WkRkTFJuUTNjRkkyTkhCa1ZIQjZUbXRNUlZKNGFHNTNUVUZqZURKcVJGZFlOelpDVG5SS04xUTFWVXQ0TTIxcWRHWTBaak0wWjJwVGRUaHJkME5UY0V0alFuQTRWV2RwU0hkdllVSkhkREUxVkZjNVUzQlNXVkoxYUZKdk1tdEljVFZ5Y0ROTmRFSnFSa2QyVUdZeVRsTlpZbUUzTmxoSGJYcFhlVEZyZUZOelEySTVUSGhqTW5welEwdG1lSEF5ZUd0VVFqWmtPVVJDUlVwVE5sUnhXbFo1WkhKU05GWmFNVkE1ZFhJeGRGcHBlbk5qYWtWd1kzVlViV1EzV0VRemRYSjZVelpqY0RSdU1sZHdSbFZNYjNsMk5tOW5ibWxaZEVOSGFUVlVlbWxEY2pKT1FWRjNWMEZYY25CMldVMWllbVEyVmt0a2RUVmpaekZZUWxoTVZFNWhUQ0lzSW01aVppSTZNVFkwTURFNU1UTTJNeXdpY0hSaklqb2lVMVZRUlZKZlZWTkZVaUlzSW5Kell5STZJaW9pZlEuQ0k5SjlOLVhUZUxQNEM5WTktUl9TcEE1aE80dHdpNUQxNFpTR2lwUzdjNS1jTlJWTVItc285Z0JZMlQzSFNaTHFmQ2xyMEtlQVJicFk2TFBwSm1NRGQ1ODdvck1TVVRnMndqN043eUNVeksxSWhOazhQMkQ3RGVlSHNxQ1lsTVotdXpjMHBSbnFJb3dPTWl6MVFkbHZXaTZ0UHNxZkZVYnl4bEx1bXRHdjV1a1hqc1FZcmYzdko3aU5DMkJibWotMGhTV25wNTNBN01TQTllLWFXVGpLUWEwSkpXVVVhWG5XS19CNjRaa3NyTWRXdW5mVFNuSE9lR2o3MFRuSXhieVcxbFhodk5pcnhIUV90ZVlKZ2xIZTRBbldEQXdUa2dnaVotdkp0WUhsYnVwQkt4S1YtNm9OMTlXS3dUT3U3QnpPX2QyUHAtWVVyY1RSSS1KZ0F2NUpnIiwicHRjIjoiU1VQRVJfVVNFUiIsInJzYyI6IioifQ.CRLB4gBBHhnsbfUhLALiCfo6mHnHSlEUczyZsWhh9TNjv9UxdgvsSWQsehGIT4XR0jQeYZo2OhasEVaF-Gtt_qqtUQIrducKngd0qzmpfjVbicsQPKVdJjlcTwm9dqhLSEtL195El0oucLzYdqMEZMf-txEmyhCd_Q8CaNExhAnwN32v1salnO6vrAw33ZJID7ZaFmBleoGUXBHQwnkv9_m_P6Fh-UGIKjaOuNmBkGXGn-4irm-eXrne2OPZCoPjhiaf0xTONu4ROrQQYykG8CppvsSXeiylOFY11Ot0sdAlHGSlyZk1_chJ3ud17K9S-CKWK9NtqiMNcUdQGFnNQQ" @@ -10,11 +9,8 @@ const header = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedHea const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(encodedPayload, "base64url"))) describe("compatibility", () => { - - beforeAll(loadTestPlugins) - it("allows parsing UCANs with 'uav: 1.0.0' into 'ucv: 0.3.0'", async () => { - const ucan = await token.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) + const ucan = await ucans.validate(oldUcan, { checkIsExpired: false, checkIsTooEarly: false, checkSignature: false }) expect(ucan).toEqual({ header: { alg: header.alg, // "RS256", diff --git a/packages/core/tests/setup.ts b/packages/core/tests/setup.ts index 937a9eb..afdabdf 100644 --- a/packages/core/tests/setup.ts +++ b/packages/core/tests/setup.ts @@ -1,6 +1,17 @@ +import * as core from "../src" import * as plugins from "@ucans/plugins" -import { loadPlugins } from "../src/plugins" -export const loadTestPlugins = () => { - loadPlugins(plugins.defaults) -} \ No newline at end of file +export * from "../src" + +const injected = core.injectPlugins(plugins.defaults) + +export const build = injected.build +export const sign = injected.sign +export const signWithKeypair = injected.signWithKeypair +export const validate = injected.validate +export const validateProofs = injected.validateProofs +export const verify = injected.verify +export const createBuilder = injected.createBuilder +export const storeFromTokens = injected.storeFromTokens +export const emptyStore = injected.emptyStore +export const delegationChains = injected.delegationChains \ No newline at end of file diff --git a/packages/core/tests/store.test.ts b/packages/core/tests/store.test.ts index 0e387a8..8e212a2 100644 --- a/packages/core/tests/store.test.ts +++ b/packages/core/tests/store.test.ts @@ -1,60 +1,53 @@ -import * as token from "../src/token" -import { Store } from "../src/store" -import { Builder } from "../src/builder" import { alice, bob, mallory } from "./fixtures" import { wnfsCapability, wnfsPublicSemantics } from "./capability/wnfs" import { Ucan } from "../src/types" -import { equalCanDelegate } from "../src/attenuation" import { all } from "../src/util" -import { loadTestPlugins } from "./setup.js" - +import * as ucans from "./setup" describe("Store.add", () => { - beforeAll(loadTestPlugins) - it("makes added items retrievable with findByAudience", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const encoded = token.encode(ucan) + const encoded = ucans.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("makes added items retrievable with findByAudience among multiple others", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucan2 = await Builder.create() + const ucan2 = await ucans.createBuilder() .issuedBy(alice) .toAudience(mallory.did()) .withLifetimeInSeconds(30) .build() - const encoded = token.encode(ucan) - const store = await Store.fromTokens(equalCanDelegate, []) + const encoded = ucans.encode(ucan) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan2) await store.add(ucan) - expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => token.encode(find) === encoded))).toEqual(encoded) + expect(encodeOrNull(store.findByAudience(ucan.payload.aud, find => ucans.encode(find) === encoded))).toEqual(encoded) }) it("doesn't add items twice", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(equalCanDelegate, []) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, []) await store.add(ucan) await store.add(ucan) expect(store.getByAudience(ucan.payload.aud)).toEqual([ ucan ]) @@ -65,22 +58,22 @@ describe("Store.add", () => { describe("Store.findByAudience", () => { it("only returns ucans with given audience", async () => { - const ucanBob = await Builder.create() + const ucanBob = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const ucanAlice = await Builder.create() + const ucanAlice = await ucans.createBuilder() .issuedBy(bob) .toAudience(alice.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => token.encode(ucan))) + const store = await ucans.storeFromTokens(ucans.equalCanDelegate, [ ucanBob, ucanAlice ].map(ucan => ucans.encode(ucan))) expect(store.findByAudience(mallory.did(), () => true)).toEqual(null) - expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(token.encode(ucanBob)) - expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(token.encode(ucanAlice)) + expect(encodeOrNull(store.findByAudience(bob.did(), () => true))).toEqual(ucans.encode(ucanBob)) + expect(encodeOrNull(store.findByAudience(alice.did(), () => true))).toEqual(ucans.encode(ucanAlice)) }) }) @@ -88,14 +81,14 @@ describe("Store.findByAudience", () => { describe("Store.findWithCapability", () => { it("finds ucans with more capabilities than the given", async () => { - const ucan = await Builder.create() + const ucan = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucan) ]) + const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucan) ]) const results = all(store.findWithCapability( bob.did(), @@ -107,24 +100,24 @@ describe("Store.findWithCapability", () => { throw "no capability" } - expect(encodeOrNull(results[0]?.ucan)).toEqual(token.encode(ucan)) + expect(encodeOrNull(results[0]?.ucan)).toEqual(ucans.encode(ucan)) }) it("reports an error if the capability can't be found with given audience", async () => { - const ucanBob = await Builder.create() + const ucanBob = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .claimCapability(wnfsCapability("alice.fission.name/public/", "SUPER_USER")) .build() - const ucanAlice = await Builder.create() + const ucanAlice = await ucans.createBuilder() .issuedBy(alice) .toAudience(bob.did()) .withLifetimeInSeconds(30) .build() - const store = await Store.fromTokens(wnfsPublicSemantics, [ token.encode(ucanAlice), token.encode(ucanBob) ]) + const store = await ucans.storeFromTokens(wnfsPublicSemantics, [ ucans.encode(ucanAlice), ucans.encode(ucanBob) ]) const results = all(store.findWithCapability( alice.did(), @@ -141,5 +134,5 @@ function encodeOrNull(ucan: Ucan | null): string { if (ucan == null) { return "null" } - return token.encode(ucan) + return ucans.encode(ucan) } diff --git a/packages/core/tests/token.test.ts b/packages/core/tests/token.test.ts index b3fbb79..9ff27ac 100644 --- a/packages/core/tests/token.test.ts +++ b/packages/core/tests/token.test.ts @@ -1,20 +1,16 @@ import * as uint8arrays from "uint8arrays" - -import * as capability from "../src/capability" -import * as token from "../src/token" -import { loadTestPlugins } from "./setup.js" import { alice, bob } from "./fixtures" - +import * as ucans from "./setup" // COMPOSING describe("token.build", () => { - beforeAll(loadTestPlugins) + // beforeAll(loadTestPlugins) it("can build payloads without nbf", () => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), }) @@ -22,7 +18,7 @@ describe("token.build", () => { }) it("builds payloads that expire in the future", () => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), @@ -33,12 +29,12 @@ describe("token.build", () => { it("throws when enclosing tokens with an invalid key type", async () => { await expect(() => { - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), }) - return token.sign( + return ucans.sign( payload, "rsa", data => alice.sign(data) @@ -55,21 +51,19 @@ describe("token.build", () => { describe("token.encodePayload", () => { - beforeAll(loadTestPlugins) - it("encodes capabilities", () => { const encodedCaps = { with: "wnfs://boris.fission.name/public/photos/", can: "crud/DELETE" } - const payload = token.buildPayload({ + const payload = ucans.buildPayload({ issuer: alice.did(), audience: bob.did(), - capabilities: [ capability.parse(encodedCaps) ] + capabilities: [ ucans.capability.parse(encodedCaps) ] }) - const encoded = token.encodePayload(payload) + const encoded = ucans.encodePayload(payload) const decodedString = uint8arrays.toString( uint8arrays.fromString(encoded, "base64url"), "utf8" @@ -93,10 +87,8 @@ describe("token.encodePayload", () => { describe("token.validate", () => { - beforeAll(loadTestPlugins) - async function makeUcan() { - return await token.build({ + return await ucans.build({ audience: bob.did(), issuer: alice, capabilities: [ @@ -118,7 +110,7 @@ describe("token.validate", () => { it("round-trips with token.build", async () => { const ucan = await makeUcan() - const parsedUcan = await token.validate(token.encode(ucan)) + const parsedUcan = await ucans.validate(ucans.encode(ucan)) expect(parsedUcan).toBeDefined() }) @@ -128,8 +120,8 @@ describe("token.validate", () => { ...ucan.payload, aud: "fakeaudience" } - const badUcan = `${token.encodeHeader(ucan.header)}.${token.encodePayload(badPayload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() + const badUcan = `${ucans.encodeHeader(ucan.header)}.${ucans.encodePayload(badPayload)}.${ucan.signature}` + await expect(() => ucans.validate(badUcan)).rejects.toBeDefined() }) it("throws with a bad issuer", async () => { @@ -138,8 +130,8 @@ describe("token.validate", () => { ...ucan.header, alg: "RS256" } - const badUcan = `${token.encodeHeader(badHeader)}.${token.encodePayload(ucan.payload)}.${ucan.signature}` - await expect(() => token.validate(badUcan)).rejects.toBeDefined() + const badUcan = `${ucans.encodeHeader(badHeader)}.${ucans.encodePayload(ucan.payload)}.${ucan.signature}` + await expect(() => ucans.validate(badUcan)).rejects.toBeDefined() }) it("identifies a ucan that is not active yet", async () => { @@ -152,7 +144,7 @@ describe("token.validate", () => { exp: 2637352774 } } - expect(token.isTooEarly(badUcan)).toBe(true) + expect(ucans.isTooEarly(badUcan)).toBe(true) }) it("identifies a ucan that has become active", async () => { @@ -165,6 +157,6 @@ describe("token.validate", () => { lifetimeInSeonds: 30 } } - expect(token.isTooEarly(activeUcan)).toBe(false) + expect(ucans.isTooEarly(activeUcan)).toBe(false) }) }) \ No newline at end of file diff --git a/packages/core/tests/verify.test.ts b/packages/core/tests/verify.test.ts index 7a1ad09..1764705 100644 --- a/packages/core/tests/verify.test.ts +++ b/packages/core/tests/verify.test.ts @@ -1,37 +1,29 @@ -import * as token from "../src/token" -import * as capability from "../src/capability" -import { SUPERUSER } from "../src/capability/super-user" -import { verify } from "../src/verify" import { emailCapability } from "./capability/email" import { alice, bob, mallory } from "./fixtures" -import { REDELEGATE } from "../src/capability/ability" -import { loadTestPlugins } from "./setup" - +import * as ucans from "./setup" describe("verify", () => { - beforeAll(loadTestPlugins) - async function aliceEmailDelegationExample(expiration?: number) { // alice -> bob, bob -> mallory // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), expiration, capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), expiration, capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - return token.encode(ucan) + return ucans.encode(ucan) } const alicesEmail = { @@ -42,7 +34,7 @@ describe("verify", () => { it("verifies a delegation chain", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -62,12 +54,12 @@ describe("verify", () => { it("rejects an invalid escalation", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ { capability: { ...emailCapability("alice@email.com"), - can: SUPERUSER, + can: ucans.ability.SUPERUSER, }, rootIssuer: alice.did() } ] @@ -79,7 +71,7 @@ describe("verify", () => { it("rejects for an invalid audience", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: bob.did(), requiredCapabilities: [ alicesEmail ] }) @@ -90,7 +82,7 @@ describe("verify", () => { it("rejects for an invalid rootIssuer", async () => { const ucan = await aliceEmailDelegationExample() - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ { capability: emailCapability("alice@email.com"), @@ -108,7 +100,7 @@ describe("verify", () => { // expiry is in the past const ucan = await aliceEmailDelegationExample(nowInSeconds - 60 * 60 * 24) - const result = await verify(ucan, { + const result = await ucans.verify(ucan, { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -120,20 +112,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -150,26 +142,26 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcanA = await token.build({ + const leafUcanA = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("ignore-me@email.com") ] }) - const leafUcanB = await token.build({ + const leafUcanB = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(1, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + capabilities: [ ucans.capability.prf(1, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcanA), ucans.encode(leafUcanB) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -183,26 +175,26 @@ describe("verify", () => { }) it("ignores other proofs not referred to by `prf:0`", async () => { - const leafUcanA = await token.build({ + const leafUcanA = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("ignore-me@email.com") ] }) - const leafUcanB = await token.build({ + const leafUcanB = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("alice@email.com") ] }) - const faultyUcan = await token.build({ + const faultyUcan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(0, REDELEGATE) ], - proofs: [ token.encode(leafUcanA), token.encode(leafUcanB) ] + capabilities: [ ucans.capability.prf(0, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcanA), ucans.encode(leafUcanB) ] }) - const result = await verify(token.encode(faultyUcan), { + const result = await ucans.verify(ucans.encode(faultyUcan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -214,20 +206,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [ emailCapability("invalid@email.com") ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.prf(capability.superUser.SUPERUSER, REDELEGATE) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.prf(ucans.ability.SUPERUSER, ucans.ability.REDELEGATE) ], + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -239,20 +231,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ], }) @@ -268,27 +260,27 @@ describe("verify", () => { // alice -> bob, bob -> mallory, mallory -> "someone" // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.my(capability.superUser.SUPERUSER) ] + capabilities: [ ucans.capability.my(ucans.ability.SUPERUSER) ] }) - const middleUcan = await token.build({ + const middleUcan = await ucans.build({ issuer: bob, audience: mallory.did(), - capabilities: [ capability.as(alice.did(), SUPERUSER) ], - proofs: [ token.encode(leafUcan) ] + capabilities: [ ucans.capability.as(alice.did(), ucans.ability.SUPERUSER) ], + proofs: [ ucans.encode(leafUcan) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: mallory, audience: "did:key:someone", capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(middleUcan) ] + proofs: [ ucans.encode(middleUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: "did:key:someone", requiredCapabilities: [ alicesEmail ] }) @@ -304,20 +296,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), capabilities: [] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) @@ -329,20 +321,20 @@ describe("verify", () => { // alice -> bob // alice delegates access to sending email as her to bob // and bob delegates it further to mallory - const leafUcan = await token.build({ + const leafUcan = await ucans.build({ issuer: alice, audience: bob.did(), - capabilities: [ capability.as(bob.did(), SUPERUSER) ] + capabilities: [ ucans.capability.as(bob.did(), ucans.ability.SUPERUSER) ] }) - const ucan = await token.build({ + const ucan = await ucans.build({ issuer: bob, audience: mallory.did(), capabilities: [ emailCapability("alice@email.com") ], - proofs: [ token.encode(leafUcan) ] + proofs: [ ucans.encode(leafUcan) ] }) - const result = await verify(token.encode(ucan), { + const result = await ucans.verify(ucans.encode(ucan), { audience: mallory.did(), requiredCapabilities: [ alicesEmail ] }) diff --git a/packages/plugins/src/default-plugins.ts b/packages/plugins/src/default-plugins.ts index c5a6000..25d6a96 100644 --- a/packages/plugins/src/default-plugins.ts +++ b/packages/plugins/src/default-plugins.ts @@ -3,7 +3,7 @@ import { ed25519Plugin } from "./ed25519/plugin.js" import { p256Plugin } from "./p256/plugin.js" import { rsaPlugin, rsaOldPlugin } from "./rsa/plugin.js" -export const defaults: Plugins = { - keys: [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], - methods: {}, -} \ No newline at end of file +export const defaults = new Plugins( + [ed25519Plugin, p256Plugin, rsaPlugin, rsaOldPlugin], + {}, +) \ No newline at end of file diff --git a/packages/ucans/src/index.ts b/packages/ucans/src/index.ts index 6f22420..0036bab 100644 --- a/packages/ucans/src/index.ts +++ b/packages/ucans/src/index.ts @@ -1,7 +1,18 @@ -import { loadPlugins } from "@ucans/core" import * as plugins from "@ucans/plugins" - -loadPlugins(plugins.defaults) +import * as core from "@ucans/core" export * from "@ucans/core" -export * from "@ucans/plugins" \ No newline at end of file +export * from "@ucans/plugins" + +const injected = core.injectPlugins(plugins.defaults) + +export const build = injected.build +export const sign = injected.sign +export const signWithKeypair = injected.signWithKeypair +export const validate = injected.validate +export const validateProofs = injected.validateProofs +export const verify = injected.verify +export const createBuilder = injected.createBuilder +export const storeFromTokens = injected.storeFromTokens +export const emptyStore = injected.emptyStore +export const delegationChains = injected.delegationChains \ No newline at end of file