Skip to content

Commit

Permalink
feat!: restrict store API to CARs (#1415)
Browse files Browse the repository at this point in the history
Restricts store API to CARs per our decision.
  • Loading branch information
Gozala authored Apr 26, 2024
1 parent 4e8e349 commit e53aa87
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 21 deletions.
11 changes: 8 additions & 3 deletions packages/capabilities/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
import { capability, Link, Schema, ok, fail } from '@ucanto/validator'
import { equalLink, equalWith, SpaceDID } from './utils.js'

// @see https://github.com/multiformats/multicodec/blob/master/table.csv#L140
export const code = 0x0202

export const CARLink = Schema.link({ code, version: 1 })

/**
* Capability can only be delegated (but not invoked) allowing audience to
* derived any `store/` prefixed capability for the (memory) space identified
Expand Down Expand Up @@ -46,7 +51,7 @@ export const add = capability({
* for this exact CAR file for agent to PUT or POST it. Attempt to write
* any other content will fail.
*/
link: Link,
link: CARLink,
/**
* Size of the CAR file to be stored. Service will provision write target
* for this exact size. Attempt to write a larger CAR file will fail.
Expand Down Expand Up @@ -94,7 +99,7 @@ export const get = capability({
/**
* shard CID to fetch info about.
*/
link: Link.optional(),
link: CARLink.optional(),
}),
derives: equalLink,
})
Expand All @@ -114,7 +119,7 @@ export const remove = capability({
/**
* CID of the CAR file to be removed from the store.
*/
link: Link,
link: CARLink,
}),
derives: equalLink,
})
Expand Down
4 changes: 2 additions & 2 deletions packages/capabilities/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ export interface StoreAddSuccessResult {
/** DID of the space this item will be stored in. */
with: DID
/** CID of the item. */
link: UnknownLink
link: CARLink
}

export interface StoreAddSuccessDone extends StoreAddSuccessResult {
Expand Down Expand Up @@ -649,7 +649,7 @@ export interface ListResponse<R> {
}

export interface StoreListItem {
link: UnknownLink
link: CARLink
size: number
origin?: UnknownLink
insertedAt: ISO8601Date
Expand Down
26 changes: 15 additions & 11 deletions packages/capabilities/test/capabilities/store.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
} from '../helpers/fixtures.js'
import { createCarCid, validateAuthorization } from '../helpers/utils.js'

const CAR_LINK = parseLink(
'bagbaierale63ypabqutmxxbz3qg2yzcp2xhz2yairorogfptwdd5n4lsz5xa'
)

const top = async () =>
Capability.top.delegate({
issuer: account,
Expand All @@ -35,7 +39,7 @@ describe('store capabilities', function () {
audience: w3,
with: account.did(),
nb: {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
},
proofs: [await top()],
Expand All @@ -55,7 +59,7 @@ describe('store capabilities', function () {
assert.deepEqual(result.ok.audience.did(), w3.did())
assert.equal(result.ok.capability.can, 'store/add')
assert.deepEqual(result.ok.capability.nb, {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
})
})
Expand All @@ -66,7 +70,7 @@ describe('store capabilities', function () {
audience: w3,
with: account.did(),
nb: {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
},
proofs: [await store()],
Expand All @@ -86,7 +90,7 @@ describe('store capabilities', function () {
assert.deepEqual(result.ok.audience.did(), w3.did())
assert.equal(result.ok.capability.can, 'store/add')
assert.deepEqual(result.ok.capability.nb, {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
})
})
Expand All @@ -104,7 +108,7 @@ describe('store capabilities', function () {
audience: w3,
with: account.did(),
nb: {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
},
proofs: [store],
Expand All @@ -124,7 +128,7 @@ describe('store capabilities', function () {
assert.deepEqual(result.ok.audience.did(), w3.did())
assert.equal(result.ok.capability.can, 'store/add')
assert.deepEqual(result.ok.capability.nb, {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 0,
})
})
Expand All @@ -147,7 +151,7 @@ describe('store capabilities', function () {
with: account.did(),
nb: {
size: 1000,
link: parseLink('bafkqaaa'),
link: CAR_LINK,
},
proofs: [delegation],
})
Expand All @@ -166,7 +170,7 @@ describe('store capabilities', function () {
assert.deepEqual(result.ok.audience.did(), w3.did())
assert.equal(result.ok.capability.can, 'store/add')
assert.deepEqual(result.ok.capability.nb, {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 1000,
})
}
Expand All @@ -178,7 +182,7 @@ describe('store capabilities', function () {
with: account.did(),
nb: {
size: 2048,
link: parseLink('bafkqaaa'),
link: CAR_LINK,
},
proofs: [delegation],
})
Expand Down Expand Up @@ -206,7 +210,7 @@ describe('store capabilities', function () {
audience: w3,
with: account.did(),
nb: {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
// @ts-expect-error
size,
},
Expand Down Expand Up @@ -252,7 +256,7 @@ describe('store capabilities', function () {
audience: w3,
with: account.did(),
nb: {
link: parseLink('bafkqaaa'),
link: CAR_LINK,
size: 1024.2,
},
proofs,
Expand Down
2 changes: 1 addition & 1 deletion packages/upload-api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ export type AdminUploadInspectResult = Result<

export interface StoreAddInput {
space: DID
link: UnknownLink
link: CARLink
size: number
origin?: UnknownLink
issuer: DID
Expand Down
8 changes: 6 additions & 2 deletions packages/upload-api/test/handlers/space-info.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ describe('space/info', function () {
proofs: [delegation],
nb: {
size: 1000,
link: parseLink('bafkqaaa'),
link: parseLink(
'bagbaierale63ypabqutmxxbz3qg2yzcp2xhz2yairorogfptwdd5n4lsz5xa'
),
},
}),
],
Expand Down Expand Up @@ -165,7 +167,9 @@ describe('space/info', function () {
with: space.did(),
proofs: [delegation],
nb: {
link: parseLink('bafkqaaa'),
link: parseLink(
'bagbaierale63ypabqutmxxbz3qg2yzcp2xhz2yairorogfptwdd5n4lsz5xa'
),
},
}),
],
Expand Down
55 changes: 55 additions & 0 deletions packages/upload-api/test/handlers/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { createServer, connect } from '../../src/lib.js'
import * as API from '../../src/types.js'
import * as CAR from '@ucanto/transport/car'
import { base64pad } from 'multiformats/bases/base64'
import * as Raw from 'multiformats/codecs/raw'
import { sha256 } from 'multiformats/hashes/sha2'
import * as Link from 'multiformats/link'
import * as StoreCapabilities from '@web3-storage/capabilities/store'
import { invoke } from '@ucanto/core'
import { alice, bob, createSpace, registerSpace } from '../util.js'
import { Absentee } from '@ucanto/principal'
import { provisionProvider } from '../helpers/utils.js'
Expand Down Expand Up @@ -449,6 +453,57 @@ export const test = {
)
},

'store/add fails with non-car link': async (assert, context) => {
const { proof, spaceDid } = await registerSpace(alice, context)
const connection = connect({
id: context.id,
channel: createServer(context),
})

const data = new Uint8Array([11, 22, 34, 44, 55])
/** @type {API.Link<unknown, any>} */
const link = Link.create(Raw.code, await sha256.digest(data))
const size = context.maxUploadSize + 1

// Throws because invocation builder expects CAR link
try {
StoreCapabilities.add.invoke({
issuer: alice,
audience: connection.id,
with: spaceDid,
nb: {
link,
size,
},
proofs: [proof],
})
assert.ok(false, 'should have throw exception')
} catch (error) {
assert.ok(String(error).match(/0x202 codec/))
}

// Going around client validation will still fail because server handler
// expects CAR link.
const invocation = await invoke({
issuer: alice,
audience: connection.id,
capability: {
can: 'store/add',
with: spaceDid,
nb: {
link,
size,
},
},

proofs: [proof],
}).delegate()

const [storeAdd] = await connection.execute(invocation)
assert.ok(storeAdd.out.error)
assert.ok(storeAdd.out.error?.message.match('0x202 codec'))
},

'store/remove fails for non existent link': async (assert, context) => {
const { proof, spaceDid } = await registerSpace(alice, context)
const connection = connect({
Expand Down
2 changes: 1 addition & 1 deletion packages/upload-client/src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export async function add(
* has the capability to perform the action.
*
* The issuer needs the `store/get` delegated capability.
* @param {import('multiformats/link').UnknownLink} link CID of stored CAR file.
* @param {import('multiformats/link').Link<unknown, CAR.codec.code>} link CID of stored CAR file.
* @param {import('./types.js').RequestOptions} [options]
* @returns {Promise<import('./types.js').StoreGetSuccess>}
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/w3up-client/src/capability/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class StoreClient extends Base {
/**
* Get details of a stored item.
*
* @param {import('../types.js').UnknownLink} link - Root data CID for the DAG that was stored.
* @param {import('../types.js').CARLink} link - Root data CID for the DAG that was stored.
* @param {import('../types.js').RequestOptions} [options]
*/
async get(link, options = {}) {
Expand Down

0 comments on commit e53aa87

Please sign in to comment.