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 2, 2024
1 parent d85023d commit 990df46
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 73 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

0 comments on commit 990df46

Please sign in to comment.