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

433 test authorize #534

Closed
wants to merge 11 commits into from
8 changes: 8 additions & 0 deletions packages/access-api/src/types/ucanto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Ucanto from '@ucanto/interface'

export type ServiceInvoke<
Service extends Record<string, any>,
InvocationCapabilities extends Ucanto.Capability = Ucanto.Capability
> = <Capability extends InvocationCapabilities>(
invocation: Ucanto.ServiceInvocation<Capability>
) => Promise<Ucanto.InferServiceInvocationReturn<Capability, Service>>
37 changes: 37 additions & 0 deletions packages/access-api/test/access-client-agent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { context } from './helpers/context.js'
import { createTesterFromContext } from './helpers/ucanto-test-utils.js'
import * as principal from '@ucanto/principal'
import { Agent as AccessAgent } from '@web3-storage/access/agent'
import * as assert from 'assert'

for (const accessApiVariant of /** @type {const} */ ([
{
name: 'using access-api in miniflare',
...(() => {
const spaceWithStorageProvider = principal.ed25519.generate()
return {
spaceWithStorageProvider,
...createTesterFromContext(context, {
registerSpaces: [spaceWithStorageProvider],
}),
}
})(),
},
])) {
describe(`access-client-agent ${accessApiVariant.name}`, () => {
it('can createSpace', async () => {
const accessAgent = await AccessAgent.create(undefined, {
connection: await accessApiVariant.connection,
})
const space = await accessAgent.createSpace('test-add')
const delegations = accessAgent.proofs()
assert.equal(space.proof.cid, delegations[0].cid)
})
})
it('can authorize', async () => {
const accessAgent = await AccessAgent.create(undefined, {
connection: await accessApiVariant.connection,
})
await accessAgent.authorize('[email protected]')
})
}
101 changes: 5 additions & 96 deletions packages/access-api/test/access-delegate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ import * as Ucanto from '@ucanto/interface'
import * as ucanto from '@ucanto/core'
import * as principal from '@ucanto/principal'
import { createAccessDelegateHandler } from '../src/service/access-delegate.js'
import { createAccessClaimHandler } from '../src/service/access-claim.js'
import {
createDelegationsStorage,
toDelegationsDict,
} from '../src/service/delegations.js'
import { createD1Database } from '../src/utils/d1.js'
import { DbDelegationsStorage } from '../src/models/delegations.js'
import * as delegationsResponse from '../src/utils/delegations-response.js'
import {
assertNotError,
Expand Down Expand Up @@ -122,70 +119,6 @@ async function testCanAccessDelegateWithRegisteredSpace(options) {
* Run the same tests against several variants of ( access/delegate | access/claim ) handlers.
*/
for (const variant of /** @type {const} */ ([
{
name: 'handled by createAccessHandler using array createDelegationsStorage',
...(() => {
const spaceWithStorageProvider = principal.ed25519.generate()
return {
spaceWithStorageProvider,
...createTesterFromHandler(
(() => {
const delegations = createDelegationsStorage()
return () => {
return createAccessHandler(
createAccessDelegateHandler({
delegations,
hasStorageProvider: async (uri) => {
return (
uri ===
(await spaceWithStorageProvider.then((s) => s.did()))
)
},
}),
createAccessClaimHandler({ delegations })
)
}
})()
),
}
})(),
},
{
name: 'handled by createAccessHandler using DbDelegationsStorage',
...(() => {
const spaceWithStorageProvider = principal.ed25519.generate()
const d1 = context().then((ctx) => ctx.d1)
const database = d1.then((d1) => createD1Database(d1))
const delegations = database.then((db) => new DbDelegationsStorage(db))
return {
spaceWithStorageProvider,
...createTesterFromHandler(
(() => {
return () => {
/**
* @type {InvocationHandler<AccessDelegate | AccessClaim, unknown, { error: true }>}
*/
return async (invocation) => {
const handle = createAccessHandler(
createAccessDelegateHandler({
delegations: await delegations,
hasStorageProvider: async (uri) => {
return (
uri ===
(await spaceWithStorageProvider.then((s) => s.did()))
)
},
}),
createAccessClaimHandler({ delegations: await delegations })
)
return handle(invocation)
}
}
})()
),
}
})(),
},
{
name: 'handled by access-api in miniflare',
...(() => {
Expand Down Expand Up @@ -443,7 +376,11 @@ async function testInsufficientStorageIfNoStorageProvider(options) {
*/

/**
* @param {InvocationHandler<AccessDelegate | AccessClaim, unknown, { error: true }>} invoke
* @typedef {import('@web3-storage/access/types').Service} AccessService
*/

/**
* @param {import('../src/types/ucanto.js').ServiceInvoke<AccessService, AccessDelegate | AccessClaim>} invoke
* @param {Ucanto.Signer<Ucanto.DID<'key'>>} issuer
* @param {Ucanto.Verifier<Ucanto.DID>} audience
*/
Expand Down Expand Up @@ -520,31 +457,3 @@ async function setupDelegateThenClaim(invoker, audience) {
* @typedef {Ucanto.InferInvokedCapability<typeof Access.claim>} AccessClaim
* @typedef {Ucanto.InferInvokedCapability<typeof Access.delegate>} AccessDelegate
*/

/**
* @param {import('../src/service/access-delegate.js').AccessDelegateHandler} handleDelegate
* @param {InvocationHandler<AccessClaim, unknown, { error: true }>} handleClaim
* @returns {InvocationHandler<AccessDelegate | AccessClaim, unknown, { error: true }>}
*/
function createAccessHandler(handleDelegate, handleClaim) {
return async (invocation) => {
const can = invocation.capabilities[0].can
switch (can) {
case 'access/claim': {
return handleClaim(
/** @type {Ucanto.Invocation<AccessClaim>} */ (invocation)
)
}
case 'access/delegate': {
return handleDelegate(
/** @type {Ucanto.Invocation<AccessDelegate>} */ (invocation)
)
}
default: {
// eslint-disable-next-line no-void
void (/** @type {never} */ (can))
}
}
throw new Error(`unexpected can=${can}`)
}
}
4 changes: 2 additions & 2 deletions packages/access-api/test/helpers/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type * as Ucanto from '@ucanto/interface'
import type { Miniflare } from 'miniflare'

export interface HelperTestContext {
export interface HelperTestContext<Service extends Record<string, any>> {
issuer: Ucanto.Signer<Ucanto.DID<'key'>>
service: Ucanto.Signer<Ucanto.DID<'web'>>
conn: Ucanto.ConnectionView<Record<string, any>>
conn: Ucanto.ConnectionView<Service>
mf: Miniflare
}
12 changes: 7 additions & 5 deletions packages/access-api/test/helpers/ucanto-test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import * as assert from 'assert'
import * as principal from '@ucanto/principal'

/**
* @typedef {import('./types').HelperTestContext} HelperTestContext
* @typedef {import('@web3-storage/access/types').Service} AccessService
*/

/**
* Tests using context from "./helpers/context.js", which sets up a testable access-api inside miniflare.
*
* @param {() => Promise<HelperTestContext>} createContext
* @template {Record<string,any>} Service
* @param {() => Promise<import('./types').HelperTestContext<Service>>} createContext
* @param {object} [options]
* @param {Iterable<Promise<Ucanto.Principal>>} options.registerSpaces - spaces to register in access-api. Some access-api functionality on a space requires it to be registered.
*/
Expand All @@ -19,19 +20,20 @@ export function createTesterFromContext(createContext, options) {
await registerSpaces(options?.registerSpaces ?? [], ctx)
return ctx
})
/** @type {Promise<Ucanto.ConnectionView<Service>>} */
const connection = context.then((ctx) => ctx.conn)
const issuer = context.then(({ issuer }) => issuer)
const audience = context.then(({ service }) => service)
const miniflare = context.then(({ mf }) => mf)
/**
* @template {Ucanto.Capability} Capability
* @param {Ucanto.Invocation<Capability>} invocation
* @type {import('../../src/types/ucanto').ServiceInvoke<Service>}
*/
const invoke = async (invocation) => {
const { conn } = await context
const [result] = await conn.execute(invocation)
return result
}
return { issuer, audience, invoke, miniflare }
return { issuer, audience, invoke, miniflare, context, connection }
}

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/access-api/test/provider-add.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,20 @@ export function createEmail(storage) {
return email
}

/**
* @typedef {import('@web3-storage/capabilities/types').AccessClaim} AccessClaim
* @typedef {import('@web3-storage/capabilities/types').AccessAuthorize} AccessAuthorize
* @typedef {import('@web3-storage/capabilities/types').ProviderAdd} ProviderAdd
*/

/**
* @param {object} options
* @param {Ucanto.Signer<Ucanto.DID<'key'>>} options.deviceA
* @param {Ucanto.Signer<Ucanto.DID<'key'>>} options.space
* @param {Ucanto.Principal<Ucanto.DID<'mailto'>>} options.accountA
* @param {Ucanto.Principal<Ucanto.DID<'web'>>} options.service - web3.storage service
* @param {import('miniflare').Miniflare} options.miniflare
* @param {(invocation: Ucanto.Invocation<Ucanto.Capability>) => Promise<unknown>} options.invoke
* @param {import('../src/types/ucanto.js').ServiceInvoke<import('./helpers/ucanto-test-utils.js').AccessService, AccessClaim|AccessAuthorize|ProviderAdd>} options.invoke
* @param {ValidationEmailSend[]} options.emails
*/
async function testAuthorizeClaimProviderAdd(options) {
Expand Down Expand Up @@ -393,6 +399,7 @@ async function testAuthorizeClaimProviderAdd(options) {
assertNotError(providerAddAsAccountResult)

const spaceStorageResult = await options.invoke(
// @ts-ignore - not in service type because only enabled while testing
await ucanto
.invoke({
issuer: space,
Expand Down
16 changes: 16 additions & 0 deletions packages/access-client/src/agent-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Signer } from '@ucanto/principal'
import { Signer as EdSigner } from '@ucanto/principal/ed25519'
import { importDAG } from '@ucanto/core/delegation'
import { CID } from 'multiformats'
import { Access } from '@web3-storage/capabilities'
import { isExpired } from './delegations.js'

/** @typedef {import('./types').AgentDataModel} AgentDataModel */

Expand Down Expand Up @@ -143,4 +145,18 @@ export class AgentData {
this.delegations.delete(cid.toString())
await this.#save(this.export())
}

/**
* The current session proof.
*/
sessionProof() {
for (const { delegation } of this.delegations.values()) {
const cap = delegation.capabilities.find(
(c) =>
// @ts-expect-error "key" does not exist in object, unless it's a session capability
c.can === Access.session.can && c.nb?.key === this.principal.did()
)
if (cap && !isExpired(delegation)) return delegation
}
}
}
Loading