-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
feat: handle access/delegate invocations without error #427
Merged
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
7ecd857
basic access/delegate cap
gobengo e1823f4
simple happy test for access/delegate
gobengo 62770fa
add access/delegate example from spec including a non-empty nb.delega…
gobengo cbf3f89
access/delegate test various ways of allocating keys in the nb.delega…
gobengo 235e46e
accesss/delegate derives checks .nb.delegations claim is subset of pr…
gobengo 8922c0b
remove ts-ignore from access/delegate derives
gobengo 2579081
access/delegate capParser has derives from '*' and 'access/*'
gobengo 1e256f1
jsdoc access/delegate
gobengo 4f1f293
simplify types in access/delegate caps
gobengo a6e52bf
test access/delegate malformed
gobengo d03e081
remove unnecessary extra set creations in capabilities access delegat…
gobengo edeee6b
some access/delegate capability parser mocha tests are now split into…
gobengo 2991b71
start access/delegate invocation handler in access-api (and access-cl…
gobengo c960fb4
fix access-api lint
gobengo 5955c4c
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo 02581a2
test access/delegate handler extracts proven delegations from invocat…
gobengo 09d6a67
mv access-delegate handler to access-api/src/service/access-delegate.js
gobengo 89830a8
access-delegate.test.js tests invoking handler directly as well as in…
gobengo cce52da
start delegate + claim test
gobengo eb6ede0
more delegate then claim test
gobengo 9651d68
testCanDelegateThenClaim against accessHandler sans miniflare
gobengo 71592fb
basic createAccessClaimHandler and testCanDelegateThenClaim
gobengo d263013
lint
gobengo faa9378
start DbDelegationsStorage backed by d1
gobengo 3e14b07
test sql DelegationsStorage incl removing delegations.aud -> accounts…
gobengo 469c2e3
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo ea5f48b
testDelegateThenClaim using both in-memory and sqlite DelegationsStorage
gobengo e083496
access-api wont handle access/delegate in ENV=production until furthe…
gobengo c769fae
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo efac95a
wip encoding delegations in AccessClaim result
gobengo 701dba4
serialize access/claim claimed delegations as car bytes
gobengo 7ac458f
access-api no longer deps on multiformats
gobengo c23a522
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo 0506f97
access/delegate handler denies service when with uri is not a space w…
gobengo 66025de
test access/delegate errors InsufficientStorage if space is not regis…
gobengo 6186822
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo d911d71
remove misleading comment
gobengo de6c168
models/delegations doesnt have noop Symbol.iterator
gobengo 421c46d
document methods on DelegationsStorage
gobengo 79afa8f
accessClaimProvider throws if env is production
gobengo d8b6dae
capabilities package has AccessDelegateSuccess and AccessDelegateFailure
gobengo 3289aa3
AccessClaim and AccessDelegate success/fail types come from @web3-sto…
gobengo 9cb6deb
comment
gobengo c62a6ac
add missing await
gobengo 052a195
fix bug in DbDelegationsStorage when push is called with empty array,…
gobengo 0174324
add comment about metadata EMPTY
gobengo 218e411
adjust DelegationsStorage import name to Delegations in access-api bi…
gobengo 022dc21
clean up DelegationsStorage interface per feedback
gobengo e3268ad
disable some tests that fail without DbDelegationsStorage asyncIterator
gobengo 457b22e
feat: `access/claim` only claims delegations with the correct audienc…
gobengo c579889
Merge branch 'main' into 1675728220-handle-access-delegate
gobengo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
packages/access-api/migrations/0005_drop_delegations_audience_to_accounts_did_fk.sql
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,30 @@ | ||
-- Migration number: 0005 2023-02-09T23:48:40.469Z | ||
|
||
/* | ||
goal: remove the foreign key constraint on delegations.audience -> accounts.did. | ||
We want to be able to store delegations whose audience is not an account did. | ||
|
||
sqlite doesn't support `alter table drop constraint`. | ||
So here we will: | ||
* create delegations_new table without the constraint | ||
* insert all from delegations -> delegations_new | ||
* rename delegations_new -> delegations | ||
*/ | ||
|
||
CREATE TABLE | ||
IF NOT EXISTS delegations_new ( | ||
cid TEXT NOT NULL PRIMARY KEY, | ||
bytes BLOB NOT NULL, | ||
audience TEXT NOT NULL, | ||
issuer TEXT NOT NULL, | ||
expiration TEXT, | ||
inserted_at TEXT NOT NULL DEFAULT (strftime ('%Y-%m-%dT%H:%M:%fZ', 'now')), | ||
updated_at TEXT NOT NULL DEFAULT (strftime ('%Y-%m-%dT%H:%M:%fZ', 'now')), | ||
UNIQUE (cid) | ||
); | ||
|
||
INSERT INTO delegations_new (cid, bytes, audience, issuer, expiration, inserted_at, updated_at) | ||
SELECT cid, bytes, audience, issuer, expiration, inserted_at, updated_at FROM delegations; | ||
|
||
DROP TABLE delegations; | ||
ALTER TABLE delegations_new RENAME TO delegations; |
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,135 @@ | ||
import * as Ucanto from '@ucanto/interface' | ||
import { | ||
delegationsToBytes, | ||
bytesToDelegations, | ||
} from '@web3-storage/access/encoding' | ||
|
||
/** | ||
* @typedef {import('@web3-storage/access/src/types').DelegationTable} DelegationRow | ||
* @typedef {Omit<DelegationRow, 'inserted_at'|'updated_at'|'expires_at'>} DelegationRowUpdate | ||
*/ | ||
|
||
/** | ||
* @typedef Tables | ||
* @property {DelegationRow} delegations | ||
*/ | ||
|
||
/** | ||
* @typedef {import("../types/database").Database<Tables>} DelegationsDatabase | ||
*/ | ||
|
||
/** | ||
* DelegationsStorage that persists using SQL. | ||
* * should work with cloudflare D1 | ||
*/ | ||
export class DbDelegationsStorage { | ||
/** @type {DelegationsDatabase} */ | ||
#db | ||
|
||
/** | ||
* @param {DelegationsDatabase} db | ||
*/ | ||
constructor(db) { | ||
this.#db = db | ||
// eslint-disable-next-line no-void | ||
void ( | ||
/** @type {import('../types/delegations').DelegationsStorage} */ (this) | ||
) | ||
} | ||
|
||
async count() { | ||
const { size } = await this.#db | ||
.selectFrom('delegations') | ||
.select((e) => e.fn.count('cid').as('size')) | ||
.executeTakeFirstOrThrow() | ||
return BigInt(size) | ||
} | ||
|
||
/** | ||
* @param {import('../types/delegations').Query} query | ||
*/ | ||
async *find(query) { | ||
for await (const row of await selectByAudience(this.#db, query.audience)) { | ||
yield rowToDelegation(row) | ||
} | ||
} | ||
|
||
/** | ||
* store items | ||
* | ||
* @param {Array<Ucanto.Delegation>} delegations | ||
* @returns {Promise<void>} | ||
*/ | ||
async putMany(...delegations) { | ||
if (delegations.length === 0) { | ||
return | ||
} | ||
const values = delegations.map((d) => createDelegationRowUpdate(d)) | ||
await this.#db | ||
.insertInto('delegations') | ||
.values(values) | ||
.onConflict((oc) => oc.column('cid').doNothing()) | ||
.executeTakeFirst() | ||
} | ||
|
||
/** | ||
* iterate through all stored items | ||
* | ||
* @returns {AsyncIterableIterator<Ucanto.Delegation>} | ||
*/ | ||
async *[Symbol.asyncIterator]() { | ||
if (!this.#db.canStream) { | ||
throw Object.assign( | ||
new Error( | ||
`cannot create asyncIterator because the underlying database does not support streaming` | ||
), | ||
{ name: 'NotImplementedError' } | ||
) | ||
} | ||
for await (const row of this.#db | ||
.selectFrom('delegations') | ||
.select(['bytes']) | ||
.stream()) { | ||
yield rowToDelegation(row) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param {Pick<DelegationRow, 'bytes'>} row | ||
* @returns {Ucanto.Delegation} | ||
*/ | ||
function rowToDelegation(row) { | ||
const delegations = bytesToDelegations(row.bytes) | ||
if (delegations.length !== 1) { | ||
throw new Error( | ||
`unexpected number of delegations from bytes: ${delegations.length}` | ||
) | ||
} | ||
return delegations[0] | ||
} | ||
|
||
/** | ||
* @param {Ucanto.Delegation} d | ||
* @returns {DelegationRowUpdate} | ||
*/ | ||
function createDelegationRowUpdate(d) { | ||
return { | ||
cid: d.cid.toV1().toString(), | ||
audience: d.audience.did(), | ||
issuer: d.issuer.did(), | ||
bytes: delegationsToBytes([d]), | ||
} | ||
} | ||
|
||
/** | ||
* @param {DelegationsDatabase} db | ||
* @param {Ucanto.DID<'key'>} audience | ||
*/ | ||
async function selectByAudience(db, audience) { | ||
return await db | ||
.selectFrom('delegations') | ||
.selectAll() | ||
.where('delegations.audience', '=', audience) | ||
.execute() | ||
} |
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,54 @@ | ||
import * as Server from '@ucanto/server' | ||
import { claim } from '@web3-storage/capabilities/access' | ||
import * as Ucanto from '@ucanto/interface' | ||
import * as validator from '@ucanto/validator' | ||
import * as delegationsResponse from '../utils/delegations-response.js' | ||
import { collect } from 'streaming-iterables' | ||
|
||
/** | ||
* @typedef {import('@web3-storage/capabilities/types').AccessClaimSuccess} AccessClaimSuccess | ||
* @typedef {import('@web3-storage/capabilities/types').AccessClaimFailure} AccessClaimFailure | ||
*/ | ||
|
||
/** | ||
* @callback AccessClaimHandler | ||
* @param {Ucanto.Invocation<import('@web3-storage/capabilities/types').AccessClaim>} invocation | ||
* @returns {Promise<Ucanto.Result<AccessClaimSuccess, AccessClaimFailure>>} | ||
*/ | ||
|
||
/** | ||
* @param {object} ctx | ||
* @param {import('../types/delegations').DelegationsStorage} ctx.delegations | ||
* @param {Pick<import('../bindings.js').RouteContext['config'], 'ENV'>} ctx.config | ||
*/ | ||
export function accessClaimProvider(ctx) { | ||
const handleClaimInvocation = createAccessClaimHandler(ctx) | ||
return Server.provide(claim, async ({ invocation }) => { | ||
// disable until hardened in test/staging | ||
if (ctx.config.ENV === 'production') { | ||
throw new Error(`acccess/claim invocation handling is not enabled`) | ||
} | ||
return handleClaimInvocation(invocation) | ||
}) | ||
} | ||
|
||
/** | ||
* @param {object} options | ||
* @param {import('../types/delegations').DelegationsStorage} options.delegations | ||
* @returns {AccessClaimHandler} | ||
*/ | ||
export function createAccessClaimHandler({ delegations }) { | ||
/** @type {AccessClaimHandler} */ | ||
return async (invocation) => { | ||
const claimedAudience = invocation.capabilities[0].with | ||
if (validator.DID.match({ method: 'mailto' }).is(claimedAudience)) { | ||
throw new Error(`did:mailto not supported`) | ||
} | ||
const claimed = await collect( | ||
delegations.find({ audience: claimedAudience }) | ||
) | ||
return { | ||
delegations: delegationsResponse.encode(claimed), | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "EMPTY" and not just ""?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#447
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a comment explaining the magic
EMPTY
0174324