Skip to content

Commit

Permalink
feat: provision provider type is now the DID of the w3s service (#528)
Browse files Browse the repository at this point in the history
* previously the only allowed storage provider on `provider/add` was a
`w3up-alpha` one. I never planned on it being a ucan issuer
* this changes the storage provider id to be the same as the running
service, e.g. `did:web:web3.storage` in prod.
  * This makes the `Provision` type generic on `ServiceId`
  • Loading branch information
gobengo authored Mar 13, 2023
1 parent 8c6dea8 commit 4cd6cd9
Show file tree
Hide file tree
Showing 10 changed files with 79 additions and 57 deletions.
31 changes: 19 additions & 12 deletions packages/access-api/src/models/provisions.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
/* eslint-disable no-void */

/**
* @typedef {import("../types/provisions").ProvisionsStorage} Provisions
* @template {import("@ucanto/interface").DID} ServiceId
* @typedef {import("../types/provisions").ProvisionsStorage<ServiceId>} Provisions
*/

/**
* @param {Array<import("../types/provisions").Provision>} storage
* @returns {Provisions}
* @template {import("@ucanto/interface").DID} ServiceId
* @param {ServiceId} service
* @param {Array<import("../types/provisions").Provision<ServiceId>>} storage
* @returns {Provisions<ServiceId>}
*/
export function createProvisions(storage = []) {
/** @type {Provisions['hasStorageProvider']} */
export function createProvisions(service, storage = []) {
/** @type {Provisions<ServiceId>['hasStorageProvider']} */
const hasStorageProvider = async (consumerId) => {
const hasRowWithSpace = storage.some(({ space }) => space === consumerId)
return hasRowWithSpace
}
/** @type {Provisions['put']} */
/** @type {Provisions<ServiceId>['put']} */
const put = async (item) => {
storage.push(item)
}
/** @type {Provisions['count']} */
/** @type {Provisions<ServiceId>['count']} */
const count = async () => {
return BigInt(storage.length)
}
return {
service,
count,
put,
hasStorageProvider,
Expand All @@ -42,24 +46,27 @@ export function createProvisions(storage = []) {
*/

/**
* @template {import("@ucanto/interface").DID} ServiceId
* Provisions backed by a kyseli database (e.g. sqlite or cloudflare d1)
*/
export class DbProvisions {
/** @type {ProvisionsDatabase} */
#db

/**
* @param {ServiceId} service
* @param {ProvisionsDatabase} db
*/
constructor(db) {
constructor(service, db) {
this.service = service
this.#db = db
this.tableNames = {
provisions: /** @type {const} */ ('provisions'),
}
void (/** @type {Provisions} */ (this))
void (/** @type {Provisions<ServiceId>} */ (this))
}

/** @type {Provisions['count']} */
/** @type {Provisions<ServiceId>['count']} */
async count(...items) {
const { size } = await this.#db
.selectFrom(this.tableNames.provisions)
Expand All @@ -68,7 +75,7 @@ export class DbProvisions {
return BigInt(size)
}

/** @type {Provisions['put']} */
/** @type {Provisions<ServiceId>['put']} */
async put(item) {
/** @type {ProvisionsRow} */
const row = {
Expand Down Expand Up @@ -132,7 +139,7 @@ export class DbProvisions {
)
}

/** @type {Provisions['hasStorageProvider']} */
/** @type {Provisions<ServiceId>['hasStorageProvider']} */
async hasStorageProvider(consumerDid) {
const { provisions } = this.tableNames
const { size } = await this.#db
Expand Down
6 changes: 4 additions & 2 deletions packages/access-api/src/service/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,9 +234,10 @@ export function service(ctx) {
}

/**
* @template {Ucanto.DID} Service
* @param {Ucanto.DID<'key'>} space
* @param {Spaces} spaces
* @param {import('../types/provisions.js').ProvisionsStorage} provisions
* @param {import('../types/provisions.js').ProvisionsStorage<Service>} provisions
* @returns {Promise<boolean>}
*/
async function spaceHasStorageProvider(space, spaces, provisions) {
Expand All @@ -257,8 +258,9 @@ async function spaceHasStorageProviderFromVoucherRedeem(space, spaces) {
}

/**
* @template {Ucanto.DID} Service
* @param {Ucanto.DID<'key'>} space
* @param {import('../types/provisions.js').ProvisionsStorage} provisions
* @param {import('../types/provisions.js').ProvisionsStorage<Service>} provisions
* @returns {Promise<boolean>}
*/
async function spaceHasStorageProviderFromProviderAdd(space, provisions) {
Expand Down
9 changes: 7 additions & 2 deletions packages/access-api/src/service/provider-add.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import * as validator from '@ucanto/validator'
*/

/**
* @template {Ucanto.DID} ServiceId
* @param {object} options
* @param {import('../types/provisions').ProvisionsStorage} options.provisions
* @param {import('../types/provisions').ProvisionsStorage<ServiceId>} options.provisions
* @returns {ProviderAddHandler}
*/
export function createProviderAddHandler(options) {
Expand All @@ -35,10 +36,14 @@ export function createProviderAddHandler(options) {
message: 'Issuer must be a mailto DID',
}
}
if (provider !== options.provisions.service) {
throw new Error(`Provider must be ${options.provisions.service}`)
}
await options.provisions.put({
invocation,
space: consumer,
provider,
// eslint-disable-next-line object-shorthand
provider: /** @type {ServiceId} */ (provider),
account: accountDID,
})
return {}
Expand Down
9 changes: 5 additions & 4 deletions packages/access-api/src/types/provisions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ export type AlphaStorageProvider = 'did:web:web3.storage:providers:w3up-alpha'
/**
* action which results in provisionment of a space consuming a storage provider
*/
export interface Provision {
export interface Provision<ServiceDID extends Ucanto.DID<'web'>> {
invocation: Ucanto.Invocation<ProviderAdd>
space: Ucanto.DID<'key'>
account: Ucanto.DID<'mailto'>
provider: AlphaStorageProvider
provider: AlphaStorageProvider | ServiceDID
}

/**
* stores instances of a storage provider being consumed by a consumer
*/
export interface ProvisionsStorage {
export interface ProvisionsStorage<ServiceDID extends Ucanto.DID<'web'>> {
service: ServiceDID
hasStorageProvider: (consumer: Ucanto.DID<'key'>) => Promise<boolean>
/**
* ensure item is stored
*
* @param item - provision to store
*/
put: (item: Provision) => Promise<void>
put: (item: Provision<ServiceDID>) => Promise<void>

/**
* get number of stored items
Expand Down
5 changes: 3 additions & 2 deletions packages/access-api/src/utils/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ export function getContext(request, env, ctx) {
env: config.ENV,
})
const url = new URL(request.url)
const signer = Signer.parse(config.PRIVATE_KEY).withDID(config.DID)
return {
log,
signer: Signer.parse(config.PRIVATE_KEY).withDID(config.DID),
signer,
config,
url,
models: {
Expand All @@ -78,7 +79,7 @@ export function getContext(request, env, ctx) {
spaces: new Spaces(config.DB),
validations: new Validations(config.VALIDATIONS),
accounts: new Accounts(config.DB),
provisions: new DbProvisions(createD1Database(config.DB)),
provisions: new DbProvisions(signer.did(), createD1Database(config.DB)),
},
email,
uploadApi: createUploadApiConnection({
Expand Down
2 changes: 1 addition & 1 deletion packages/access-api/test/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Miniflare } from 'miniflare'

export interface HelperTestContext {
issuer: Ucanto.Signer<Ucanto.DID<'key'>>
service: Ucanto.Signer<Ucanto.DID>
service: Ucanto.Signer<Ucanto.DID<'web'>>
conn: Ucanto.ConnectionView<Record<string, any>>
mf: Miniflare
}
15 changes: 9 additions & 6 deletions packages/access-api/test/provider-add.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ for (const providerAddHandlerVariant of /** @type {const} */ ([
name: 'handled by createProviderAddHandler',
...(() => {
const spaceWithStorageProvider = principal.ed25519.generate()
const provisions = createProvisions()
const service = {
did: () => /** @type {const} */ ('did:web:web3.storage'),
}
const provisions = createProvisions(service.did())
return {
spaceWithStorageProvider,
provisions,
Expand All @@ -47,7 +50,7 @@ for (const providerAddHandlerVariant of /** @type {const} */ ([
with: `did:mailto:example.com:foo`,
nb: {
consumer: space.did(),
provider: 'did:web:web3.storage:providers:w3up-alpha',
provider: providerAddHandlerVariant.provisions.service,
},
})
.delegate()
Expand Down Expand Up @@ -149,7 +152,7 @@ for (const accessApiVariant of /** @type {const} */ ([
can: 'provider/add',
with: accountDid,
nb: {
provider: 'did:web:web3.storage:providers:w3up-alpha',
provider: service.did(),
consumer: space.did(),
},
},
Expand Down Expand Up @@ -218,7 +221,7 @@ for (const accessApiVariant of /** @type {const} */ ([
can: 'provider/add',
with: accountDid,
nb: {
provider: 'did:web:web3.storage:providers:w3up-alpha',
provider: service.did(),
consumer: space.did(),
},
},
Expand Down Expand Up @@ -291,7 +294,7 @@ export function createEmail(storage) {
* @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} options.service - web3.storage service
* @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 {ValidationEmailSend[]} options.emails
Expand Down Expand Up @@ -375,7 +378,7 @@ async function testAuthorizeClaimProviderAdd(options) {
audience: service,
with: accountA.did(),
nb: {
provider: 'did:web:web3.storage:providers:w3up-alpha',
provider: service.did(),
consumer: space.did(),
},
proofs: claimedDelegations,
Expand Down
6 changes: 3 additions & 3 deletions packages/access-api/test/provisions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { CID } from 'multiformats'

describe('DbProvisions', () => {
it('should persist provisions', async () => {
const { d1 } = await context()
const { d1, service } = await context()
const db = createD1Database(d1)
const storage = new DbProvisions(db)
const storage = new DbProvisions(service.did(), db)
const count = 2 + Math.round(Math.random() * 3)
const spaceA = await principal.ed25519.generate()
const [firstProvision, ...lastProvisions] = await Promise.all(
Expand All @@ -28,7 +28,7 @@ describe('DbProvisions', () => {
},
})
.delegate()
/** @type {import('../src/types/provisions.js').Provision} */
/** @type {import('../src/types/provisions.js').Provision<'did:web:web3.storage:providers:w3up-alpha'>} */
const provision = {
invocation,
space: spaceA.did(),
Expand Down
10 changes: 7 additions & 3 deletions packages/capabilities/src/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
import { capability, DID, literal, struct } from '@ucanto/validator'
import { equalWith, fail, equal } from './utils.js'

export const StorageProvider = literal(
'did:web:web3.storage:providers:w3up-alpha'
export const Web3StorageId = literal('did:web:web3.storage').or(
literal('did:web:staging.web3.storage')
)

export const Provider = Web3StorageId.or(DID.match({ method: 'key' })).or(
DID.match({ method: 'web' })
)

export const AccountDID = DID.match({ method: 'mailto' })
Expand All @@ -24,7 +28,7 @@ export const add = capability({
can: 'provider/add',
with: AccountDID,
nb: struct({
provider: StorageProvider,
provider: Provider,
consumer: DID.match({ method: 'key' }),
}),
derives: (child, parent) => {
Expand Down
Loading

0 comments on commit 4cd6cd9

Please sign in to comment.