-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement
plan/get
capability (#1005)
In console, we need a way to tell if a user has a subscription. Implement the `plan/get` capability from #959 to enable that. Includes the core capability definitions plus `upload-api` handlers and `access-client` helper functions.
- Loading branch information
Showing
20 changed files
with
457 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { capability, DID, ok } from '@ucanto/validator' | ||
import { equalWith, and } from './utils.js' | ||
|
||
export const AccountDID = DID.match({ method: 'mailto' }) | ||
|
||
/** | ||
* Capability can be invoked by an account to get information about | ||
* the plan it is currently signed up for. | ||
*/ | ||
export const get = capability({ | ||
can: 'plan/get', | ||
with: AccountDID, | ||
derives: (child, parent) => { | ||
return and(equalWith(child, parent)) || ok({}) | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import assert from 'assert' | ||
import { access } from '@ucanto/validator' | ||
import { Verifier } from '@ucanto/principal/ed25519' | ||
import * as Plan from '../../src/plan.js' | ||
import { service, alice, bob } from '../helpers/fixtures.js' | ||
import { createAuthorization, validateAuthorization } from '../helpers/utils.js' | ||
|
||
describe('plan/get', function () { | ||
const agent = alice | ||
const account = 'did:mailto:mallory.com:mallory' | ||
it('can invoke as an account', async function () { | ||
const auth = Plan.get.invoke({ | ||
issuer: agent, | ||
audience: service, | ||
with: account, | ||
proofs: await createAuthorization({ agent, service, account }), | ||
}) | ||
const result = await access(await auth.delegate(), { | ||
capability: Plan.get, | ||
principal: Verifier, | ||
authority: service, | ||
validateAuthorization, | ||
}) | ||
if (result.error) { | ||
assert.fail(`error in self issue: ${result.error.message}`) | ||
} else { | ||
assert.deepEqual(result.ok.audience.did(), service.did()) | ||
assert.equal(result.ok.capability.can, 'plan/get') | ||
assert.deepEqual(result.ok.capability.with, account) | ||
} | ||
}) | ||
|
||
it('fails without account delegation', async function () { | ||
const agent = alice | ||
const auth = Plan.get.invoke({ | ||
issuer: agent, | ||
audience: service, | ||
with: account, | ||
}) | ||
|
||
const result = await access(await auth.delegate(), { | ||
capability: Plan.get, | ||
principal: Verifier, | ||
authority: service, | ||
validateAuthorization, | ||
}) | ||
|
||
assert.equal(result.error?.message.includes('not authorized'), true) | ||
}) | ||
|
||
it('fails when invoked by a different agent', async function () { | ||
const auth = Plan.get.invoke({ | ||
issuer: bob, | ||
audience: service, | ||
with: account, | ||
proofs: await createAuthorization({ agent, service, account }), | ||
}) | ||
|
||
const result = await access(await auth.delegate(), { | ||
capability: Plan.get, | ||
principal: Verifier, | ||
authority: service, | ||
validateAuthorization, | ||
}) | ||
assert.equal(result.error?.message.includes('not authorized'), true) | ||
}) | ||
|
||
it('can delegate plan/get', async function () { | ||
const invocation = Plan.get.invoke({ | ||
issuer: bob, | ||
audience: service, | ||
with: account, | ||
proofs: [ | ||
await Plan.get.delegate({ | ||
issuer: agent, | ||
audience: bob, | ||
with: account, | ||
proofs: await createAuthorization({ agent, service, account }), | ||
}), | ||
], | ||
}) | ||
const result = await access(await invocation.delegate(), { | ||
capability: Plan.get, | ||
principal: Verifier, | ||
authority: service, | ||
validateAuthorization, | ||
}) | ||
if (result.error) { | ||
assert.fail(`error in self issue: ${result.error.message}`) | ||
} else { | ||
assert.deepEqual(result.ok.audience.did(), service.did()) | ||
assert.equal(result.ok.capability.can, 'plan/get') | ||
assert.deepEqual(result.ok.capability.with, account) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import * as Types from './types.js' | ||
import * as Get from './plan/get.js' | ||
|
||
/** | ||
* @param {Types.PlanServiceContext} context | ||
*/ | ||
export const createService = (context) => ({ | ||
get: Get.provide(context), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as API from '../types.js' | ||
import * as Provider from '@ucanto/server' | ||
import { Plan } from '@web3-storage/capabilities' | ||
|
||
/** | ||
* @param {API.PlanServiceContext} context | ||
*/ | ||
export const provide = (context) => | ||
Provider.provide(Plan.get, (input) => get(input, context)) | ||
|
||
/** | ||
* @param {API.Input<Plan.get>} input | ||
* @param {API.PlanServiceContext} context | ||
* @returns {Promise<API.Result<API.PlanGetSuccess, API.PlanGetFailure>>} | ||
*/ | ||
const get = async ({ capability }, context) => { | ||
return context.plansStorage.get(capability.with) | ||
} |
Oops, something went wrong.