Skip to content

Commit

Permalink
fix authorizeContentServe export
Browse files Browse the repository at this point in the history
  • Loading branch information
fforbeck committed Dec 3, 2024
1 parent d85023d commit 8ab9792
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 75 deletions.
142 changes: 74 additions & 68 deletions packages/w3up-client/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,80 +314,13 @@ export class Client extends Base {
}

for (const serviceConnection of options.authorizeGatewayServices) {
await this.authorizeContentServe(space, serviceConnection)
await authorizeContentServe(this, space, serviceConnection)
}
}

return space
}

/**
* Authorizes an audience to serve content from the provided space and record egress events.
* It also publishes the delegation to the content serve service.
* Delegates the following capabilities to the audience:
* - `space/content/serve/*`
*
* @param {import('./types.js').OwnedSpace} space - The space to authorize the audience for.
* @param {import('./types.js').ConnectionView<import('./types.js').ContentServeService>} connection - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation.
* @param {object} [options] - Options for the content serve authorization invocation.
* @param {`did:${string}:${string}`} [options.audience] - The Web DID of the audience (gateway or peer) to authorize.
* @param {number} [options.expiration] - The time at which the delegation expires in seconds from unix epoch.
*/
async authorizeContentServe(space, connection, options = {}) {
const currentSpace = this.currentSpace()
try {
// Set the current space to the space we are authorizing the gateway for, otherwise the delegation will fail
await this.setCurrentSpace(space.did())

/** @type {import('@ucanto/client').Principal<`did:${string}:${string}`>} */
const audience = {
did: () => options.audience ?? connection.id.did(),
}

// Grant the audience the ability to serve content from the space, it includes existing proofs automatically
const delegation = await this.createDelegation(
audience,
[SpaceCapabilities.contentServe.can],
{
expiration: options.expiration ?? Infinity,
}
)

// Publish the delegation to the content serve service
const accessProofs = this.proofs([
{ can: AccessCapabilities.access.can, with: space.did() },
])
const verificationResult = await AccessCapabilities.delegate
.invoke({
issuer: this._agent.issuer,
audience,
with: space.did(),
proofs: [...accessProofs, delegation],
nb: {
delegations: {
[delegation.cid.toString()]: delegation.cid,
},
},
})
.execute(connection)

/* c8 ignore next 8 - can't mock this error */
if (verificationResult.out.error) {
throw new Error(
`failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`,
{
cause: verificationResult.out.error,
}
)
}
return { ok: { ...verificationResult.out.ok, delegation } }
} finally {
if (currentSpace) {
await this.setCurrentSpace(currentSpace.did())
}
}
}

/**
* Share an existing space with another Storacha account via email address delegation.
* Delegates access to the space to the specified email account with the following permissions:
Expand Down Expand Up @@ -619,3 +552,76 @@ export class Client extends Base {
await this.capability.upload.remove(contentCID)
}
}

/**
* Authorizes an audience to serve content from the provided space and record egress events.
* It also publishes the delegation to the content serve service.
* Delegates the following capabilities to the audience:
* - `space/content/serve/*`
*
* @param {Client} client - The w3up client instance.
* @param {import('./types.js').OwnedSpace} space - The space to authorize the audience for.
* @param {import('./types.js').ConnectionView<import('./types.js').ContentServeService>} connection - The connection to the Content Serve Service that will handle, validate, and store the access/delegate UCAN invocation.
* @param {object} [options] - Options for the content serve authorization invocation.
* @param {`did:${string}:${string}`} [options.audience] - The Web DID of the audience (gateway or peer) to authorize.
* @param {number} [options.expiration] - The time at which the delegation expires in seconds from unix epoch.
*/
export const authorizeContentServe = async (
client,
space,
connection,
options = {}
) => {
const currentSpace = client.currentSpace()
try {
// Set the current space to the space we are authorizing the gateway for, otherwise the delegation will fail
await client.setCurrentSpace(space.did())

/** @type {import('@ucanto/client').Principal<`did:${string}:${string}`>} */
const audience = {
did: () => options.audience ?? connection.id.did(),
}

// Grant the audience the ability to serve content from the space, it includes existing proofs automatically
const delegation = await client.createDelegation(
audience,
[SpaceCapabilities.contentServe.can],
{
expiration: options.expiration ?? Infinity,
}
)

// Publish the delegation to the content serve service
const accessProofs = client.proofs([
{ can: AccessCapabilities.access.can, with: space.did() },
])
const verificationResult = await AccessCapabilities.delegate
.invoke({
issuer: client.agent.issuer,
audience,
with: space.did(),
proofs: [...accessProofs, delegation],
nb: {
delegations: {
[delegation.cid.toString()]: delegation.cid,
},
},
})
.execute(connection)

/* c8 ignore next 8 - can't mock this error */
if (verificationResult.out.error) {
throw new Error(
`failed to publish delegation for audience ${options.audience}: ${verificationResult.out.error.message}`,
{
cause: verificationResult.out.error,
}
)
}
return { ok: { ...verificationResult.out.ok, delegation } }
} finally {
if (currentSpace) {
await client.setCurrentSpace(currentSpace.did())
}
}
}
4 changes: 1 addition & 3 deletions packages/w3up-client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Client } from './client.js'
export * as Result from './result.js'
export * as Account from './account.js'
export * from './ability.js'
export { authorizeContentServe } from './client.js'

/**
* Create a new w3up client.
Expand Down Expand Up @@ -44,7 +45,4 @@ export async function create(options = {}) {
return new Client(data, options)
}

export const authorizeContentServe =
Client.prototype.authorizeContentServe.bind(Client.prototype)

export { Client }
5 changes: 3 additions & 2 deletions packages/w3up-client/test/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { randomBytes, randomCAR } from './helpers/random.js'
import { toCAR } from './helpers/car.js'
import { File } from './helpers/shims.js'
import { Client } from '../src/client.js'
import { authorizeContentServe, Client } from '../src/client.js'
import * as Test from './test.js'
import { receiptsEndpoint } from './helpers/utils.js'
import { Absentee } from '@ucanto/principal'
Expand Down Expand Up @@ -595,7 +595,8 @@ export const testClient = {
).connection

// Step 3: Alice authorizes the gateway to serve content from the space
const delegationResult = await aliceClient.authorizeContentServe(
const delegationResult = await authorizeContentServe(
aliceClient,
spaceA,
gatewayConnection
)
Expand Down
6 changes: 4 additions & 2 deletions packages/w3up-client/test/mocks/service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Client from '@ucanto/client'
import * as Server from '@ucanto/server'
import { HTTP } from '@ucanto/transport'
import * as CAR from '@ucanto/transport/car'
import * as AccessCaps from '@web3-storage/capabilities'

Expand All @@ -23,8 +24,9 @@ export function getContentServeMockService(result = { ok: {} }) {
*
* @param {any} id
* @param {any} service
* @param {string | undefined} [url]
*/
export function getConnection(id, service) {
export function getConnection(id, service, url = undefined) {
const server = Server.create({
id: id,
service,
Expand All @@ -34,7 +36,7 @@ export function getConnection(id, service) {
const connection = Client.connect({
id: id,
codec: CAR.outbound,
channel: server,
channel: url ? HTTP.open({ url: new URL(url) }) : server,
})

return { connection }
Expand Down

0 comments on commit 8ab9792

Please sign in to comment.