diff --git a/packages/upload-api/src/blob/accept.js b/packages/upload-api/src/blob/accept.js index 94bdd3f5f..12e1b2be5 100644 --- a/packages/upload-api/src/blob/accept.js +++ b/packages/upload-api/src/blob/accept.js @@ -54,6 +54,12 @@ export function blobAcceptProvider(context) { expiration: Infinity, }) + // Publish this claim to the content claims service + const pubClaim = await publishLocationClaim(context, { digest, location: createUrl.ok }) + if (pubClaim.error) { + return pubClaim + } + // Create result object /** @type {API.OkBuilder} */ const result = Server.ok({ @@ -137,3 +143,23 @@ export const poll = async (context, receipt) => { return { ok: {} } } + +/** + * @param {API.ClaimsClientContext} ctx + * @param {{ digest: API.MultihashDigest, location: API.URI }} params + */ +const publishLocationClaim = async (ctx, { digest, location }) => { + const { invocationConfig, connection } = ctx.claimsService + const { issuer, audience, with: resource, proofs } = invocationConfig + const res = await Assert.location + .invoke({ + issuer, + audience, + with: resource, + nb: { content: { digest: digest.bytes }, location: [location] }, + expiration: Infinity, + proofs, + }) + .execute(connection) + return res.out +} diff --git a/packages/upload-api/src/types.ts b/packages/upload-api/src/types.ts index c56d9a893..9e3fecb52 100644 --- a/packages/upload-api/src/types.ts +++ b/packages/upload-api/src/types.ts @@ -203,7 +203,7 @@ import { StorageGetError } from './types/storage.js' import { AllocationsStorage, BlobsStorage, BlobAddInput } from './types/blob.js' export type { AllocationsStorage, BlobsStorage, BlobAddInput } import { IPNIService, IndexServiceContext } from './types/index.js' -import { ClaimsClientConfig } from './types/content-claims.js' +import { ClaimsClientConfig, ClaimsClientContext } from './types/content-claims.js' import { Claim } from '@web3-storage/content-claims/client/api' export type { IndexServiceContext, @@ -378,7 +378,7 @@ export type BlobServiceContext = SpaceServiceContext & { getServiceConnection: () => ConnectionView } -export type W3ServiceContext = SpaceServiceContext & { +export type W3ServiceContext = SpaceServiceContext & ClaimsClientContext & { /** * Service signer */ diff --git a/packages/upload-api/test/handlers/blob.js b/packages/upload-api/test/handlers/blob.js index 10595937e..530fba4e1 100644 --- a/packages/upload-api/test/handlers/blob.js +++ b/packages/upload-api/test/handlers/blob.js @@ -9,6 +9,7 @@ import { alice, registerSpace } from '../util.js' import { BlobSizeOutsideOfSupportedRangeName } from '../../src/blob/lib.js' import { createConcludeInvocation } from '../../src/ucan/conclude.js' import { parseBlobAddReceiptNext } from '../helpers/blob.js' +import * as Result from '../helpers/result.js' /** * @type {API.Tests} @@ -426,6 +427,67 @@ export const test = { 'accept was not successful' ) }, + 'blob/accept publishes location claim to claims service': async ( + assert, + context + ) => { + const { proof, spaceDid } = await registerSpace(alice, context) + + const data = new Uint8Array([11, 22, 34, 44, 55]) + const digest = await sha256.digest(data) + const size = data.byteLength + + const service = createServer(context) + const connection = connect({ id: context.id, channel: service }) + + const blobAddInvocation = BlobCapabilities.add.invoke({ + issuer: alice, + audience: context.id, + with: spaceDid, + nb: { blob: { digest: digest.bytes, size } }, + proofs: [proof], + }) + const receipt = await blobAddInvocation.execute(connection) + assert.ok(receipt.out.ok) + + const nextTasks = parseBlobAddReceiptNext(receipt) + const { address } = Result.unwrap(nextTasks.allocate.receipt.out) + assert.ok(address) + + if (address) { + const httpPut = await fetch(address.url, { + method: 'PUT', + mode: 'cors', + body: data, + headers: address.headers, + }) + assert.equal(httpPut.status, 200, await httpPut.text()) + } + + const keys = + /** @type {API.SignerArchive} */ + (nextTasks.put.task.facts[0]['keys']) + const blobProvider = ed25519.from(keys) + const httpPutReceipt = await Receipt.issue({ + issuer: blobProvider, + ran: nextTasks.put.task.link(), + result: { ok: {} }, + }) + const httpPutConcludeInvocation = createConcludeInvocation( + alice, + context.id, + httpPutReceipt + ) + const ucanConclude = await httpPutConcludeInvocation.execute(connection) + assert.ok(ucanConclude.out.ok) + + // ensure a location claim exists for the content root + const claims = Result.unwrap(await context.claimsService.read(digest)) + assert.ok( + claims.some(c => c.type === 'assert/location'), + 'did not find location claim' + ) + }, 'blob/add fails when a blob with size bigger than maximum size is added': async (assert, context) => { const { proof, spaceDid } = await registerSpace(alice, context)