diff --git a/.github/release-please-config.json b/.github/release-please-config.json index 2ece5dad8..4bf673c1f 100644 --- a/.github/release-please-config.json +++ b/.github/release-please-config.json @@ -3,7 +3,6 @@ "bootstrap-sha": "c918ffc59eafa01fbc63d5df11ba621cc1888c64", "packages": { "packages/access-client": {}, - "packages/access-api": {}, "packages/capabilities": {}, "packages/did-mailto": {}, "packages/upload-api": {}, diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index e7d691c0b..10fe7b1bd 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1,5 +1,4 @@ { - "packages/access-api": "7.0.0", "packages/access-client": "13.0.2", "packages/capabilities": "5.0.1", "packages/upload-api": "3.0.0", diff --git a/.github/workflows/access-api.yml b/.github/workflows/access-api.yml deleted file mode 100644 index 0dd807e89..000000000 --- a/.github/workflows/access-api.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Access API -env: - CI: true - FORCE_COLOR: 1 -on: - push: - branches: - - main - paths: - - 'packages/access-api/**' - - '.github/workflows/access-api.yml' - - 'pnpm-lock.yaml' - - '.env.tpl' - pull_request: - paths: - - 'packages/access-api/**' - - '.github/workflows/access-api.yml' - - 'pnpm-lock.yaml' - - '.env.tpl' -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2.2.3 - with: - version: 7 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'pnpm' - - run: pnpm install - - run: pnpm -r --filter @web3-storage/access-api run lint - - run: pnpm -r --filter @web3-storage/access-api run test - - run: pnpm -r --filter @web3-storage/access-api exec depcheck - deploy-staging: - needs: test - if: github.head_ref == 'release-please--branches--main--components--access-api' - uses: './.github/workflows/reusable-deploy-api.yml' - with: - environment: staging - environment_url: https://w3access-staging.protocol-labs.workers.dev - secrets: inherit diff --git a/.github/workflows/access-client.yml b/.github/workflows/access-client.yml index 0f967498b..d028e97f6 100644 --- a/.github/workflows/access-client.yml +++ b/.github/workflows/access-client.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/capabilities.yml b/.github/workflows/capabilities.yml index 91f253bb2..a4620c1e5 100644 --- a/.github/workflows/capabilities.yml +++ b/.github/workflows/capabilities.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/did-mailto.yml b/.github/workflows/did-mailto.yml index b82464464..6cd5acadb 100644 --- a/.github/workflows/did-mailto.yml +++ b/.github/workflows/did-mailto.yml @@ -25,7 +25,7 @@ jobs: - name: Install uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - name: Setup uses: actions/setup-node@v3 with: diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 126f10c1b..1daeef15a 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -6,9 +6,8 @@ on: description: 'Package to deploy' required: true type: choice - default: 'access-api' + default: 'docs' options: - - access-api - upload-client - access-client - docs @@ -22,12 +21,6 @@ on: - staging - dev jobs: - deploy-access-api: - if: github.event.inputs.package == 'access-api' - uses: './.github/workflows/reusable-deploy-api.yml' - with: - environment: ${{ github.event.inputs.environment }} - secrets: inherit deploy-access-client: runs-on: ubuntu-latest if: github.event.inputs.package == 'access-client' @@ -35,7 +28,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 @@ -57,7 +50,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3d7ee303..b3059ea0b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,14 +21,6 @@ jobs: manifest-file: .github/release-please-manifest.json default-branch: main release-type: node - access-api: - needs: release - if: contains(fromJson(needs.release.outputs.paths_released), 'packages/access-api') - uses: './.github/workflows/reusable-deploy-api.yml' - with: - environment: production - environment_url: https://access.web3.storage - secrets: inherit npm: needs: release if: | @@ -43,7 +35,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/reusable-deploy-api.yml b/.github/workflows/reusable-deploy-api.yml deleted file mode 100644 index dce2116ac..000000000 --- a/.github/workflows/reusable-deploy-api.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Reusable Deploy API -on: - workflow_call: - inputs: - environment: - required: true - type: string - environment_url: - description: URL of the deployed environment. If provided, this will be added to the GitHub environment. - required: false - type: string - secrets: - CF_TOKEN: - required: true - POSTMARK_TOKEN: - required: true - PRIVATE_KEY: - required: true - SENTRY_DSN: - required: true - SENTRY_UPLOAD: - required: true - SENTRY_TOKEN: - required: true - LOGTAIL_TOKEN: - required: true - UCAN_LOG_BASIC_AUTH: - required: true - -jobs: - deploy-api: - runs-on: ubuntu-latest - environment: - name: ${{ inputs.environment }} - url: ${{ inputs.environment_url }} - steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2.2.3 - with: - version: 7 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'pnpm' - - run: pnpm --filter '@web3-storage/access-api...' install - # get release name that will be used for sentry - - id: set-release-name - env: - ENV: ${{ inputs.environment }} - run: | - echo "release_name=$(node packages/access-api/scripts/release.js)" >> "$GITHUB_OUTPUT" - - run: echo "release name is ${{ steps.set-release-name.outputs.release_name }}" - # write release info to ./src so it can be imported at runtime (e.g. used by toucan-js for `opts.release`) - # be sure to keep this before wrangler-action bundles+deploys - - name: write release info to src - working-directory: packages/access-api/ - env: - ENV: ${{ vars.ENV }} - run: | - node scripts/release.js esm > src/utils/release.build.js - # Apply D1 Migrations - - run: pnpm -r --filter @web3-storage/access-api exec wrangler d1 migrations apply __D1_BETA__ --env ${{ inputs.environment }} - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CF_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} - # Publish worker to cloudflare - - uses: cloudflare/wrangler-action@2.0.0 - with: - # preCommands: git config --global --add safe.directory "*" - apiToken: ${{ secrets.CF_TOKEN }} - command: publish --env "${{ vars.ENV }}" --outdir=dist - workingDirectory: 'packages/access-api' - environment: ${{ inputs.environment }} - secrets: | - POSTMARK_TOKEN - PRIVATE_KEY - SENTRY_DSN - LOGTAIL_TOKEN - UCAN_LOG_BASIC_AUTH - env: - POSTMARK_TOKEN: ${{ secrets.POSTMARK_TOKEN }} - PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - LOGTAIL_TOKEN: ${{ secrets.LOGTAIL_TOKEN }} - UCAN_LOG_BASIC_AUTH: ${{ secrets.UCAN_LOG_BASIC_AUTH }} - - name: create sentry release - working-directory: packages/access-api - run: | - ls -alh ./dist - # create sentry release - pnpm exec sentry-cli releases new "$RELEASE_NAME" --finalize - # associate wrangler-built src+map to sentry release - pnpm exec sentry-cli releases files "$RELEASE_NAME" upload-sourcemaps ./dist - env: - ENV: ${{ vars.ENV }} - RELEASE_NAME: ${{ steps.set-release-name.outputs.release_name }} - SENTRY_ORG: ${{ vars.SENTRY_ORG }} - SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_TOKEN }} diff --git a/.github/workflows/reusable-deploy-docs.yml b/.github/workflows/reusable-deploy-docs.yml index 6f3e9ae43..631f50951 100644 --- a/.github/workflows/reusable-deploy-docs.yml +++ b/.github/workflows/reusable-deploy-docs.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/upload-api.yml b/.github/workflows/upload-api.yml index 4c5b54036..4126a84a7 100644 --- a/.github/workflows/upload-api.yml +++ b/.github/workflows/upload-api.yml @@ -29,7 +29,7 @@ jobs: - name: Install uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - name: Setup uses: actions/setup-node@v3 @@ -55,7 +55,7 @@ jobs: - name: Install uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - name: Setup uses: actions/setup-node@v3 diff --git a/.github/workflows/upload-client.yml b/.github/workflows/upload-client.yml index 3e0043a6a..63cb8fee0 100644 --- a/.github/workflows/upload-client.yml +++ b/.github/workflows/upload-client.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.github/workflows/w3up-client.yml b/.github/workflows/w3up-client.yml index b816bcf4d..9b8c9166e 100644 --- a/.github/workflows/w3up-client.yml +++ b/.github/workflows/w3up-client.yml @@ -25,7 +25,7 @@ jobs: - name: Install uses: pnpm/action-setup@v2.2.3 with: - version: 7 + version: 8 - name: Setup uses: actions/setup-node@v3 with: diff --git a/packages/access-api/.gitignore b/packages/access-api/.gitignore deleted file mode 100644 index babca1bb1..000000000 --- a/packages/access-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.dev.vars diff --git a/packages/access-api/CHANGELOG.md b/packages/access-api/CHANGELOG.md deleted file mode 100644 index 87fd75664..000000000 --- a/packages/access-api/CHANGELOG.md +++ /dev/null @@ -1,539 +0,0 @@ -# Changelog - -## [7.0.0](https://github.com/web3-storage/w3up/compare/access-api-v6.1.1...access-api-v7.0.0) (2023-05-04) - - -### ⚠ BREAKING CHANGES - -* upgrade to ucanto7.x.x ([#774](https://github.com/web3-storage/w3up/issues/774)) - -### Features - -* upgrade to ucanto7.x.x ([#774](https://github.com/web3-storage/w3up/issues/774)) ([0cc6e66](https://github.com/web3-storage/w3up/commit/0cc6e66a80476e05c75bea94c1bee9bd12cbacf5)) - -## [6.1.1](https://github.com/web3-storage/w3up/compare/access-api-v6.1.0...access-api-v6.1.1) (2023-04-17) - - -### Bug Fixes - -* add POSTMARK_TOKEN back to list of required env ([#761](https://github.com/web3-storage/w3up/issues/761)) ([5018095](https://github.com/web3-storage/w3up/commit/50180958b6c1e05159aca879f89ef40547ed608e)) - -## [6.1.0](https://github.com/web3-storage/w3up/compare/access-api-v6.0.0...access-api-v6.1.0) (2023-04-14) - - -### Features - -* change access-api wrangler.toml to be no_bundle=false. use wrangler bundling ([#739](https://github.com/web3-storage/w3up/issues/739)) ([d659516](https://github.com/web3-storage/w3up/commit/d6595169bba974428899a8fd18f596718aaa5e41)) - - -### Bug Fixes - -* access-api cli.js build does sourcemap=external instead of true ([#731](https://github.com/web3-storage/w3up/issues/731)) ([d1a35b7](https://github.com/web3-storage/w3up/commit/d1a35b78030732f9c87f86a8f458678b0064abdd)) -* add /.debug/error route ([#732](https://github.com/web3-storage/w3up/issues/732)) ([2ca5de8](https://github.com/web3-storage/w3up/commit/2ca5de8cff7be64cb75b48b9e5ff9188732ec99d)) -* change access-api src/index.js 'export default' to be an immediately-defined object in hopes stack traces will work better ([#733](https://github.com/web3-storage/w3up/issues/733)) ([a509313](https://github.com/web3-storage/w3up/commit/a509313cb7bafb6eaf5f1ff925e67d1303188504)), closes [#623](https://github.com/web3-storage/w3up/issues/623) -* toucan-js in access-api does not use RewriteFrame integration ([#729](https://github.com/web3-storage/w3up/issues/729)) ([eeb90e6](https://github.com/web3-storage/w3up/commit/eeb90e6bf8e162356e19852db224236462d5d55f)) - -## [6.0.0](https://github.com/web3-storage/w3up/compare/access-api-v5.2.1...access-api-v6.0.0) (2023-04-06) - - -### ⚠ BREAKING CHANGES - -* add did mailto package, replacing `createDidMailtoFromEmail` ([#722](https://github.com/web3-storage/w3up/issues/722)) - -### Features - -* add did mailto package, replacing `createDidMailtoFromEmail` ([#722](https://github.com/web3-storage/w3up/issues/722)) ([b48c256](https://github.com/web3-storage/w3up/commit/b48c256bfa57dda5d1762f77e41af1ecddf35846)) - - -### Bug Fixes - -* bug where did:mailto with pct-encoded email parts was not decoded correctly ([#719](https://github.com/web3-storage/w3up/issues/719)) ([7003dd2](https://github.com/web3-storage/w3up/commit/7003dd2a159c58872cbecaad1fd4a863e4a7f34b)) -* upgrade toucan-js and @web3-storage/worker-utils ([#728](https://github.com/web3-storage/w3up/issues/728)) ([976c77c](https://github.com/web3-storage/w3up/commit/976c77c260ced0e8545dad8599796cb32c6eabe2)) - -## [5.2.1](https://github.com/web3-storage/w3protocol/compare/access-api-v5.2.0...access-api-v5.2.1) (2023-03-24) - - -### Bug Fixes - -* center email validation layout ([#645](https://github.com/web3-storage/w3protocol/issues/645)) ([82a665c](https://github.com/web3-storage/w3protocol/commit/82a665cf34f36918e06e147778a4690ca2a8dbea)) -* release changes to email validation confirmation page ([#643](https://github.com/web3-storage/w3protocol/issues/643)) ([b2de5c6](https://github.com/web3-storage/w3protocol/commit/b2de5c62ad224eacc94dd018c73bd34b8e5c142a)) - -## [5.2.0](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.5...access-api-v5.2.0) (2023-03-24) - - -### Features - -* update access confirmation email ([#636](https://github.com/web3-storage/w3protocol/issues/636)) ([15c9503](https://github.com/web3-storage/w3protocol/commit/15c95030f46507affd7e68b7ef6b7449ba6e3ac8)) - - -### Bug Fixes - -* image color, typos in access email confirmation ([#638](https://github.com/web3-storage/w3protocol/issues/638)) ([539cb33](https://github.com/web3-storage/w3protocol/commit/539cb331ab9b66f8e2703f9ef8518518a7bcb299)) - -## [5.1.5](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.4...access-api-v5.1.5) (2023-03-23) - - -### Bug Fixes - -* do not include token in an error message ([#634](https://github.com/web3-storage/w3protocol/issues/634)) ([8043976](https://github.com/web3-storage/w3protocol/commit/8043976213fa1da252a31c4000876a22bcae3651)) - -## [5.1.4](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.3...access-api-v5.1.4) (2023-03-23) - - -### Bug Fixes - -* access-api utils/context no longer throws in staging and prod if UCAN configs are falsy ([#630](https://github.com/web3-storage/w3protocol/issues/630)) ([f7d1df3](https://github.com/web3-storage/w3protocol/commit/f7d1df3d6389d31ce245039b0663615956b24f85)) -* adjust how access-api uses config to build a ucanLog ([#627](https://github.com/web3-storage/w3protocol/issues/627)) ([30f6f77](https://github.com/web3-storage/w3protocol/commit/30f6f770fbabf60d2ab1b85dc985f057d3dbdbd7)) -* bug with how loadConfig reads UCAN_LOG_BASIC_AUTH ([#629](https://github.com/web3-storage/w3protocol/issues/629)) ([1580953](https://github.com/web3-storage/w3protocol/commit/15809534e839c5f89613aa1437b453b9976fd536)) - -## [5.1.3](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.2...access-api-v5.1.3) (2023-03-23) - - -### Bug Fixes - -* rm access/claim disabling in prod ([#624](https://github.com/web3-storage/w3protocol/issues/624)) ([5bfb7c9](https://github.com/web3-storage/w3protocol/commit/5bfb7c9aa9e2b9cd8a866e9d13f11bb941a1f521)) - -## [5.1.2](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.1...access-api-v5.1.2) (2023-03-23) - - -### Bug Fixes - -* ucan log connect only when url and token ([#607](https://github.com/web3-storage/w3protocol/issues/607)) ([5eef6cc](https://github.com/web3-storage/w3protocol/commit/5eef6ccb60ab46a7c2888c10d8f03420d7c81623)) - -## [5.1.1](https://github.com/web3-storage/w3protocol/compare/access-api-v5.1.0...access-api-v5.1.1) (2023-03-23) - - -### Bug Fixes - -* ucan log route needs path ([#618](https://github.com/web3-storage/w3protocol/issues/618)) ([789554b](https://github.com/web3-storage/w3protocol/commit/789554b31a37d85c0678670d9861a35cf4a186c9)) - -## [5.1.0](https://github.com/web3-storage/w3protocol/compare/access-api-v5.0.0...access-api-v5.1.0) (2023-03-23) - - -### Features - -* remove checks that disable some access/ functionality in prod ([#609](https://github.com/web3-storage/w3protocol/issues/609)) ([b970942](https://github.com/web3-storage/w3protocol/commit/b97094287301e83222f71508d6c0e3176357742f)) - - -### Bug Fixes - -* release please config ([#615](https://github.com/web3-storage/w3protocol/issues/615)) ([50eec61](https://github.com/web3-storage/w3protocol/commit/50eec61d95ed430761f8eb3f99323a2dfc504e8a)) -* set env for ucan log url ([#611](https://github.com/web3-storage/w3protocol/issues/611)) ([529aa55](https://github.com/web3-storage/w3protocol/commit/529aa558e72af751936127f13a7e5be20fb9ad17)) - -## [5.0.0](https://github.com/web3-storage/w3protocol/compare/access-api-v5.0.0...access-api-v5.0.0) (2023-03-23) - - -### Features - -* remove checks that disable some access/ functionality in prod ([#609](https://github.com/web3-storage/w3protocol/issues/609)) ([b970942](https://github.com/web3-storage/w3protocol/commit/b97094287301e83222f71508d6c0e3176357742f)) - - -### Bug Fixes - -* set env for ucan log url ([#611](https://github.com/web3-storage/w3protocol/issues/611)) ([529aa55](https://github.com/web3-storage/w3protocol/commit/529aa558e72af751936127f13a7e5be20fb9ad17)) - -## [5.0.0](https://github.com/web3-storage/w3protocol/compare/access-api-v5.0.0...access-api-v5.0.0) (2023-03-23) - - -### Features - -* remove checks that disable some access/ functionality in prod ([#609](https://github.com/web3-storage/w3protocol/issues/609)) ([b970942](https://github.com/web3-storage/w3protocol/commit/b97094287301e83222f71508d6c0e3176357742f)) - -## [5.0.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.11.0...access-api-v5.0.0) (2023-03-23) - - -### ⚠ BREAKING CHANGES - -* implement new account-based multi-device flow ([#433](https://github.com/web3-storage/w3protocol/issues/433)) -* upgrade capabilities to latest ucanto ([#463](https://github.com/web3-storage/w3protocol/issues/463)) - -### Features - -* access-api handles provider/add invocations ([#462](https://github.com/web3-storage/w3protocol/issues/462)) ([5fb56f7](https://github.com/web3-storage/w3protocol/commit/5fb56f794529f3d4de2b4597c47503002767fabb)) -* access-api serves access/claim invocations ([#456](https://github.com/web3-storage/w3protocol/issues/456)) ([baacf35](https://github.com/web3-storage/w3protocol/commit/baacf3553ce8de0ca75e0815da849cca65ec880a)) -* access/authorize confirmation email click results in a delegation back to the issuer did:key so that access/claim works ([#460](https://github.com/web3-storage/w3protocol/issues/460)) ([a466a7d](https://github.com/web3-storage/w3protocol/commit/a466a7de4e5b3d9c727307dd781f5e5c9b7cdf0a)) -* allow multiple providers ([#595](https://github.com/web3-storage/w3protocol/issues/595)) ([96c5a2e](https://github.com/web3-storage/w3protocol/commit/96c5a2e5a03432d8483d044ae10f6f3e03c2710c)) -* define `access/confirm` handler and use it in ucanto-test-utils registerSpaces + validate-email handler ([#530](https://github.com/web3-storage/w3protocol/issues/530)) ([b1bbc90](https://github.com/web3-storage/w3protocol/commit/b1bbc907c96cfc7788f50fb0c154d9b54894e03e)) -* handle access/delegate invocations without error ([#427](https://github.com/web3-storage/w3protocol/issues/427)) ([4f0bd1c](https://github.com/web3-storage/w3protocol/commit/4f0bd1c1cd3cfb1c848892ad418c6d7b2197045a)) -* if POST /validate-email?mode=authorize catches error w/ too big qr code ([#516](https://github.com/web3-storage/w3protocol/issues/516)) ([d0df525](https://github.com/web3-storage/w3protocol/commit/d0df52554bf2d80a6be11da631ad41a5ce7675ca)) -* implement new account-based multi-device flow ([#433](https://github.com/web3-storage/w3protocol/issues/433)) ([1ddc6a0](https://github.com/web3-storage/w3protocol/commit/1ddc6a0c53f8cdb6837a315d8aaf567100dfb8d7)) -* includes proofs chains in the delegated authorization chain ([#467](https://github.com/web3-storage/w3protocol/issues/467)) ([5144293](https://github.com/web3-storage/w3protocol/commit/5144293deabd9d5380448ae288e089ef2652def7)) -* move access-api delegation bytes out of d1 and into r2 ([#578](https://github.com/web3-storage/w3protocol/issues/578)) ([4510c9a](https://github.com/web3-storage/w3protocol/commit/4510c9a8c4389ca975d66f5c9592bce326bbc1c7)) -* move validation flow to a Durable Object to make it ⏩ fast ⏩ fast ⏩ fast ⏩ ([#449](https://github.com/web3-storage/w3protocol/issues/449)) ([02d7552](https://github.com/web3-storage/w3protocol/commit/02d75522b1ed794d267880e5f8a4fc3964066992)) -* provision provider type is now the DID of the w3s service ([#528](https://github.com/web3-storage/w3protocol/issues/528)) ([6a72855](https://github.com/web3-storage/w3protocol/commit/6a72855db4d6e838e9948f3951fdb5ef324eec95)) -* space/info will not error for spaces that have had storage provider added via provider/add ([#510](https://github.com/web3-storage/w3protocol/issues/510)) ([ea4e872](https://github.com/web3-storage/w3protocol/commit/ea4e872475c74165b08016c210e65b4062a2ffb6)) -* upgrade capabilities to latest ucanto ([#463](https://github.com/web3-storage/w3protocol/issues/463)) ([2d786ee](https://github.com/web3-storage/w3protocol/commit/2d786ee81a6eb72c4782548ad3e3796fe3947fa5)) -* upgrade to new ucanto ([#498](https://github.com/web3-storage/w3protocol/issues/498)) ([dcb41a9](https://github.com/web3-storage/w3protocol/commit/dcb41a9981c2b6bebbdbd29debcad9f510383680)) -* write invocations and receipts into ucan log ([#592](https://github.com/web3-storage/w3protocol/issues/592)) ([754bf52](https://github.com/web3-storage/w3protocol/commit/754bf522530c183807e10baca12574f2f1f058f6)) - - -### Bug Fixes - -* access/delegate checks hasStorageProvider(space) in a way that provider/add allows access/delegate ([#483](https://github.com/web3-storage/w3protocol/issues/483)) ([f4c640d](https://github.com/web3-storage/w3protocol/commit/f4c640d4127d511fb55a9160f22e1c9042b35994)) -* adjust migration 0005 to keep delegations table but create new used delegations_v2 ([#469](https://github.com/web3-storage/w3protocol/issues/469)) ([a205ad1](https://github.com/web3-storage/w3protocol/commit/a205ad13f425675e8aac2ec4f06294d0134c8112)) -* adjust migration 0005 to not do a drop table and instead rename delegations -> delegations_old and create a new delegations ([#468](https://github.com/web3-storage/w3protocol/issues/468)) ([6c8242d](https://github.com/web3-storage/w3protocol/commit/6c8242db6708fa7129f51ad22e1b64d244a2c9fe)) -* allow injecting email ([#466](https://github.com/web3-storage/w3protocol/issues/466)) ([e19847f](https://github.com/web3-storage/w3protocol/commit/e19847fef804fed33f709ec8b78640fff21ca01e)) -* DbDelegationsStorage#find throws UnexpectedDelegation w/ { row } if failed bytesToDelegations ([#476](https://github.com/web3-storage/w3protocol/issues/476)) ([a6dafcb](https://github.com/web3-storage/w3protocol/commit/a6dafcb0c44b20fa87bbe81d7a982fb13387084e)) -* DbProvisionsStorage putMany doesnt error on cid col conflict ([#517](https://github.com/web3-storage/w3protocol/issues/517)) ([c1fea63](https://github.com/web3-storage/w3protocol/commit/c1fea63bd2e60326d8587079a8bbc810db8c741f)) -* delegations model tries to handle if row.bytes is Array not Buffer (e.g. cloudflare) ([#478](https://github.com/web3-storage/w3protocol/issues/478)) ([030e7b7](https://github.com/web3-storage/w3protocol/commit/030e7b7fe7b712d0e828ba2a1777d8f4e0a11eb5)) - - -### Miscellaneous Chores - -* **access-client:** release 11.0.0-rc.0 ([#573](https://github.com/web3-storage/w3protocol/issues/573)) ([be4386d](https://github.com/web3-storage/w3protocol/commit/be4386d66ceea393f289adb3c79273c250542807)) - -## [4.11.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.10.0...access-api-v4.11.0) (2023-02-21) - - -### Features - -* send emails from noreply@web3.storage ([#439](https://github.com/web3-storage/w3protocol/issues/439)) ([4f974a9](https://github.com/web3-storage/w3protocol/commit/4f974a9ae221fbd213fcd4d27df243f6ed6160ef)) - -## [4.10.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.9.0...access-api-v4.10.0) (2023-02-16) - - -### Features - -* add support for access/authorize and update ([#392](https://github.com/web3-storage/w3protocol/issues/392)) ([9c8ca0b](https://github.com/web3-storage/w3protocol/commit/9c8ca0b385c940c8f0c21ee9edde093d2dcab8b8)), closes [#386](https://github.com/web3-storage/w3protocol/issues/386) -* optional override for Postmark email From: field ([#354](https://github.com/web3-storage/w3protocol/issues/354)) ([f6b2350](https://github.com/web3-storage/w3protocol/commit/f6b23509768e59b72db51f6ae56cba260058771a)) -* rm /reproduce-cloudflare-error route ([#426](https://github.com/web3-storage/w3protocol/issues/426)) ([99cbd2f](https://github.com/web3-storage/w3protocol/commit/99cbd2f9bbdb55f31773c5d654d02b66c2468e32)) -* rm upload-api-proxy ability to route to separate environment audiences ([#407](https://github.com/web3-storage/w3protocol/issues/407)) ([5cfe274](https://github.com/web3-storage/w3protocol/commit/5cfe274fc402e3df4fd4bc8520cefb178f0eba65)) - - -### Bug Fixes - -* align postmark/welcome.txt with .html version ([#431](https://github.com/web3-storage/w3protocol/issues/431)) ([a53d6e6](https://github.com/web3-storage/w3protocol/commit/a53d6e62af165a1eb4649525cd0dc7e83a9f5279)) -* avoid email delegation via GET request ([#430](https://github.com/web3-storage/w3protocol/issues/430)) ([d282d6a](https://github.com/web3-storage/w3protocol/commit/d282d6abac12c29dfbe9690ba5cd285ee3960f91)) - -## [4.9.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.8.0...access-api-v4.9.0) (2023-01-30) - - -### Features - -* access-api handling store/info for space not in db returns failure with name ([#391](https://github.com/web3-storage/w3protocol/issues/391)) ([9610fcf](https://github.com/web3-storage/w3protocol/commit/9610fcf31b9dbf57cc5ec3ca31f642f7ab6190de)) -* update @ucanto/* to ~4.2.3 ([#405](https://github.com/web3-storage/w3protocol/issues/405)) ([50c0c80](https://github.com/web3-storage/w3protocol/commit/50c0c80789c26b777e854b7208b7391499d2ef18)) -* update access-api ucanto proxy to not need a signer ([#390](https://github.com/web3-storage/w3protocol/issues/390)) ([71cbeb7](https://github.com/web3-storage/w3protocol/commit/71cbeb718d0a5132b97efa1173a5aaf9c75cbe80)) - - -### Bug Fixes - -* make tests use did:web everywhere ([#397](https://github.com/web3-storage/w3protocol/issues/397)) ([c7d5c34](https://github.com/web3-storage/w3protocol/commit/c7d5c345360f97eee537c1833783eb5f2efc76d4)) - -## [4.8.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.7.0...access-api-v4.8.0) (2023-01-23) - - -### Features - -* add /reproduce-cloudflare-error route to access-api ([#380](https://github.com/web3-storage/w3protocol/issues/380)) ([edf925f](https://github.com/web3-storage/w3protocol/commit/edf925f6c17d8a78981ac71d7400dcac927301ac)) - -## [4.7.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.6.1...access-api-v4.7.0) (2023-01-19) - - -### Features - -* upload-api-proxy production url is to aws http gateway instead of up.web3.storage ([#376](https://github.com/web3-storage/w3protocol/issues/376)) ([96a856d](https://github.com/web3-storage/w3protocol/commit/96a856dc7b8d9ed7d305e44d06579b16d629fa33)) - -## [4.6.1](https://github.com/web3-storage/w3protocol/compare/access-api-v4.6.0...access-api-v4.6.1) (2023-01-19) - - -### Bug Fixes - -* debug logging describeFetch does not access `response.type` ([#373](https://github.com/web3-storage/w3protocol/issues/373)) ([d27b94c](https://github.com/web3-storage/w3protocol/commit/d27b94c9e7b685d62126c453734bf9d1cd6f2486)) - -## [4.6.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.5.0...access-api-v4.6.0) (2023-01-19) - - -### Features - -* access-api proxy.js has configurable options.catchInvocationError, by default catches HTTPError -> error result w/ status=502 ([#366](https://github.com/web3-storage/w3protocol/issues/366)) ([c8ca473](https://github.com/web3-storage/w3protocol/commit/c8ca4738dc5678b69e79cc70372bc69825c7026e)) - -## [4.5.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.4.0...access-api-v4.5.0) (2023-01-19) - - -### Features - -* access-api createStoreProxy uses patched fetch that will log any non-ok responses (to debug) ([#364](https://github.com/web3-storage/w3protocol/issues/364)) ([e741bc0](https://github.com/web3-storage/w3protocol/commit/e741bc040ad78a65d9c329765bfd8aec7820d96f)) - -## [4.4.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.3.0...access-api-v4.4.0) (2023-01-18) - - -### Features - -* access-api upload-api-proxy routes invocations with staging audience to staging upload-api ([#357](https://github.com/web3-storage/w3protocol/issues/357)) ([d8325af](https://github.com/web3-storage/w3protocol/commit/d8325afebde6c67fb54767a0ab1e639d6248389e)) - - -### Bug Fixes - -* upload-api-proxy default fetch binds to globalThis ([#361](https://github.com/web3-storage/w3protocol/issues/361)) ([a96e6af](https://github.com/web3-storage/w3protocol/commit/a96e6af1cebacb84a097d1fc24d4fe5f658c1b93)) - -## [4.3.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.2.0...access-api-v4.3.0) (2023-01-17) - - -### Features - -* access-api forwards store/ and upload/ invocations to upload-api ([#334](https://github.com/web3-storage/w3protocol/issues/334)) ([b773376](https://github.com/web3-storage/w3protocol/commit/b77337692d9e4580031c429c429d4055d6f6ebff)) - -## [4.2.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.1.0...access-api-v4.2.0) (2022-12-15) - - -### Features - -* **access-api:** access-api version endpoint has publicKey property ([#317](https://github.com/web3-storage/w3protocol/issues/317)) ([474d561](https://github.com/web3-storage/w3protocol/commit/474d561845f7c10a785421ef95e552385861a970)) - -## [4.1.0](https://github.com/web3-storage/w3protocol/compare/access-api-v4.0.0...access-api-v4.1.0) (2022-12-14) - - -### Features - -* access-api version route sets did=ucantoServerId and adds a signer prop ([#305](https://github.com/web3-storage/w3protocol/issues/305)) ([5eab262](https://github.com/web3-storage/w3protocol/commit/5eab262d4bc503bf7a68f19b8193b3a57e5b8451)) -* embedded key resolution ([#312](https://github.com/web3-storage/w3protocol/issues/312)) ([4da91d5](https://github.com/web3-storage/w3protocol/commit/4da91d5f7f798d0d46c4df2aaf224610a8760d9e)) -* include ucanto server principal did as 'aud' key in /version endpoint ([#309](https://github.com/web3-storage/w3protocol/issues/309)) ([bf3b171](https://github.com/web3-storage/w3protocol/commit/bf3b17110b188c1326ba5b041d0f75906c793671)) - - -### Bug Fixes - -* access-api ctx.signer no longer uses env.DID. instead env.DID is only used for ucanto server id ([#303](https://github.com/web3-storage/w3protocol/issues/303)) ([93d7003](https://github.com/web3-storage/w3protocol/commit/93d70038abe1b36c2a84d10214143cda6b5e4423)) -* access-api wrangler.toml sets DID env var in env.dev ([#297](https://github.com/web3-storage/w3protocol/issues/297)) ([c4ca459](https://github.com/web3-storage/w3protocol/commit/c4ca459445f902d65eade97f41a197fc9178636a)) -* access-client/src/agent default PRINCIPAL is did:web:web3.storage ([#296](https://github.com/web3-storage/w3protocol/issues/296)) ([27f2f60](https://github.com/web3-storage/w3protocol/commit/27f2f60dac7c95cb9efd42a28f5abfef8bdeb197)) -* add support for did:web in the cli ([#301](https://github.com/web3-storage/w3protocol/issues/301)) ([885f7c1](https://github.com/web3-storage/w3protocol/commit/885f7c15cec7a0724fcc4a8dd5eb0146a918373d)) -* fix client cli service did resolve ([#292](https://github.com/web3-storage/w3protocol/issues/292)) ([6be9608](https://github.com/web3-storage/w3protocol/commit/6be9608a907665a8123938ef804bebfffc5c7232)) -* use did:web key in root handler ([#311](https://github.com/web3-storage/w3protocol/issues/311)) ([537dc48](https://github.com/web3-storage/w3protocol/commit/537dc48f4d864d0dbe41752f1a45dd14cd03e489)) - -## [4.0.0](https://github.com/web3-storage/w3protocol/compare/access-api-v3.0.0...access-api-v4.0.0) (2022-12-13) - - -### ⚠ BREAKING CHANGES - -* upgrade access-api @ucanto/* and @ipld/dag-ucan major versions ([#246](https://github.com/web3-storage/w3protocol/issues/246)) -* access-client store decoupling ([#228](https://github.com/web3-storage/w3protocol/issues/228)) -* upgrade to `@ucanto/{interface,principal}`@^4.0.0 ([#238](https://github.com/web3-storage/w3protocol/issues/238)) -* follow up on the capabilities extract ([#239](https://github.com/web3-storage/w3protocol/issues/239)) -* doc capabilities & make requierd nb non-optionals ([#159](https://github.com/web3-storage/w3protocol/issues/159)) -* Remove 0.8 caps and add account delegation to the service ([#123](https://github.com/web3-storage/w3protocol/issues/123)) -* bump to 0.9 ([#116](https://github.com/web3-storage/w3protocol/issues/116)) - -### Features - -* [#153](https://github.com/web3-storage/w3protocol/issues/153) ([#177](https://github.com/web3-storage/w3protocol/issues/177)) ([d6d448c](https://github.com/web3-storage/w3protocol/commit/d6d448c16f188398c30f2d1b83f69e1d7becd450)) -* access-api uses DID env variable when building its ucanto server id ([#275](https://github.com/web3-storage/w3protocol/issues/275)) ([311da78](https://github.com/web3-storage/w3protocol/commit/311da786b8f2fdf5ac585170e746a5a7b04ec57b)) -* **access-client:** cli and recover ([#207](https://github.com/web3-storage/w3protocol/issues/207)) ([adb3a8d](https://github.com/web3-storage/w3protocol/commit/adb3a8d61d42b31f106e86b95faa3e442f5dc2c7)) -* account recover with email ([#149](https://github.com/web3-storage/w3protocol/issues/149)) ([6c659ba](https://github.com/web3-storage/w3protocol/commit/6c659ba68d23c3448d5150bc76f1ddcb91ae18d8)) -* bump to 0.9 ([#116](https://github.com/web3-storage/w3protocol/issues/116)) ([3e0b63f](https://github.com/web3-storage/w3protocol/commit/3e0b63f38aace3a86655a1aa40e529c1501dc136)) -* doc capabilities & make requierd nb non-optionals ([#159](https://github.com/web3-storage/w3protocol/issues/159)) ([6496773](https://github.com/web3-storage/w3protocol/commit/6496773f2a4977e06126ade37ae9dfc218b05f7f)) -* follow up on the capabilities extract ([#239](https://github.com/web3-storage/w3protocol/issues/239)) ([ef5e779](https://github.com/web3-storage/w3protocol/commit/ef5e77922b67155f0c3e5cb37c12e32f9a56cce1)) -* Remove 0.8 caps and add account delegation to the service ([#123](https://github.com/web3-storage/w3protocol/issues/123)) ([878f8c9](https://github.com/web3-storage/w3protocol/commit/878f8c9a38f02dac509ef0b4437ab3d1b8467eb3)), closes [#117](https://github.com/web3-storage/w3protocol/issues/117) [#121](https://github.com/web3-storage/w3protocol/issues/121) -* Revert "feat!: upgrade to `@ucanto/{interface,principal}`@^4.0.0" ([#245](https://github.com/web3-storage/w3protocol/issues/245)) ([c182bbe](https://github.com/web3-storage/w3protocol/commit/c182bbe5e8c5a7d5c74b10cbf4b7a45b51e9b184)) -* sync encode/decode delegations ([#276](https://github.com/web3-storage/w3protocol/issues/276)) ([ab981fb](https://github.com/web3-storage/w3protocol/commit/ab981fb6e33799153022c0f6d06c282917e7af7c)) -* upgrade access-api @ucanto/* and @ipld/dag-ucan major versions ([#246](https://github.com/web3-storage/w3protocol/issues/246)) ([5e663d1](https://github.com/web3-storage/w3protocol/commit/5e663d12ccea7d21cc8e7c36869f144a08eaa1b0)) -* upgrade to `@ucanto/{interface,principal}`@^4.0.0 ([#238](https://github.com/web3-storage/w3protocol/issues/238)) ([2f3bab8](https://github.com/web3-storage/w3protocol/commit/2f3bab8924fe7f34a5db64d2521730fc85739d3a)) - - -### Bug Fixes - -* 0.9 ([#78](https://github.com/web3-storage/w3protocol/issues/78)) ([1b1ed01](https://github.com/web3-storage/w3protocol/commit/1b1ed01d537e88bbdeb5ea2aeb967b27bd11f87d)) -* add validation copy ([#257](https://github.com/web3-storage/w3protocol/issues/257)) ([7f50af4](https://github.com/web3-storage/w3protocol/commit/7f50af471aa5d4f50fc2ac92e7bcbcc311a800b4)), closes [#139](https://github.com/web3-storage/w3protocol/issues/139) -* fix Access API cannot get space/info [#243](https://github.com/web3-storage/w3protocol/issues/243) ([#255](https://github.com/web3-storage/w3protocol/issues/255)) ([1bacd54](https://github.com/web3-storage/w3protocol/commit/1bacd544da803c43cf85043ecdada4dee2b3e2d3)) -* fix d1 migrations ([#264](https://github.com/web3-storage/w3protocol/issues/264)) ([fb8c09d](https://github.com/web3-storage/w3protocol/commit/fb8c09d5a243a20cbde2086eac57b9637a2e1d1d)) -* make d1 spaces.metadata nullable and change to kysely ([#284](https://github.com/web3-storage/w3protocol/issues/284)) ([c8a9ce5](https://github.com/web3-storage/w3protocol/commit/c8a9ce544226b3c8456d45b15e29cec84894aeb8)), closes [#280](https://github.com/web3-storage/w3protocol/issues/280) -* make multiformats 9 go away ([#133](https://github.com/web3-storage/w3protocol/issues/133)) ([2668880](https://github.com/web3-storage/w3protocol/commit/2668880a23c28ee45596fb1bc978564908a17e18)) -* miniflare dev script ([#137](https://github.com/web3-storage/w3protocol/issues/137)) ([1c5a4e2](https://github.com/web3-storage/w3protocol/commit/1c5a4e2f89018fdf1624cc41650764ee08f9adc4)) -* testing staging deploy ([3d500fa](https://github.com/web3-storage/w3protocol/commit/3d500fa4e6cd17a1c0c9739fb3e0e722f97d599d)) -* try to deploy api staging ([18b7b29](https://github.com/web3-storage/w3protocol/commit/18b7b296cb7e94c7fddeb163340cc0e72ce51c0a)) -* try to deploy staging ([c616818](https://github.com/web3-storage/w3protocol/commit/c61681883e69982c5ae378e6ffbf54f45c397c89)) - - -### Code Refactoring - -* access-client store decoupling ([#228](https://github.com/web3-storage/w3protocol/issues/228)) ([a785278](https://github.com/web3-storage/w3protocol/commit/a7852785e2ac783bcb21790b4a87ee5ad0a1380e)) - -## [3.0.0](https://github.com/web3-storage/w3-protocol/compare/access-api-v2.0.1...access-api-v3.0.0) (2022-10-14) - - -### ⚠ BREAKING CHANGES - -* new accounts (#72) -* awake (#66) - -### Features - -* **access-api:** add email notifications ([40b5332](https://github.com/web3-storage/w3-protocol/commit/40b53323b9c569b191f35d62d32aac844de18fdd)) -* **access-api:** add logtail ([1246ab0](https://github.com/web3-storage/w3-protocol/commit/1246ab0846f49f9adce734cf4f651d22f7ac7536)) -* **access-api:** add prod config ([60707ec](https://github.com/web3-storage/w3-protocol/commit/60707ecef6ed1af8d8fe38f9ee0deb9761cb80a6)) -* **access-api:** new email template ([cc19320](https://github.com/web3-storage/w3-protocol/commit/cc193202e385d8079144aa90e989af07cf743b0b)) -* awake ([#66](https://github.com/web3-storage/w3-protocol/issues/66)) ([bb66f57](https://github.com/web3-storage/w3-protocol/commit/bb66f5772049e3363a753ea5b336c2fa1e42911e)) -* fail validate for register email and add metrics ([0916ba6](https://github.com/web3-storage/w3-protocol/commit/0916ba6bda8ad46ccc4f6bb0c6f4a48dd99db0c8)) -* new accounts ([#72](https://github.com/web3-storage/w3-protocol/issues/72)) ([9f6cb41](https://github.com/web3-storage/w3-protocol/commit/9f6cb419d33b9446dd80f8541228096cf2677d45)) -* resync ([5cae9cd](https://github.com/web3-storage/w3-protocol/commit/5cae9cd55cfcc06046eb23a2f33931299dd07ff5)) -* sdk ([305b2d3](https://github.com/web3-storage/w3-protocol/commit/305b2d317ba4b8743a1594e9dbe0d22bac90c229)) -* sdk and cli ([2373447](https://github.com/web3-storage/w3-protocol/commit/2373447db93ee16276f45fbfe40e4b98c28b6ab7)) -* support ucan as bearer ([#50](https://github.com/web3-storage/w3-protocol/issues/50)) ([bc73755](https://github.com/web3-storage/w3-protocol/commit/bc73755a308d116ba42f67cef2c7a6be5307c327)) -* update deps ([d276375](https://github.com/web3-storage/w3-protocol/commit/d2763750159ad56132f0b002ff5f50cc36fce20c)) -* use modules and setup ([#99](https://github.com/web3-storage/w3-protocol/issues/99)) ([b060c0b](https://github.com/web3-storage/w3-protocol/commit/b060c0b299ee55dbe7820231c63be90129a39652)), closes [#98](https://github.com/web3-storage/w3-protocol/issues/98) - - -### Bug Fixes - -* **access-api:** add kvs ([e241d62](https://github.com/web3-storage/w3-protocol/commit/e241d6290d93bfc20986591398ab20af6173ef78)) -* **access-api:** add logtail to tests ([546152a](https://github.com/web3-storage/w3-protocol/commit/546152aea75aff393935d866cbd9c1a818e81725)) -* **access-api:** add name to emails and fix miniflare dev ([ad84b46](https://github.com/web3-storage/w3-protocol/commit/ad84b4696ec9078800d4bb99597bc1da5068abc1)), closes [#60](https://github.com/web3-storage/w3-protocol/issues/60) -* **access-api:** changes names, update deps ([3f9e1f8](https://github.com/web3-storage/w3-protocol/commit/3f9e1f800728f57a9b194154c1b2e0133aa5bca4)) -* **access-api:** ci tests ([314cf63](https://github.com/web3-storage/w3-protocol/commit/314cf635aa632e05a79822c5f7ec012559ff4427)) -* **access-api:** cors on errors ([bb8e916](https://github.com/web3-storage/w3-protocol/commit/bb8e91693be87186846bac166b1e5ab05c33a112)) -* **access-api:** test error in staging ([1a97a57](https://github.com/web3-storage/w3-protocol/commit/1a97a5797678d242db5849d9f10b8bc1907873d6)) -* **access-api:** use different email for tests ([1a01504](https://github.com/web3-storage/w3-protocol/commit/1a0150429253afc572c9689f0a09a4c26499bd03)) -* add analytics to staging and prod ([14941d9](https://github.com/web3-storage/w3-protocol/commit/14941d901e48e92896cc962b3e93488731afa381)) -* add readme ([25a01f5](https://github.com/web3-storage/w3-protocol/commit/25a01f54c4db8f5af75e23c924d86a7a67823beb)) -* add readme text ([088273e](https://github.com/web3-storage/w3-protocol/commit/088273e97a72f7ec85c60a5cb5dd6a8754603064)) -* api ([4e70483](https://github.com/web3-storage/w3-protocol/commit/4e7048316d3ed95f5b7606830d75420c5020bf53)) -* bump api ([88a381a](https://github.com/web3-storage/w3-protocol/commit/88a381ae2077d24ccd762dc7fb47f91a58268e89)) -* change sentry name ([ad69e47](https://github.com/web3-storage/w3-protocol/commit/ad69e47cb01ad1acedccf95798eacd9a649659bd)) -* fix ci ([ce8f7a9](https://github.com/web3-storage/w3-protocol/commit/ce8f7a937a336c6f86d22a25fe4a4465f0c86c58)) -* new email for notifications ([57b6845](https://github.com/web3-storage/w3-protocol/commit/57b6845a56f534505eeabdf5ab2e20f8b37c9532)) -* proper envs and update deps ([d5dccb6](https://github.com/web3-storage/w3-protocol/commit/d5dccb6e9c23b5ddbdffa4c67c04d195524b38f2)) -* reconfigure ava to avoid ci problems ([ce2006e](https://github.com/web3-storage/w3-protocol/commit/ce2006eb8f02153618c2e483a5bb5983443ff89a)) -* redirect on success ([#77](https://github.com/web3-storage/w3-protocol/issues/77)) ([0e67f3b](https://github.com/web3-storage/w3-protocol/commit/0e67f3be07118dd132770818486d2498416a300f)) -* remove error ([1ba180b](https://github.com/web3-storage/w3-protocol/commit/1ba180b2c8a2da5dd9d3a69f23bcd93022500d6e)) -* remove it@dag.house ([8d319fe](https://github.com/web3-storage/w3-protocol/commit/8d319fef315fa5dd3a0b88b8bf23f5576e8adc28)) -* revert fail email validation ([389784b](https://github.com/web3-storage/w3-protocol/commit/389784bb995b18683ca71af63816db123880d3eb)) -* route for staging ([8d5866f](https://github.com/web3-storage/w3-protocol/commit/8d5866f2bf870eaf2bb26bddef02ee8a0b6d1386)) -* staging ([29db547](https://github.com/web3-storage/w3-protocol/commit/29db547848e8a5cab4d5c91a72010ed4aec5d09c)) -* staging ([53f62b0](https://github.com/web3-storage/w3-protocol/commit/53f62b0269a7c3def5c22afb2b888d5886683f05)) -* test api ([9597c44](https://github.com/web3-storage/w3-protocol/commit/9597c4486abb1c21a0d8a504d4bb8632581e4800)) -* update readme ([2f78a93](https://github.com/web3-storage/w3-protocol/commit/2f78a930ef46b1e80d63f8e9b3bce6e960d5e4d9)) - -## [2.0.1](https://github.com/web3-storage/w3-protocol/compare/access-api-v2.0.0...access-api-v2.0.1) (2022-10-13) - - -### Bug Fixes - -* **access-api:** add name to emails and fix miniflare dev ([ad84b46](https://github.com/web3-storage/w3-protocol/commit/ad84b4696ec9078800d4bb99597bc1da5068abc1)), closes [#60](https://github.com/web3-storage/w3-protocol/issues/60) -* bump api ([88a381a](https://github.com/web3-storage/w3-protocol/commit/88a381ae2077d24ccd762dc7fb47f91a58268e89)) -* fix ci ([ce8f7a9](https://github.com/web3-storage/w3-protocol/commit/ce8f7a937a336c6f86d22a25fe4a4465f0c86c58)) -* update readme ([2f78a93](https://github.com/web3-storage/w3-protocol/commit/2f78a930ef46b1e80d63f8e9b3bce6e960d5e4d9)) - -## [2.0.0](https://github.com/web3-storage/w3-protocol/compare/access-api-v1.0.0...access-api-v2.0.0) (2022-10-10) - - -### ⚠ BREAKING CHANGES - -* new accounts (#72) - -### Features - -* new accounts ([#72](https://github.com/web3-storage/w3-protocol/issues/72)) ([9f6cb41](https://github.com/web3-storage/w3-protocol/commit/9f6cb419d33b9446dd80f8541228096cf2677d45)) - - -### Bug Fixes - -* redirect on success ([#77](https://github.com/web3-storage/w3-protocol/issues/77)) ([0e67f3b](https://github.com/web3-storage/w3-protocol/commit/0e67f3be07118dd132770818486d2498416a300f)) - -## [1.0.0](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.4.0...access-api-v1.0.0) (2022-09-21) - - -### ⚠ BREAKING CHANGES - -* awake (#66) - -### Features - -* awake ([#66](https://github.com/web3-storage/ucan-protocol/issues/66)) ([bb66f57](https://github.com/web3-storage/ucan-protocol/commit/bb66f5772049e3363a753ea5b336c2fa1e42911e)) -* support ucan as bearer ([#50](https://github.com/web3-storage/ucan-protocol/issues/50)) ([bc73755](https://github.com/web3-storage/ucan-protocol/commit/bc73755a308d116ba42f67cef2c7a6be5307c327)) - - -### Bug Fixes - -* **access-api:** cors on errors ([bb8e916](https://github.com/web3-storage/ucan-protocol/commit/bb8e91693be87186846bac166b1e5ab05c33a112)) - -## [0.4.0](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.3.0...access-api-v0.4.0) (2022-08-31) - - -### Features - -* **access-api:** add email notifications ([40b5332](https://github.com/web3-storage/ucan-protocol/commit/40b53323b9c569b191f35d62d32aac844de18fdd)) - - -### Bug Fixes - -* new email for notifications ([57b6845](https://github.com/web3-storage/ucan-protocol/commit/57b6845a56f534505eeabdf5ab2e20f8b37c9532)) -* reconfigure ava to avoid ci problems ([ce2006e](https://github.com/web3-storage/ucan-protocol/commit/ce2006eb8f02153618c2e483a5bb5983443ff89a)) -* remove it@dag.house ([8d319fe](https://github.com/web3-storage/ucan-protocol/commit/8d319fef315fa5dd3a0b88b8bf23f5576e8adc28)) - -## [0.3.0](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.2.0...access-api-v0.3.0) (2022-08-30) - - -### Features - -* fail validate for register email and add metrics ([0916ba6](https://github.com/web3-storage/ucan-protocol/commit/0916ba6bda8ad46ccc4f6bb0c6f4a48dd99db0c8)) -* update deps ([d276375](https://github.com/web3-storage/ucan-protocol/commit/d2763750159ad56132f0b002ff5f50cc36fce20c)) - - -### Bug Fixes - -* add analytics to staging and prod ([14941d9](https://github.com/web3-storage/ucan-protocol/commit/14941d901e48e92896cc962b3e93488731afa381)) -* revert fail email validation ([389784b](https://github.com/web3-storage/ucan-protocol/commit/389784bb995b18683ca71af63816db123880d3eb)) - -## [0.2.0](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.1.4...access-api-v0.2.0) (2022-08-26) - - -### Features - -* **access-api:** add logtail ([1246ab0](https://github.com/web3-storage/ucan-protocol/commit/1246ab0846f49f9adce734cf4f651d22f7ac7536)) -* **access-api:** new email template ([cc19320](https://github.com/web3-storage/ucan-protocol/commit/cc193202e385d8079144aa90e989af07cf743b0b)) - - -### Bug Fixes - -* **access-api:** add logtail to tests ([546152a](https://github.com/web3-storage/ucan-protocol/commit/546152aea75aff393935d866cbd9c1a818e81725)) -* **access-api:** use different email for tests ([1a01504](https://github.com/web3-storage/ucan-protocol/commit/1a0150429253afc572c9689f0a09a4c26499bd03)) - -## [0.1.4](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.1.3...access-api-v0.1.4) (2022-08-25) - - -### Bug Fixes - -* api ([4e70483](https://github.com/web3-storage/ucan-protocol/commit/4e7048316d3ed95f5b7606830d75420c5020bf53)) -* route for staging ([8d5866f](https://github.com/web3-storage/ucan-protocol/commit/8d5866f2bf870eaf2bb26bddef02ee8a0b6d1386)) -* staging ([29db547](https://github.com/web3-storage/ucan-protocol/commit/29db547848e8a5cab4d5c91a72010ed4aec5d09c)) -* staging ([53f62b0](https://github.com/web3-storage/ucan-protocol/commit/53f62b0269a7c3def5c22afb2b888d5886683f05)) - -## [0.1.3](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.1.2...access-api-v0.1.3) (2022-08-25) - - -### Bug Fixes - -* test api ([9597c44](https://github.com/web3-storage/ucan-protocol/commit/9597c4486abb1c21a0d8a504d4bb8632581e4800)) - -## [0.1.2](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.1.1...access-api-v0.1.2) (2022-08-25) - - -### Bug Fixes - -* **access-api:** add kvs ([e241d62](https://github.com/web3-storage/ucan-protocol/commit/e241d6290d93bfc20986591398ab20af6173ef78)) -* **access-api:** test error in staging ([1a97a57](https://github.com/web3-storage/ucan-protocol/commit/1a97a5797678d242db5849d9f10b8bc1907873d6)) -* change sentry name ([ad69e47](https://github.com/web3-storage/ucan-protocol/commit/ad69e47cb01ad1acedccf95798eacd9a649659bd)) -* proper envs and update deps ([d5dccb6](https://github.com/web3-storage/ucan-protocol/commit/d5dccb6e9c23b5ddbdffa4c67c04d195524b38f2)) -* remove error ([1ba180b](https://github.com/web3-storage/ucan-protocol/commit/1ba180b2c8a2da5dd9d3a69f23bcd93022500d6e)) - -## [0.1.1](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.1.0...access-api-v0.1.1) (2022-08-24) - - -### Bug Fixes - -* add readme ([25a01f5](https://github.com/web3-storage/ucan-protocol/commit/25a01f54c4db8f5af75e23c924d86a7a67823beb)) -* add readme text ([088273e](https://github.com/web3-storage/ucan-protocol/commit/088273e97a72f7ec85c60a5cb5dd6a8754603064)) - -## [0.1.0](https://github.com/web3-storage/ucan-protocol/compare/access-api-v0.0.1...access-api-v0.1.0) (2022-08-24) - - -### Features - -* **access-api:** add prod config ([60707ec](https://github.com/web3-storage/ucan-protocol/commit/60707ecef6ed1af8d8fe38f9ee0deb9761cb80a6)) -* resync ([5cae9cd](https://github.com/web3-storage/ucan-protocol/commit/5cae9cd55cfcc06046eb23a2f33931299dd07ff5)) -* sdk ([305b2d3](https://github.com/web3-storage/ucan-protocol/commit/305b2d317ba4b8743a1594e9dbe0d22bac90c229)) -* sdk and cli ([2373447](https://github.com/web3-storage/ucan-protocol/commit/2373447db93ee16276f45fbfe40e4b98c28b6ab7)) - - -### Bug Fixes - -* **access-api:** changes names, update deps ([3f9e1f8](https://github.com/web3-storage/ucan-protocol/commit/3f9e1f800728f57a9b194154c1b2e0133aa5bca4)) -* **access-api:** ci tests ([314cf63](https://github.com/web3-storage/ucan-protocol/commit/314cf635aa632e05a79822c5f7ec012559ff4427)) diff --git a/packages/access-api/migrations/0000_create_spaces_table.sql b/packages/access-api/migrations/0000_create_spaces_table.sql deleted file mode 100644 index c56fb1cf5..000000000 --- a/packages/access-api/migrations/0000_create_spaces_table.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Migration number: 0000 2022-11-17T15:52:48.968Z -CREATE TABLE - IF NOT EXISTS spaces ( - did TEXT NOT NULL PRIMARY KEY, - product TEXT NOT NULL, - email TEXT NOT NULL, - agent TEXT NOT NULL, - 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 (did) - ); \ No newline at end of file diff --git a/packages/access-api/migrations/0001_add_metadata_and_invocation_to_spaces.sql b/packages/access-api/migrations/0001_add_metadata_and_invocation_to_spaces.sql deleted file mode 100644 index 771adfa59..000000000 --- a/packages/access-api/migrations/0001_add_metadata_and_invocation_to_spaces.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Migration number: 0001 2022-11-24T11:52:58.174Z -ALTER TABLE "spaces" -ADD COLUMN "metadata" JSON NOT NULL DEFAULT '"{}"'; - -ALTER TABLE "spaces" -ADD COLUMN "invocation" text NOT NULL DEFAULT EMPTY; diff --git a/packages/access-api/migrations/0002_add_delegation_column.sql b/packages/access-api/migrations/0002_add_delegation_column.sql deleted file mode 100644 index 339d64d0b..000000000 --- a/packages/access-api/migrations/0002_add_delegation_column.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Migration number: 0002 2022-11-29T14:41:37.991Z -ALTER TABLE "spaces" -ADD COLUMN "delegation" text DEFAULT NULL; \ No newline at end of file diff --git a/packages/access-api/migrations/0003_space.metadata_can_be_null.sql b/packages/access-api/migrations/0003_space.metadata_can_be_null.sql deleted file mode 100644 index e7926c093..000000000 --- a/packages/access-api/migrations/0003_space.metadata_can_be_null.sql +++ /dev/null @@ -1,44 +0,0 @@ --- Migration number: 0003 2022-12-12T18:58:30.339Z -ALTER TABLE spaces -RENAME TO _spaces_old; - -CREATE TABLE - spaces ( - did TEXT NOT NULL PRIMARY KEY, - product TEXT NOT NULL, - email TEXT NOT NULL, - agent TEXT NOT NULL, - 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')), - metadata JSON DEFAULT EMPTY, - invocation TEXT NOT NULL DEFAULT EMPTY, - delegation TEXT DEFAULT NULL, - UNIQUE (did) - ); - -INSERT INTO - spaces ( - did, - product, - email, - agent, - inserted_at, - updated_at, - metadata, - invocation, - delegation - ) -SELECT - did, - product, - email, - agent, - inserted_at, - updated_at, - metadata, - invocation, - delegation -FROM - _spaces_old; - -DROP TABLE "_spaces_old"; \ No newline at end of file diff --git a/packages/access-api/migrations/0004_add_accounts_table.sql b/packages/access-api/migrations/0004_add_accounts_table.sql deleted file mode 100644 index 81118aed1..000000000 --- a/packages/access-api/migrations/0004_add_accounts_table.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Migration number: 0004 2023-01-24T15:09:12.316Z -CREATE TABLE - IF NOT EXISTS accounts ( - did TEXT NOT NULL PRIMARY KEY, - 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 (did) - ); - -CREATE TABLE - IF NOT EXISTS delegations ( - 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), - FOREIGN KEY (audience) REFERENCES accounts (did) ON UPDATE CASCADE ON DELETE CASCADE - ); \ No newline at end of file diff --git a/packages/access-api/migrations/0005_drop_delegations_audience_to_accounts_did_fk.sql b/packages/access-api/migrations/0005_drop_delegations_audience_to_accounts_did_fk.sql deleted file mode 100644 index 8bbae4624..000000000 --- a/packages/access-api/migrations/0005_drop_delegations_audience_to_accounts_did_fk.sql +++ /dev/null @@ -1,24 +0,0 @@ --- 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`. -AND cloudflare d1 doesn't support `DROP TABLE` -*/ - -CREATE TABLE - IF NOT EXISTS delegations_v2 ( - 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_v2 (cid, bytes, audience, issuer, expiration, inserted_at, updated_at) -SELECT cid, bytes, audience, issuer, expiration, inserted_at, updated_at FROM delegations; diff --git a/packages/access-api/migrations/0006_add_storage_provider_consumer_schema.sql b/packages/access-api/migrations/0006_add_storage_provider_consumer_schema.sql deleted file mode 100644 index 7b94d6f07..000000000 --- a/packages/access-api/migrations/0006_add_storage_provider_consumer_schema.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Migration number: 0006 2023-03-02T16:40:04.407Z - -/* -goal: add a table to keep track of the storage provider for a space. -to enable https://github.com/web3-storage/w3protocol/issues/459 -*/ - -CREATE TABLE - -- provision: the action of providing or supplying something for use - -- use case: representing the registration of a storage provider to a space - IF NOT EXISTS provisions ( - -- cid of invocation that created this provision - cid TEXT NOT NULL PRIMARY KEY, - -- DID of the actor that is consuming the provider. e.g. a space DID - consumer TEXT NOT NULL, - -- DID of the provider e.g. a storage provider - provider TEXT NOT NULL, - -- DID of the actor that authorized this provision - sponsor TEXT NOT NULL, - 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')) - ); diff --git a/packages/access-api/migrations/0007_add_delegations_v3.sql b/packages/access-api/migrations/0007_add_delegations_v3.sql deleted file mode 100644 index 4220ef3e0..000000000 --- a/packages/access-api/migrations/0007_add_delegations_v3.sql +++ /dev/null @@ -1,20 +0,0 @@ --- Migration number: 0007 2023-03-20T23:48:40.469Z - -/* -goal: add a new table to store delegations in -which doesn't have a 'bytes' column. - -context: we're going to start storing bytes outside of the database (e.g. in R2) -*/ - -CREATE TABLE - IF NOT EXISTS delegations_v3 ( - /* cidv1 dag-ucan/dag-cbor sha2-256 */ - cid TEXT NOT NULL PRIMARY KEY, - 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) - ); diff --git a/packages/access-api/package.json b/packages/access-api/package.json deleted file mode 100644 index cb4388970..000000000 --- a/packages/access-api/package.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "name": "@web3-storage/access-api", - "version": "7.0.0", - "description": "Access API", - "type": "module", - "main": "dist/worker.js", - "module": "dist/worker.js", - "private": true, - "scripts": { - "lint": "tsc --build && eslint '**/*.{js,ts}' && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore", - "lint:fix": "prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore --write", - "dev": "scripts/cli.js dev", - "build": "scripts/cli.js build", - "check": "tsc --build", - "test": "pnpm build && mocha --bail --timeout 10s -n no-warnings -n experimental-vm-modules", - "test-watch": "pnpm build && mocha --bail --timeout 10s --watch --parallel -n no-warnings -n experimental-vm-modules --watch-files src,test" - }, - "author": "Hugo Dias (hugodias.me)", - "license": "(Apache-2.0 OR MIT)", - "dependencies": { - "@ipld/dag-ucan": "^3.3.2", - "@ucanto/client": "^8.0.0", - "@ucanto/core": "^8.0.0", - "@ucanto/interface": "^8.0.0", - "@ucanto/principal": "^8.0.0", - "@ucanto/server": "^8.0.0", - "@ucanto/transport": "^8.0.0", - "@ucanto/validator": "^8.0.0", - "@web3-storage/access": "workspace:^", - "@web3-storage/capabilities": "workspace:^", - "@web3-storage/did-mailto": "workspace:^", - "@web3-storage/worker-utils": "0.5.0-dev", - "dotenv": "^16.0.3", - "kysely": "^0.23.4", - "kysely-d1": "^0.3.0", - "multiformats": "^11.0.2", - "p-retry": "^5.1.2", - "preact": "^10.11.3", - "preact-render-to-string": "^5.2.6", - "qrcode": "^1.5.1", - "streaming-iterables": "^7.1.0", - "toucan-js": "^3.1.0" - }, - "devDependencies": { - "@cloudflare/workers-types": "^3.19.0", - "@databases/split-sql-query": "^1.0.3", - "@databases/sql": "^3.2.0", - "@miniflare/core": "^2.11.0", - "@miniflare/r2": "^2.12.1", - "@sentry/cli": "2.7.0", - "@types/assert": "^1.5.6", - "@types/git-rev-sync": "^2.0.0", - "@types/mocha": "^10.0.1", - "@types/node": "^18.11.18", - "@types/qrcode": "^1.5.0", - "@ucanto/client": "^8.0.0", - "better-sqlite3": "8.0.1", - "buffer": "^6.0.3", - "esbuild": "^0.17.2", - "git-rev-sync": "^3.0.2", - "hd-scripts": "^4.0.0", - "is-subset": "^0.1.1", - "miniflare": "^2.12.1", - "mocha": "^10.2.0", - "p-wait-for": "^5.0.0", - "process": "^0.11.10", - "readable-stream": "^4.2.0", - "sade": "^1.8.1", - "typescript": "4.9.5", - "wrangler": "^2.13.0" - }, - "eslintConfig": { - "extends": [ - "./node_modules/hd-scripts/eslint/preact.js" - ], - "parserOptions": { - "project": "./tsconfig.json", - "ecmaFeatures": { - "jsx": true - } - }, - "env": { - "mocha": true - }, - "globals": { - "VERSION": "readonly", - "COMMITHASH": "readonly", - "BRANCH": "readonly", - "DEBUG": "readonly", - "SPACES": "writable", - "VALIDATIONS": "writable", - "BUCKET": "writable", - "W3ACCESS_METRICS": "writable", - "WebSocketPair": "readonly" - }, - "rules": { - "unicorn/prefer-number-properties": "off", - "unicorn/expiring-todo-comments": "off", - "@typescript-eslint/ban-types": "off", - "unicorn/no-null": "off", - "jsdoc/no-undefined-types": [ - "error", - { - "definedTypes": [ - "AsyncIterable", - "AsyncIterableIterator", - "Awaited", - "D1Database", - "FetchEvent", - "Iterable", - "IterableIterator", - "KVNamespace", - "PromiseLike", - "ResponseInit" - ] - } - ] - } - }, - "eslintIgnore": [ - "node_modules", - "coverage", - "dist", - "docs" - ], - "ava": { - "failFast": true, - "files": [ - "test/**/*.test.js" - ], - "nodeArguments": [ - "--no-warnings", - "--experimental-vm-modules" - ], - "ignoredByWatcher": [ - "./dist/*", - "./wrangler/**" - ] - }, - "depcheck": { - "specials": [ - "bin" - ], - "ignores": [ - "@types/*", - "wrangler", - "@cloudflare/workers-types", - "process", - "buffer", - "hd-scripts", - "better-sqlite3" - ] - }, - "pnpm": { - "overrides": { - "undici": "^5.20.0" - } - } -} diff --git a/packages/access-api/postmark/README.md b/packages/access-api/postmark/README.md deleted file mode 100644 index aca7796dd..000000000 --- a/packages/access-api/postmark/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## Postmark templates - -These are provided here for reference and version tracking. - -Before use they must be manually configured in the Postmark account: - -1. In Postmark account, choose Server, then go to Templates tab: -2. Initial setup: add template if necessary, "Code your own" and your choice of existing/new or "Don't use a layout". -3. The name of the template is not critical, but the "Alias" configuration link must be set to match the file name(s) in this directory. The subject should be set if provided here. -4. There are two tabs in Postmark's editor, copy-paste both the HTML and Text versions for a given template. - -Please make sure to keep the files here up-to-date with any edits you might make from the Postmark dashboard. \ No newline at end of file diff --git a/packages/access-api/postmark/welcome.html b/packages/access-api/postmark/welcome.html deleted file mode 100644 index 0c41fe1ef..000000000 --- a/packages/access-api/postmark/welcome.html +++ /dev/null @@ -1,41 +0,0 @@ - -

Hi {{email}}! To complete your {{product_name}} registration, we just need to verify your email address.

- - - - - -
- - - - - -
- - - - -
- Verify email address -
-
-
-

Once verified, you can continue your registration process.

- - - - - - - - - -
-
-

Button not working? Paste the following link into your browser: {{action_url}}

-
- -
-

You're receiving this email because you recently started a registration with {{product_name}}. If this wasn't you, please ignore this email.

-
diff --git a/packages/access-api/postmark/welcome.txt b/packages/access-api/postmark/welcome.txt deleted file mode 100644 index 405fa4050..000000000 --- a/packages/access-api/postmark/welcome.txt +++ /dev/null @@ -1,14 +0,0 @@ -Hi {{email}}! To complete your {{product_name}} registration, -we just need to verify your email address. - -Please visit the following link in your web browser: - -{{action_url}} - -Once verified, you can continue your registration process. - - ---- - -You're receiving this email because you recently started a registration with {{product_name}}. -If this wasn't you, please ignore this email. diff --git a/packages/access-api/readme.md b/packages/access-api/readme.md deleted file mode 100644 index 116a9bcca..000000000 --- a/packages/access-api/readme.md +++ /dev/null @@ -1,40 +0,0 @@ -


web3.storage

-

The access api for https://web3.storage

- -# Development - -Run the root [instructions](../../readme.md#setup-a-development-environment) first. -Review the repo-wide `.env` file copied in those steps and fill in real API keys for any services you plan to test against. - -```bash -# Run api locally -pnpm run dev - -# Lint and typecheck -pnpm run lint - -# Run tests -pnpm run test -``` - -The dev API server is currently hardcoded to listen on port 8787 on unspecified (i.e. typically: all) network interfaces. - -# Migrations - -## Create migration - -```bash -pnpm exec wrangler d1 migrations create __D1_BETA__ "" -``` - -This will create a new file inside the `migrations` folder where you can write SQL. - -# Sourcemaps - -Sourcemaps work locally but on deployed environments they won't because Cloudflare forces bundling while D1 is in beta. Check this setting [here](https://github.com/web3-storage/w3protocol/blob/187428e5ca5e8af5ac24bdeebf06199c3299f3af/packages/access-api/wrangler.toml#L11). - -Sentry sourcemaps also don't show the proper source stacktraces for the same reason. - -If you encounter a situation where sourcemaps don't work properly locally please open an issue and check if `no_bundle` to `true` in wrangler.toml helps. - -Tracking issue for this problem https://github.com/web3-storage/w3protocol/issues/112. diff --git a/packages/access-api/scripts/cli.js b/packages/access-api/scripts/cli.js deleted file mode 100755 index 67c1c8c4e..000000000 --- a/packages/access-api/scripts/cli.js +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env -S node --experimental-vm-modules --no-warnings -/* eslint-disable no-console */ -import path from 'path' -import dotenv from 'dotenv' -import fs from 'fs' -import sade from 'sade' -import { fileURLToPath } from 'url' -import { build } from 'esbuild' -import Sentry from '@sentry/cli' -import { createRequire } from 'module' -import { Log, LogLevel, Miniflare } from 'miniflare' - -// @ts-ignore -import git from 'git-rev-sync' -import { migrate } from './migrate.js' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const require = createRequire(__dirname) -const prog = sade('api') - -dotenv.config({ - path: path.join(__dirname, '..', '..', '..', '.env'), -}) - -const pkg = JSON.parse( - // eslint-disable-next-line unicorn/prefer-json-parse-buffer - fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8') -) - -/** @type {import('esbuild').Plugin} */ -const PluginAlias = { - name: 'alias', - setup(build) { - build.onResolve({ filter: /^stream$/ }, () => { - return { path: require.resolve('readable-stream') } - }) - - build.onResolve({ filter: /^node-fetch$/ }, () => { - return { path: path.resolve(__dirname, 'fetch.js') } - }) - build.onResolve({ filter: /^cross-fetch$/ }, () => { - return { path: path.resolve(__dirname, 'fetch.js') } - }) - }, -} -prog - .command('build') - .describe('Build the worker.') - .option('--env', 'Environment', 'dev') - .action(async (opts) => { - try { - const version = `${pkg.name}@${pkg.version}-${opts.env}+${git.short( - __dirname - )}`.replace('/', '__') - await build({ - entryPoints: [path.join(__dirname, '../src/index.js')], - bundle: true, - format: 'esm', - outfile: 'dist/worker.js', - legalComments: 'external', - inject: [path.join(__dirname, 'node-globals.js')], - plugins: [PluginAlias], - define: { - ACCOUNT_VERSION: JSON.stringify(version), - ACCOUNT_COMMITHASH: JSON.stringify(git.long(__dirname)), - ACCOUNT_BRANCH: JSON.stringify(git.branch(__dirname)), - global: 'globalThis', - }, - minify: opts.env !== 'dev', - sourcemap: 'external', - jsxImportSource: 'preact', - jsx: 'automatic', - loader: { '.js': 'jsx' }, - }) - - // Sentry release and sourcemap upload - if (process.env.SENTRY_UPLOAD === 'true') { - const cli = new Sentry(undefined, { - authToken: process.env.SENTRY_TOKEN, - org: 'protocol-labs-it', - project: 'w3access-api', - // @ts-ignore - dist: git.short(__dirname), - }) - - await cli.releases.new(version) - await cli.releases.setCommits(version, { - auto: true, - ignoreEmpty: true, - ignoreMissing: true, - }) - await cli.releases.uploadSourceMaps(version, { - include: ['./dist'], - urlPrefix: '/', - }) - await cli.releases.finalize(version) - await cli.releases.newDeploy(version, { - env: opts.env, - }) - } - } catch (error) { - console.error(error) - process.exit(1) - } - }) - -prog - .command('dev') - .describe('Start dev server.') - .action(async () => { - const mf = new Miniflare({ - packagePath: true, - wranglerConfigPath: true, - sourceMap: true, - modules: true, - watch: true, - envPath: path.resolve(__dirname, '../../../.env'), - log: new Log(LogLevel.DEBUG), // Enable --debug messages - }) - - const server = await mf.createServer() // Create http.Server instance - server.listen(8787, async () => { - const binds = await mf.getBindings() - const db = /** @type {D1Database} */ (binds.__D1_BETA__) - await migrate(db) - console.log('⚡️ Server listening on http://localhost:8787') - }) - }) -prog.parse(process.argv) diff --git a/packages/access-api/scripts/fetch.js b/packages/access-api/scripts/fetch.js deleted file mode 100644 index c3ee50e22..000000000 --- a/packages/access-api/scripts/fetch.js +++ /dev/null @@ -1 +0,0 @@ -export default globalThis.fetch.bind(globalThis) diff --git a/packages/access-api/scripts/migrate.js b/packages/access-api/scripts/migrate.js deleted file mode 100644 index b15732436..000000000 --- a/packages/access-api/scripts/migrate.js +++ /dev/null @@ -1,47 +0,0 @@ -import split from '@databases/split-sql-query' -import sql from '@databases/sql' -import path from 'path' -import { fileURLToPath } from 'url' -import fs from 'fs' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -/** @type {import('@databases/sql').FormatConfig} */ -const sqliteFormat = { - escapeIdentifier: (_) => '', - formatValue: (_, __) => ({ placeholder: '', value: '' }), -} - -// const files = globbySync(`${__dirname}/../migrations/*`) -const dir = path.resolve(`${__dirname}/../migrations`) - -const files = fs.readdirSync(dir) -const migrations = files.map((f) => sql.file(path.join(dir, f))) - -/** - * Migrate from migration files - * - * @param {D1Database} db - */ -export async function migrate(db) { - const appliedMigrations = /** @type {number} */ ( - await db.prepare('PRAGMA user_version').first('user_version') - ) - - migrations.splice(0, appliedMigrations) - const remaining = migrations.length - for (const m of migrations) { - /** @type {import('@databases/sql').SQLQuery[]} */ - // @ts-ignore - const qs = split.default(m) - await db.batch( - qs.map((q) => { - return db.prepare(q.format(sqliteFormat).text.replace(/^--.*$/gm, '')) - }) - ) - - await db - .prepare(`PRAGMA user_version = ${appliedMigrations + remaining}`) - .all() - } -} diff --git a/packages/access-api/scripts/node-globals.js b/packages/access-api/scripts/node-globals.js deleted file mode 100644 index f9c6a6960..000000000 --- a/packages/access-api/scripts/node-globals.js +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable unicorn/prefer-module */ -// @ts-nocheck -export const { Buffer } = require('buffer') -export const process = require('process/browser') diff --git a/packages/access-api/scripts/release.js b/packages/access-api/scripts/release.js deleted file mode 100644 index 5880f0cdf..000000000 --- a/packages/access-api/scripts/release.js +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable unicorn/prefer-top-level-await */ -/* eslint-disable no-console */ -/* eslint-disable unicorn/prefer-module */ -/** - * @file - * use this at build time (node.js) to create files that can be - * imported at runtime (maybe workerd). - */ -import sade from 'sade' -import * as url from 'node:url' -import { getReleaseName } from '../src/utils/release.node.js' -import path from 'path' -import { fileURLToPath } from 'url' -// @ts-ignore -import git from 'git-rev-sync' - -function __dirname() { - return path.dirname(fileURLToPath(import.meta.url)) -} - -if (import.meta.url.startsWith('file:')) { - const modulePath = url.fileURLToPath(import.meta.url) - if (process.argv[1] === modulePath) { - main().catch((error) => { - throw error - }) - } -} - -async function main(argv = process.argv) { - const cli = sade('access-api-release') - cli - .command('name', '', { default: true }) - .option('--env', 'Environment', process.env.ENV) - .action((opts) => { - const releaseName = getReleaseName(opts.env) - console.log(releaseName) - }) - cli - .command('esm', 'print release info as ES Module string') - .option('--env', 'Environment', process.env.ENV) - .action((opts) => { - const lines = [ - `/** @type {string|undefined} */`, - `export const gitRevShort = '${git.short(__dirname())}'`, - `/** @type {string|undefined} */`, - `export const name = '${getReleaseName(opts.env)}'`, - ] - console.log(lines.join('\n')) - }) - cli.parse(argv) -} diff --git a/packages/access-api/src/api.js b/packages/access-api/src/api.js deleted file mode 100644 index 5e95d5166..000000000 --- a/packages/access-api/src/api.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * This is file is shadowed by api.ts and only exists so JS could import it to - * refer to types without encountering runtime errors - * - * @see https://github.com/microsoft/TypeScript/issues/41825 - */ - -export {} diff --git a/packages/access-api/src/api.ts b/packages/access-api/src/api.ts deleted file mode 100644 index fdd85f475..000000000 --- a/packages/access-api/src/api.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable @typescript-eslint/method-signature-style */ -/* eslint-disable unicorn/prefer-export-from */ -import type { - DID, - DIDKey as SpaceDID, - Failure, - Unit, - Result, - ServiceMethod, - Match, - CapabilityParser, - ParsedCapability, - DelegationOptions, - InferInvokedCapability, -} from '@ucanto/interface' -import * as UploadAPI from './service/upload-api-proxy.js' -import * as AccessAPI from '@web3-storage/access/types' -import type { RouteContext } from './bindings.js' -import * as Capabilities from '@web3-storage/capabilities' -import type { ProviderInput } from '@ucanto/server' - -export * from '@web3-storage/access/types' -export * from '@ucanto/interface' -export * from '@web3-storage/capabilities/types' -export type { SpaceDID, Unit, RouteContext, DelegationOptions } -export type AccountDID = DID<'mailto'> -export type ServiceDID = DID<'web'> - -export type { ProvisionsStorage } from './types/provisions.js' -export type { DelegationsStorage } from './types/delegations.js' - -export type AllocationError = InsufficientStorage - -export interface InsufficientStorage extends Failure { - name: 'InsufficientStorage' -} - -export interface SpaceProviderRegistry { - hasStorageProvider(space: SpaceDID): Promise> -} - -export { AccessAPI, UploadAPI } -export interface Service extends AccessAPI.Service, UploadAPI.Service { - consumer: { - has: ServiceMethod< - InferInvokedCapability, - boolean, - Failure - > - } - customer: { - get: ServiceMethod< - InferInvokedCapability, - CustomerGetOk, - CustomerGetError - > - } - console: { - log: ServiceMethod< - InferInvokedCapability, - {}, - never - > - error: ServiceMethod< - InferInvokedCapability, - never, - Failure & { cause: unknown } - > - } -} - -export interface UnknownProvider extends Failure { - name: 'UnknownProvider' -} - -export type CustomerGetError = UnknownProvider - -export interface CustomerGetOk { - customer: null | { - did: AccountDID - } -} - -export type CustomerGetResult = Result - -export type Input>> = - ProviderInput & ParsedCapability> diff --git a/packages/access-api/src/bindings.js b/packages/access-api/src/bindings.js deleted file mode 100644 index 336ce12bb..000000000 --- a/packages/access-api/src/bindings.js +++ /dev/null @@ -1 +0,0 @@ -export {} diff --git a/packages/access-api/src/bindings.ts b/packages/access-api/src/bindings.ts deleted file mode 100644 index 73ee27b16..000000000 --- a/packages/access-api/src/bindings.ts +++ /dev/null @@ -1,214 +0,0 @@ -import type { Logging } from '@web3-storage/worker-utils/logging' -import type { Handler as _Handler } from '@web3-storage/worker-utils/router' -import { Spaces } from './models/spaces.js' -import { Validations } from './models/validations.js' -import { loadConfig } from './config.js' -import { ConnectionView, Signer as EdSigner } from '@ucanto/principal/ed25519' -import { Accounts } from './models/accounts.js' -import { DelegationsStorage as Delegations } from './types/delegations.js' -import { ProvisionsStorage } from './types/provisions.js' -import { R2Bucket } from '@miniflare/r2' -import { - DID, - Link, - Delegation, - Signature, - Block, - HTTPRequest, -} from '@ucanto/interface' -export * from '@ucanto/interface' - -export {} - -// CF Analytics Engine types not available yet -export interface AnalyticsEngine { - writeDataPoint: (event: AnalyticsEngineEvent) => void -} - -export interface AnalyticsEngineEvent { - readonly doubles?: number[] - readonly blobs?: Array -} - -export interface Email { - sendValidation: (input: { to: string; url: string }) => Promise - send: (input: { - to: string - textBody: string - subject: string - }) => Promise -} - -// We can't use interface here or it will not extend Record and cause type error -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type Env = { - // vars - ENV: string - DEBUG: string - /** - * publicly advertised decentralized identifier of the running api service - * * this may be used to filter incoming ucanto invocations - */ - DID: `did:web:${string}` - // URLs to upload-api so we proxy invocations to it - UPLOAD_API_URL: string - // secrets - PRIVATE_KEY: string - SENTRY_DSN: string - POSTMARK_TOKEN: string - POSTMARK_SENDER?: string - UCAN_LOG_URL?: string - UCAN_LOG_BASIC_AUTH?: string - - /** CSV DIDs of services that can be used to provision spaces. */ - PROVIDERS?: string - DEBUG_EMAIL?: string - LOGTAIL_TOKEN: string - // bindings - SPACES: KVNamespace - VALIDATIONS: KVNamespace - W3ACCESS_METRICS: AnalyticsEngine - /** - * will be used for storing env.models.delegations CARs - */ - DELEGATIONS_BUCKET: R2Bucket - // eslint-disable-next-line @typescript-eslint/naming-convention - __D1_BETA__: D1Database -} - -export interface HandlerContext { - waitUntil: (promise: Promise) => void -} - -export interface RouteContext { - log: Logging - signer: EdSigner.Signer - config: ReturnType - url: URL - email: Email - ucanLog: UCANLog - models: { - accounts: Accounts - delegations: Delegations - spaces: Spaces - provisions: ProvisionsStorage - validations: Validations - } - uploadApi: ConnectionView -} - -export interface UCANLog { - /** - * This can fail if it is unable to write to the underlying store. Handling - * invocations will be blocked until write is complete. Implementation may - * choose to do several retries before failing. - * - * @param request - Request in CAR containing invocations and receipts. - */ - log: (request: HTTPRequest) => Promise -} - -export interface Receipt { - ran: Link - out: ReceiptResult - meta: Record - iss?: DID - prf?: Array> - - s: Signature -} - -export interface ReceiptBlock extends Block { - data: Receipt -} - -/** - * Defines result type as per invocation spec - * - * @see https://github.com/ucan-wg/invocation/#6-result - */ - -export type ReceiptResult = Variant<{ - ok: T - error: X -}> - -/** - * Utility type for defining a [keyed union] type as in IPLD Schema. In practice - * this just works around typescript limitation that requires discriminant field - * on all variants. - * - * ```ts - * type Result = - * | { ok: T } - * | { error: X } - * - * const demo = (result: Result) => { - * if (result.ok) { - * // ^^^^^^^^^ Property 'ok' does not exist on type '{ error: Error; }` - * } - * } - * ``` - * - * Using `Variant` type we can define same union type that works as expected: - * - * ```ts - * type Result = Variant<{ - * ok: T - * error: X - * }> - * - * const demo = (result: Result) => { - * if (result.ok) { - * result.ok.toUpperCase() - * } - * } - * ``` - * - * [keyed union]:https://ipld.io/docs/schemas/features/representation-strategies/#union-keyed-representation - */ -export type Variant> = { - [Key in keyof U]: { [K in Exclude]?: never } & { - [K in Key]: U[Key] - } -}[keyof U] - -export type Handler = _Handler - -export type Bindings = Record< - string, - | string - | undefined - | KVNamespace - | DurableObjectNamespace - | CryptoKey - | D1Database - | AnalyticsEngine - | R2Bucket -> - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace ModuleWorker { - type FetchHandler = ( - request: Request, - env: Environment, - ctx: Pick - ) => Promise | Response - - type CronHandler = ( - event: Omit, - env: Environment, - ctx: Pick - ) => Promise | void -} - -export interface ModuleWorker { - fetch?: ModuleWorker.FetchHandler - scheduled?: ModuleWorker.CronHandler -} - -// D1 types - -export interface D1ErrorRaw extends Error { - cause: Error & { code: string } -} diff --git a/packages/access-api/src/config.js b/packages/access-api/src/config.js deleted file mode 100644 index e78fe288d..000000000 --- a/packages/access-api/src/config.js +++ /dev/null @@ -1,126 +0,0 @@ -// @ts-ignore -// eslint-disable-next-line no-unused-vars -import * as UCAN from '@ucanto/interface' -import { DID } from '@ucanto/core' -import * as release from './utils/release.build.js' - -/** - * Loads configuration variables from the global environment and returns a JS object - * keyed by variable names. - * - * @param {import("./bindings").Env} env - */ -export function loadConfig(env) { - /** @type Record */ - const vars = {} - - /** @type {Array} */ - const required = ['DID', 'ENV', 'DEBUG', 'PRIVATE_KEY', 'POSTMARK_TOKEN'] - - for (const name of required) { - const val = env[name] - if (typeof val === 'string' && val.length > 0) { - vars[name] = val - } else { - throw new Error( - `Missing required config variables: ${name}. Check your .env, testing globals or cloudflare vars.` - ) - } - } - - if (typeof env.DELEGATIONS_BUCKET !== 'object') { - throw new TypeError( - `expected env.DELEGATIONS_BUCKET to be an R2Bucket object, but got ${typeof env.DELEGATIONS_BUCKET}` - ) - } - - return { - DEBUG: boolValue(vars.DEBUG), - ENV: parseRuntimeEnv(vars.ENV), - - POSTMARK_TOKEN: vars.POSTMARK_TOKEN, - POSTMARK_SENDER: env.POSTMARK_SENDER, - SENTRY_DSN: env.SENTRY_DSN, - LOGTAIL_TOKEN: env.LOGTAIL_TOKEN, - UCAN_LOG_BASIC_AUTH: env.UCAN_LOG_BASIC_AUTH, - UCAN_LOG_URL: env.UCAN_LOG_URL, - - // These are injected in esbuild - // @ts-ignore - // eslint-disable-next-line no-undef - BRANCH: env.ACCOUNT_BRANCH ?? '', - // @ts-ignore - // eslint-disable-next-line no-undef - VERSION: env.ACCOUNT_VERSION ?? release.name ?? '', - // @ts-ignore - // eslint-disable-next-line no-undef - COMMITHASH: env.ACCOUNT_COMMITHASH ?? release.gitRevShort ?? '', - - PRIVATE_KEY: vars.PRIVATE_KEY, - DID: /** @type {UCAN.DID<"web">} */ (DID.parse(vars.DID).did()), - - /** DIDs of services that can be used to provision spaces. */ - PROVIDERS: env.PROVIDERS - ? env.PROVIDERS.split(',').map( - (id) => /** @type {UCAN.DID<"web">} */ (DID.parse(id).did()) - ) - : [/** @type {UCAN.DID<"web">} */ (DID.parse(vars.DID).did())], - - UPLOAD_API_URL: env.UPLOAD_API_URL || 'https://up.web3.storage/', - // bindings - METRICS: - /** @type {import("./bindings").AnalyticsEngine} */ ( - env.W3ACCESS_METRICS - ) || createAnalyticsEngine(), - SPACES: env.SPACES, - VALIDATIONS: env.VALIDATIONS, - DB: /** @type {D1Database} */ (env.__D1_BETA__), - DELEGATIONS_BUCKET: env.DELEGATIONS_BUCKET, - } -} - -/** - * Returns `true` if the string `s` is equal to `"true"` (case-insensitive) or `"1", and false for `"false"`, `"0"` or an empty value. - * - * @param {string} s - * @returns {boolean} - */ -function boolValue(s) { - return Boolean(s && JSON.parse(String(s).toLowerCase())) -} - -/** - * Validates that `s` is a defined runtime environment name and returns it. - * - * @param {unknown} s - */ -function parseRuntimeEnv(s) { - switch (s) { - case 'test': - case 'dev': - case 'staging': - case 'production': { - return s - } - default: { - throw new Error('invalid runtime environment name: ' + s) - } - } -} - -export function createAnalyticsEngine() { - /** @type {Map} */ - const store = new Map() - - return { - writeDataPoint: ( - /** @type {import("./bindings").AnalyticsEngineEvent} */ event - ) => { - store.set( - `${Date.now()}${(Math.random() + 1).toString(36).slice(7)}`, - event - ) - }, - _store: store, - } -} diff --git a/packages/access-api/src/index.js b/packages/access-api/src/index.js deleted file mode 100644 index 94a06b2a2..000000000 --- a/packages/access-api/src/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import { corsHeaders, preflight } from '@web3-storage/worker-utils/cors' -import { errorHandler } from '@web3-storage/worker-utils/error' -import { notFound } from '@web3-storage/worker-utils/response' -import { Router } from '@web3-storage/worker-utils/router' -import { postRoot, postJSON } from './routes/root.js' -import { preValidateEmail, validateEmail } from './routes/validate-email.js' -import { validateWS } from './routes/validate-ws.js' -import { version } from './routes/version.js' -import { getContext } from './utils/context.js' - -/** @type Router */ -const r = new Router({ onNotFound: notFound }) - -r.add('options', '*', preflight) -r.add('get', '/version', version) -r.add('get', '/validate-email', preValidateEmail) -r.add('post', '/validate-email', validateEmail) -r.add('get', '/validate-ws', validateWS) -r.add('post', '/', postRoot) -// From tests it appears that we expect requests and responses on this endpoint -// to be in application/json format. -r.add('post', '/raw', postJSON) -r.add('get', '/.debug/error', () => { - throw new Error('this is the result of handling a request at /.debug/error') -}) - -/** @type {import('./bindings.js').ModuleWorker} */ -export default { - fetch: async (request, env, ctx) => { - const context = getContext(request, env, ctx) - context.log.time('request') - try { - const rsp = await r.fetch(request, context, ctx) - return context.log.end(corsHeaders(request, rsp)) - } catch (error) { - return context.log.end( - corsHeaders( - request, - errorHandler(/** @type {Error} */ (error), context.log) - ) - ) - } - }, -} diff --git a/packages/access-api/src/models/accounts.js b/packages/access-api/src/models/accounts.js deleted file mode 100644 index ed8321de7..000000000 --- a/packages/access-api/src/models/accounts.js +++ /dev/null @@ -1,54 +0,0 @@ -import * as Ucanto from '@ucanto/interface' -import { Kysely } from 'kysely' -import { D1Dialect } from 'kysely-d1' -import { GenericPlugin } from '../utils/d1.js' - -/** - * Accounts - */ -export class Accounts { - /** - * @param {D1Database} d1 - */ - constructor(d1) { - const objectPlugin = new GenericPlugin({ - // eslint-disable-next-line unicorn/no-null - expires_at: (v) => (typeof v === 'string' ? new Date(v) : null), - inserted_at: (v) => new Date(v), - updated_at: (v) => new Date(v), - }) - this.d1 = - /** @type {Kysely<{ accounts: import('@web3-storage/access/src/types.js').AccountTable }>} */ ( - new Kysely({ - dialect: new D1Dialect({ database: d1 }), - plugins: [objectPlugin], - }) - ) - } - - /** - * @param {Ucanto.DID<'mailto'>} did - */ - async create(did) { - const result = await this.d1 - .insertInto('accounts') - .values({ - did, - }) - .onConflict((oc) => oc.column('did').doNothing()) - .returning('accounts.did') - .execute() - return { data: result } - } - - /** - * @param {Ucanto.DID<'mailto'>} did - */ - async get(did) { - return await this.d1 - .selectFrom('accounts') - .selectAll() - .where('accounts.did', '=', did) - .executeTakeFirst() - } -} diff --git a/packages/access-api/src/models/delegations.js b/packages/access-api/src/models/delegations.js deleted file mode 100644 index 568550081..000000000 --- a/packages/access-api/src/models/delegations.js +++ /dev/null @@ -1,223 +0,0 @@ -import * as Ucanto from '@ucanto/interface' -import { - delegationsToBytes, - bytesToDelegations, -} from '@web3-storage/access/encoding' -import { base32 } from 'multiformats/bases/base32' -import { CID } from 'multiformats' - -/** - * @typedef {import('../types/access-api-cf-db').R2Bucket} R2Bucket - */ - -/** - * @template {import('../types/access-api-cf-db').DelegationsV3Row} DelegationRow - * @typedef {Omit} DelegationRowUpdate - */ - -/** - * @template Tables - * @typedef {import("../types/database").Database} Database - */ - -export const delegationsV2TableName = /** @type {const} */ ('delegations_v2') - -/** - * indicates that processing failed due to encountering an unexpected delegation. - * e.g. if a delegation could not be parsed from underlying storage - */ -export class UnexpectedDelegation extends Error { - name = 'UnexpectedDelegation' -} - -/** - * @param {Ucanto.Delegation} d - * @returns {DelegationRowUpdate} - */ -export function createDelegationRowUpdateV3(d) { - return { - cid: d.cid.toString(), - audience: d.audience.did(), - issuer: d.issuer.did(), - } -} - -/** - * @param {Array | Buffer | unknown} sqlValue - value from kysely 'bytes' table - in node it could be a Buffer. In cloudflare it might be an Array - * @returns {ArrayBuffer|undefined} - undefined if unable to convert - */ -export function delegationsTableBytesToArrayBuffer(sqlValue) { - if (ArrayBuffer.isView(sqlValue)) { - return new Uint8Array( - sqlValue.buffer, - sqlValue.byteOffset, - sqlValue.byteLength - ) - } - if (Array.isArray(sqlValue)) { - return Uint8Array.from(sqlValue) - } -} - -export const delegationsV3Table = /** @type {const} */ (`delegations_v3`) - -/** - * @template {Database} DB - */ -export class DbDelegationsStorageWithR2 { - // @todo abstract away R2 specifics into DagStore: ~AsyncMap - /** @type {R2Bucket} */ - #dags - /** @type {DB} */ - #db - #delegationsTableName = delegationsV3Table - #getDagsKey = createDelegationsBucketKey - - /** - * @param {DB} db - * @param {R2Bucket} dags - */ - constructor(db, dags) { - this.#db = db - this.#dags = dags - // eslint-disable-next-line no-void - void ( - /** @type {import('../types/delegations').DelegationsStorage} */ (this) - ) - } - - /** - * store items - * - * @param {Array} delegations - * @returns {Promise} - */ - async putMany(...delegations) { - if (delegations.length === 0) { - return - } - await writeDelegations(this.#dags, delegations, this.#getDagsKey) - const values = delegations.map((d) => createDelegationRowUpdateV3(d)) - // @todo - if this fails, undo writeDelegations that dont need to be stored - await this.#db - .insertInto(this.#delegationsTableName) - .values(values) - .onConflict((oc) => oc.column('cid').doNothing()) - .execute() - } - - /** @returns {Promise} */ - async count() { - return count(this.#db, this.#delegationsTableName) - } - - /** - * @param {import('../types/delegations').Query} query - */ - async *find(query) { - const { audience } = query - const delegations = this.#delegationsTableName - const selection = await this.#db - .selectFrom(delegations) - .select(['cid']) - .where(`${delegations}.audience`, '=', audience) - .execute() - for await (const row of selection) { - yield this.#rowToDelegation(row) - } - } - - 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(this.#delegationsTableName) - .select(['cid']) - .stream()) { - yield this.#rowToDelegation(row) - } - } - - /** - * @param {Pick} row - * @param {R2Bucket} dags - * @param {(d: { cid: Ucanto.Delegation['cid'] }) => string} createKey - builds k/v key strings for each delegation - * @returns {Promise} - */ - async #rowToDelegation(row, dags = this.#dags, createKey = this.#getDagsKey) { - const cid = /** @type {Ucanto.UCANLink} */ (CID.parse(row.cid)) - const key = createKey({ cid }) - const carBytesR2 = await dags.get(key) - if (!carBytesR2) { - throw new Error(`failed to read car bytes for cid ${row.cid} key ${key}`) - } - const delegations = bytesToDelegations( - new Uint8Array(await carBytesR2.arrayBuffer()) - ) - const delegation = delegations.find((d) => d.cid.equals(cid)) - if (!delegation) { - throw new Error(`failed to parse delegation with expected cid ${row.cid}`) - } - return delegation - } -} - -/** - * @typedef {import('../types/access-api-cf-db').DelegationsV3Row} DelegationsV3Row - */ - -/** - * @template {string} TableName - * @template {Record} Tables - * @param {import('../types/database').Database} db - * @param {TableName} delegationsTable - * @returns {Promise} - count of table - */ -async function count(db, delegationsTable) { - const { size } = await db - .selectFrom(delegationsTable) - .select((e) => e.fn.count('cid').as('size')) - .executeTakeFirstOrThrow() - return BigInt(size) -} - -/** - * @param {{ cid: Ucanto.Delegation['cid'] }} ucan - */ -function createDelegationsBucketKey(ucan) { - const key = /** @type {const} */ ( - `/delegations/${ucan.cid.toString(base32)}.car` - ) - return key -} - -/** - * @param {R2Bucket} bucket - * @param {Iterable} delegations - * @param {(d: { cid: Ucanto.Delegation['cid'] }) => string} createKey - builds k/v key strings for each delegation - */ -async function writeDelegations(bucket, delegations, createKey) { - return writeEntries( - bucket, - [...delegations].map((delegation) => { - const key = createKey(delegation) - const carBytes = delegationsToBytes([delegation]) - const value = carBytes - return /** @type {[key: string, value: Uint8Array]} */ ([key, value]) - }) - ) -} - -/** - * @param {R2Bucket} bucket - * @param {Iterable} entries - */ -async function writeEntries(bucket, entries) { - await Promise.all([...entries].map(([key, value]) => bucket.put(key, value))) -} diff --git a/packages/access-api/src/models/provisions.js b/packages/access-api/src/models/provisions.js deleted file mode 100644 index 11224af37..000000000 --- a/packages/access-api/src/models/provisions.js +++ /dev/null @@ -1,291 +0,0 @@ -/* eslint-disable no-void */ -import { Failure } from '@ucanto/server' - -/** - * @template {import("@ucanto/interface").DID} ServiceId - * @typedef {import("../types/provisions").ProvisionsStorage} Provisions - */ - -/** - * @template {import("@ucanto/interface").DID} ServiceId - * @param {ServiceId[]} services - * @param {Array>} storage - * @returns {Provisions} - */ -export function createProvisions(services, storage = []) { - /** @type {Provisions['hasStorageProvider']} */ - const hasStorageProvider = async (consumerId) => { - const hasRowWithSpace = storage.some(({ space }) => space === consumerId) - return { ok: hasRowWithSpace } - } - /** @type {Provisions['put']} */ - const put = async (item) => { - storage.push(item) - return { - ok: {}, - } - } - /** @type {Provisions['count']} */ - const count = async () => { - return BigInt(storage.length) - } - return { - services, - count, - put, - hasStorageProvider, - } -} - -/** - * @typedef ProvisionsRow - * @property {string} cid - * @property {string} consumer - * @property {string} provider - * @property {string} sponsor - did of actor who authorized for this provision - */ - -/** - * @typedef {import("../types/database").Database<{ provisions: ProvisionsRow }>} ProvisionsDatabase - */ - -/** - * @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[]} services - * @param {ProvisionsDatabase} db - */ - constructor(services, db) { - this.services = services - this.#db = db - this.tableNames = { - provisions: /** @type {const} */ ('provisions'), - } - void (/** @type {Provisions} */ (this)) - } - - /** @type {Provisions['count']} */ - async count(...items) { - const { size } = await this.#db - .selectFrom(this.tableNames.provisions) - .select((e) => e.fn.count('provider').as('size')) - .executeTakeFirstOrThrow() - return BigInt(size) - } - - /** - * Selects all rows that match the query. - * - * @param {object} query - * @param {string} [query.space] - * @param {string} [query.provider] - * @param {string} [query.sponsor] - */ - async find(query = {}) { - const { provisions } = this.tableNames - let select = this.#db - .selectFrom(provisions) - .select(['cid', 'consumer', 'provider', 'sponsor']) - - if (query.space) { - select = select.where(`${provisions}.consumer`, '=', query.space) - } - - if (query.provider) { - select = select.where(`${provisions}.provider`, '=', query.provider) - } - - if (query.sponsor) { - select = select.where(`${provisions}.sponsor`, '=', query.sponsor) - } - - return await select.execute() - } - - /** @type {Provisions['put']} */ - async put(item) { - /** @type {ProvisionsRow} */ - const row = { - cid: item.invocation.cid.toString(), - consumer: item.space, - provider: item.provider, - sponsor: item.account, - } - - // We want to ensure that a space can not have provider of multiple types, - // e.g. a space can not have both a web3.storage and nft.storage providers - // otherwise it would be unclear where stored data should be added. - // Therefore we check look for any existing rows for this space, and if - // there is a row with a different provider, we error. - // Note that this does not give us transactional guarantees and in the face - // of concurrent requests, we may still end up with multiple providers - // however we soon intend to replace this table with one that has necessary - // constraints so we take this risk for now to avoid extra migration. - const matches = await this.find({ space: row.consumer }) - const conflict = matches.find((row) => row.provider !== item.provider) - if (conflict) { - return { - error: new ConflictError({ - message: `Space ${row.consumer} can not be provisioned with ${row.provider}, it already has a ${conflict.provider} provider`, - insertion: row, - existing: conflict, - }), - } - } - - /** @type {Array} */ - const rowColumns = ['cid', 'consumer', 'provider', 'sponsor'] - const insert = this.#db - .insertInto(this.tableNames.provisions) - .values(row) - .returning(rowColumns) - - let primaryKeyError - try { - await insert.executeTakeFirstOrThrow() - } catch (error) { - primaryKeyError = getCidUniquenessError(error) - if (!primaryKeyError) { - return { - error: new Failure(`Unexpected error inserting provision: ${error}`), - } - } - } - - if (!primaryKeyError) { - // no error inserting, we're done with put - return { - ok: {}, - } - } - - // there was already a row with this invocation cid - // as long as the row we tried to insert is same as one already there, no need to error. - // so let's compare the existing row with that cid to the row we tried to insert. - const existing = await this.#db - .selectFrom(this.tableNames.provisions) - .select(rowColumns) - .where('cid', '=', row.cid) - .executeTakeFirst() - - if (!existing) { - return { error: new Failure(`Unexpected error inserting provision`) } - } - - if (existing && deepEqual(existing, row)) { - // the insert failed, but the existing row is identical to the row that failed to insert. - // so the put is a no-op, and we can consider it a success despite encountering the primaryKeyError - return { ok: {} } - } - - // this is a sign of something very wrong. throw so error reporters can report on it - // and determine what led to a put() with same invocation cid but new non-cid column values - return { - error: new ConflictError({ - message: `Provision with cid ${item.invocation.cid} already exists with different field values`, - insertion: row, - existing, - }), - } - } - - /** @type {Provisions['hasStorageProvider']} */ - async hasStorageProvider(consumerDid) { - try { - const { provisions } = this.tableNames - const { size } = await this.#db - .selectFrom(provisions) - .select((e) => e.fn.count('provider').as('size')) - .where(`${provisions}.consumer`, '=', consumerDid) - .executeTakeFirstOrThrow() - return { ok: size > 0 } - } catch { - return { ok: false } - } - } - - /** - * @param {import("@ucanto/interface").DID<'key'>} consumer - */ - async findForConsumer(consumer) { - const { provisions } = this.tableNames - const rows = await this.#db - .selectFrom(provisions) - .selectAll() - .where(`${provisions}.consumer`, '=', consumer.toString()) - .execute() - return rows - } -} - -/** - * @param {Record} x - * @param {Record} y - * @returns {boolean} - */ -function deepEqual(x, y) { - const ok = Object.keys - const tx = typeof x - const ty = typeof y - return x && y && tx === 'object' && tx === ty - ? ok(x).length === ok(y).length && - ok(x).every((key) => deepEqual(x[key], y[key])) - : x === y -} - -/** - * @param {unknown} error - */ -function extractD1Error(error) { - const isD1 = /D1_(ALL_)?ERROR/.test(String(error)) - if (!isD1) return - const cause = - error && typeof error === 'object' && 'cause' in error && error.cause - const code = - (cause && - typeof cause === 'object' && - 'code' in cause && - typeof cause.code === 'string' && - cause.code) || - undefined - return { cause, code } -} - -class ConflictError extends Failure { - /** - * @param {object} input - * @param {string} input.message - * @param {unknown} input.insertion - * @param {unknown} input.existing - */ - constructor({ message, insertion, existing }) { - super(message) - this.name = 'ConflictError' - this.insertion = insertion - this.existing = existing - } -} - -/** - * return whether or not the provided parameter indicates an error - * writing provision to kysely database because there is already an entry - * for the written a cid - * - * @param {unknown} error - */ -function getCidUniquenessError(error) { - const d1Error = extractD1Error(error) - if (d1Error?.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') { - return d1Error - } else if ( - /UNIQUE constraint failed: provisions.cid/.test(String(d1Error?.cause)) - ) { - return d1Error - } -} diff --git a/packages/access-api/src/models/spaces.js b/packages/access-api/src/models/spaces.js deleted file mode 100644 index 1ac4159b0..000000000 --- a/packages/access-api/src/models/spaces.js +++ /dev/null @@ -1,125 +0,0 @@ -// eslint-disable-next-line no-unused-vars - -import * as API from '../api.js' -import { delegationToString } from '@web3-storage/access/encoding' -import { Kysely } from 'kysely' -import { D1Dialect } from 'kysely-d1' -import { D1Error, GenericPlugin } from '../utils/d1.js' - -/** - * @typedef {import('@web3-storage/access/src/types.js').SpaceTable} SpaceTable - */ - -/** - * Spaces - * - * @implements {API.SpaceProviderRegistry} - */ -export class Spaces { - /** - * - * @param {D1Database} d1 - */ - constructor(d1) { - /** @type {GenericPlugin>} */ - const objectPlugin = new GenericPlugin({ - metadata: (v) => { - // this will be `EMPTY` because it's the default value in the sql schema - // https://github.com/web3-storage/w3protocol/issues/447 - if (v === 'EMPTY') { - return - } - return JSON.parse(v) - }, - inserted_at: (v) => new Date(v), - updated_at: (v) => new Date(v), - }) - this.d1 = /** @type {Kysely<{ spaces: SpaceTable }>} */ ( - new Kysely({ - dialect: new D1Dialect({ database: d1 }), - plugins: [objectPlugin], - }) - ) - } - - /** - * @param {import('@web3-storage/capabilities/types').VoucherRedeem} capability - * @param {API.Invocation} invocation - * @param {API.Delegation<[import('@web3-storage/access/types').Top]> | undefined} delegation - */ - async create(capability, invocation, delegation) { - try { - const metadata = - /** @type {import('@web3-storage/access/types').SpaceTableMetadata | undefined} */ ( - /** @type {unknown} */ (invocation.facts[0]) - ) - - const result = await this.d1 - .insertInto('spaces') - .values({ - agent: invocation.issuer.did(), - did: /** @type {API.DIDKey} */ (capability.nb.space), - email: capability.nb.identity.replace('mailto:', ''), - invocation: delegationToString(invocation), - product: capability.nb.product, - metadata, - delegation: delegation ? delegationToString(delegation) : undefined, - }) - .returning('spaces.did') - .execute() - return { data: result } - } catch (error) { - return { - error: new D1Error( - /** @type {import('../bindings').D1ErrorRaw} */ (error) - ), - } - } - } - - /** - * Get space by DID - * - * @param {API.DIDKey} did - */ - async get(did) { - const space = await this.d1 - .selectFrom('spaces') - .selectAll() - .where('spaces.did', '=', did) - .executeTakeFirst() - - if (space) { - return space - } - } - - /** - * @param {API.DIDKey} did - */ - - async hasStorageProvider(did) { - try { - return { ok: Boolean(await this.get(did)) } - } catch { - return { ok: false } - } - } - - /** - * @param {API.URI<"mailto:">} email - */ - async getByEmail(email) { - const spaces = await this.d1 - .selectFrom('spaces') - .selectAll() - .where('spaces.email', '=', email.replace('mailto:', '')) - .execute() - - if (spaces.length === 0) { - return - } - - return spaces - } -} diff --git a/packages/access-api/src/models/validations.js b/packages/access-api/src/models/validations.js deleted file mode 100644 index 5f36865cb..000000000 --- a/packages/access-api/src/models/validations.js +++ /dev/null @@ -1,62 +0,0 @@ -import { stringToDelegation } from '@web3-storage/access/encoding' - -/** - * Validations - */ -export class Validations { - /** - * - * @param {KVNamespace} kv - */ - constructor(kv) { - this.kv = kv - } - - /** - * @template {import('@ucanto/interface').Capabilities} [T=import('@ucanto/interface').Capabilities] - * @param {import('@web3-storage/access/src/types').EncodedDelegation} ucan - */ - async put(ucan) { - const delegation = - /** @type {import('@ucanto/interface').Delegation} */ ( - stringToDelegation(ucan) - ) - - await this.kv.put(delegation.audience.did(), ucan, { - expiration: delegation.expiration, - }) - - return delegation - } - - /** - * @template {import('@ucanto/interface').Capabilities} T = import('@ucanto/interface').Capabilities - * @param {import('@web3-storage/access/src/types').EncodedDelegation} ucan - * @param {import('@ucanto/interface').DID} agent - * @param {number} expirationTtl - Expiration in second from now. Defaults to 5 mins. - */ - async putSession(ucan, agent, expirationTtl = 60 * 5) { - return await this.kv.put(agent, ucan, { - expirationTtl, - }) - } - - /** - * @param {string} did - */ - async get(did) { - const val = await this.kv.get(did) - if (!val) { - throw new Error('Validation not found') - } - - return val - } - - /** - * @param {string} did - */ - async delete(did) { - await this.kv.delete(did) - } -} diff --git a/packages/access-api/src/routes/root.js b/packages/access-api/src/routes/root.js deleted file mode 100644 index 41e808891..000000000 --- a/packages/access-api/src/routes/root.js +++ /dev/null @@ -1,117 +0,0 @@ -import * as Server from '@ucanto/server' -import * as CAR from '@ucanto/transport/car' -import * as Legacy from '@ucanto/transport/legacy' -import * as Codec from '@ucanto/transport/codec' -import { provide } from '../service/index.js' -import * as json from '../ucanto/server-codec.js' -import * as API from '../bindings.js' - -/** - * We define a ucanto codec that will switch encoder / decoder based on the - * `content-type` and `accept` headers of the request. - */ -const codec = Codec.inbound({ - decoders: { - // If the `content-type` is set to `application/vnd.ipld.car` use CAR codec. - [CAR.contentType]: CAR.request, - // If the `content-type` is set to `application/car` use legacy CAR codec - // which unlike current CAR codec used CAR roots to signal invocations. - [Legacy.contentType]: Legacy.request, - // If the `content-type` is set to `application/json` use JSON codec. - [json.contentType]: json, - }, - encoders: { - // Legacy clients did not set `accept` header so catch them using `*/*` - // and encode responses using legacy (CBOR) encoder. - '*/*;q=0.1': Legacy.response, - // Modern clients set `accept` header to `application/vnd.ipld.car` and - // we encode responses to them in CAR encoding. - [CAR.contentType]: CAR.response, - // If client sets `accept` header to `application/json` we encode responses - // using JSON codec. - [json.contentType]: json, - }, -}) - -/** - * @param {{headers: Record, body: Uint8Array}} request - * @param {API.RouteContext} env - * @param {API.HandlerContext} ctx - */ -export async function post({ headers, body }, env, ctx) { - const server = Server.create({ - id: env.signer, - codec, - service: provide(env), - catch: (/** @type {string | Error} */ err) => { - env.log.error(err) - }, - }) - - const channel = server.codec.accept({ headers, body }) - - // If we were unable to select a codec we do not support request encoding - // so we simply return an error response to the client. - if (channel.error) { - const { status, headers = {}, message } = channel.error - return new Response(message, { - status, - headers, - }) - } else { - const { encoder, decoder } = channel.ok - /** @type {API.AgentMessage} */ - const message = await decoder.decode({ headers, body }) - - // We block until we can log the UCAN invocation if this fails we return a 500 - // to the client. That is because in the future we expect that invocations will - // be written to a queue first and then processed asynchronously, so if we - // fail to enqueue the invocation we should fail the request. - await env.ucanLog.log(CAR.request.encode(message)) - - const result = await Server.execute(message, server) - - ctx.waitUntil(env.ucanLog.log(CAR.response.encode(result))) - - const { body: responseBody, ...response } = await encoder.encode(result) - return new Response(responseBody, response) - } -} - -/** - * Post request implicitly in JSON encoding. - * - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} request - * @param {API.RouteContext} env - * @param {API.HandlerContext} ctx - */ -export const postJSON = async (request, env, ctx) => - post( - { - headers: { - 'content-type': json.contentType, - accept: json.contentType, - ...Object.fromEntries(request.headers.entries()), - }, - body: new Uint8Array(await request.arrayBuffer()), - }, - env, - ctx - ) - -/** - * Post request in arbitrary encoding. - * - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} request - * @param {API.RouteContext} env - * @param {API.HandlerContext} ctx - */ -export const postRoot = async (request, env, ctx) => - post( - { - headers: Object.fromEntries(request.headers.entries()), - body: new Uint8Array(await request.arrayBuffer()), - }, - env, - ctx - ) diff --git a/packages/access-api/src/routes/validate-email.js b/packages/access-api/src/routes/validate-email.js deleted file mode 100644 index 03d885be8..000000000 --- a/packages/access-api/src/routes/validate-email.js +++ /dev/null @@ -1,178 +0,0 @@ -import { - delegationsToString, - stringToDelegation, -} from '@web3-storage/access/encoding' -import QRCode from 'qrcode' -import * as DidMailto from '@web3-storage/did-mailto' -import { - HtmlResponse, - ValidateEmail, - ValidateEmailError, - PendingValidateEmail, -} from '../utils/html.js' -import { Verifier } from '@ucanto/principal' -import * as delegationsResponse from '../utils/delegations-response.js' -import * as accessConfirm from '../service/access-confirm.js' - -/** - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} req - * @param {import('../bindings.js').RouteContext} env - */ -export async function preValidateEmail(req, env) { - if (!req.query?.ucan) { - return new HtmlResponse( - - ) - } - - return new HtmlResponse() -} - -/** - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} req - * @param {import('../bindings.js').RouteContext} env - */ -export async function validateEmail(req, env) { - if (req.query && req.query.ucan && req.query.mode === 'recover') { - return recover(req, env) - } - - if (req.query && req.query.ucan && req.query.mode === 'authorize') { - return authorize(req, env) - } - - if (req.query && req.query.ucan) { - try { - const delegation = await env.models.validations.put( - /** @type {import('@web3-storage/access/src/types.js').EncodedDelegation<[import('@web3-storage/access/src/types.js').VoucherClaim]>} */ ( - req.query.ucan - ) - ) - - return new HtmlResponse( - ( - - ) - ) - } catch (error) { - const err = /** @type {Error} */ (error) - - if (err.message.includes('Invalid expiration')) { - return new HtmlResponse( - - ) - } - - env.log.error(err) - return new HtmlResponse( - - ) - } - } - - return new HtmlResponse( - - ) -} - -/** - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} req - * @param {import('../bindings.js').RouteContext} env - */ -async function recover(req, env) { - try { - const delegation = await env.models.validations.put( - /** @type {import('@web3-storage/access/src/types.js').EncodedDelegation<[import('@web3-storage/access/src/types.js').SpaceRecover]>} */ ( - req.query.ucan - ) - ) - - return new HtmlResponse( - ( - - ) - ) - } catch (error) { - const err = /** @type {Error} */ (error) - - if (err.message.includes('Invalid expiration')) { - return new HtmlResponse( - - ) - } - - env.log.error(err) - return new HtmlResponse( - - ) - } -} - -/** - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} req - * @param {import('../bindings.js').RouteContext} env - */ -async function authorize(req, env) { - try { - /** - * @type {import('@ucanto/interface').Delegation<[import('@web3-storage/capabilities/src/types.js').AccessConfirm]>} - */ - const request = stringToDelegation(req.query.ucan) - - const confirm = accessConfirm.provide(env) - const confirmResult = await confirm(request, { - id: env.signer, - principal: Verifier, - }) - - if (confirmResult.error || !confirmResult.ok) { - throw new Error('error confirming', { - cause: confirmResult, - }) - } - const { account, agent } = accessConfirm.parse(request.capabilities[0]) - const confirmDelegations = [ - ...delegationsResponse.decode(confirmResult.ok.delegations), - ] - - // We render HTML page explaining to the user what has happened and providing - // a QR code in the details if they want to drill down. - return new HtmlResponse( - ( - - ) - ) - } catch (error) { - const err = /** @type {Error} */ (error) - env.log.error(err) - return new HtmlResponse( - , - { status: 500 } - ) - } -} diff --git a/packages/access-api/src/routes/validate-ws.js b/packages/access-api/src/routes/validate-ws.js deleted file mode 100644 index 514cae567..000000000 --- a/packages/access-api/src/routes/validate-ws.js +++ /dev/null @@ -1,60 +0,0 @@ -import pRetry from 'p-retry' - -const run = async ( - /** @type {import('../models/validations').Validations} */ kv, - /** @type {WebSocket} */ server, - /** @type {string} */ did -) => { - const d = await kv.get(did) - - server.send( - JSON.stringify({ - type: 'delegation', - delegation: d, - }) - ) - server.close() - await kv.delete(did) - return d -} - -/** - * @param {import('@web3-storage/worker-utils/router').ParsedRequest} req - * @param {import('../bindings.js').RouteContext} env - */ -export async function validateWS(req, env) { - const upgradeHeader = req.headers.get('Upgrade') - if (!upgradeHeader || upgradeHeader !== 'websocket') { - return new Response('Expected Upgrade: websocket', { status: 426 }) - } - - const [client, server] = Object.values(new WebSocketPair()) - // @ts-ignore - server.accept() - server.addEventListener('message', async (msg) => { - // @ts-ignore - const { did } = JSON.parse(msg.data) - - try { - await pRetry(() => run(env.models.validations, server, did), { - retries: 200, - minTimeout: 1000, - factor: 1, - }) - } catch (error) { - const err = /** @type {Error} */ (error) - server.send( - JSON.stringify({ - type: 'timeout', - error: err.message, - }) - ) - server.close() - } - }) - - return new Response(undefined, { - status: 101, - webSocket: client, - }) -} diff --git a/packages/access-api/src/routes/version.js b/packages/access-api/src/routes/version.js deleted file mode 100644 index fb9f3d498..000000000 --- a/packages/access-api/src/routes/version.js +++ /dev/null @@ -1,12 +0,0 @@ -import { JSONResponse } from '@web3-storage/worker-utils/response' - -/** @type {import('../bindings.js').Handler} */ -export async function version(event, env, ctx) { - return new JSONResponse({ - version: env.config.VERSION, - commit: env.config.COMMITHASH, - branch: env.config.BRANCH, - did: env.signer.did(), - publicKey: env.signer.verifier.toDIDKey(), - }) -} diff --git a/packages/access-api/src/service/access-claim.js b/packages/access-api/src/service/access-claim.js deleted file mode 100644 index 3ac86a1c9..000000000 --- a/packages/access-api/src/service/access-claim.js +++ /dev/null @@ -1,29 +0,0 @@ -import * as Server from '@ucanto/server' -import * as Access from '@web3-storage/capabilities/access' -import * as API from '../api.js' -import * as delegationsResponse from '../utils/delegations-response.js' -import { collect } from 'streaming-iterables' - -/** - * @typedef {object} Context - * @property {object} models - * @property {API.DelegationsStorage} models.delegations - * @param {Context} ctx - */ -export const provide = (ctx) => - Server.provide(Access.claim, (input) => claim(input, ctx)) - -/** - * @param {API.Input} input - * @param {Context} ctx - * @returns {Promise>} - */ -export const claim = async ({ invocation }, { models: { delegations } }) => { - const claimedAudience = invocation.capabilities[0].with - const claimed = await collect(delegations.find({ audience: claimedAudience })) - return { - ok: { - delegations: delegationsResponse.encode(claimed), - }, - } -} diff --git a/packages/access-api/src/service/access.js b/packages/access-api/src/service/access.js deleted file mode 100644 index 568515b00..000000000 --- a/packages/access-api/src/service/access.js +++ /dev/null @@ -1,15 +0,0 @@ -import * as API from '../api.js' -import * as Authorize from './access-authorize.js' -import * as Delegate from './access-delegate.js' -import * as Claim from './access-claim.js' -import * as Confirm from './access-confirm.js' - -/** - * @param {API.RouteContext} context - */ -export const provide = (context) => ({ - authorize: Authorize.provide(context), - delegate: Delegate.provide(context), - claim: Claim.provide(context), - confirm: Confirm.provide(context), -}) diff --git a/packages/access-api/src/service/consumer.js b/packages/access-api/src/service/consumer.js deleted file mode 100644 index 7aaefa6e1..000000000 --- a/packages/access-api/src/service/consumer.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as API from '../api.js' -import * as Has from './consumer/has.js' - -/** - * @param {API.RouteContext} context - */ -export const provide = (context) => ({ - has: Has.provide(context), -}) diff --git a/packages/access-api/src/service/customer.js b/packages/access-api/src/service/customer.js deleted file mode 100644 index a7f58600a..000000000 --- a/packages/access-api/src/service/customer.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as API from '../api.js' -import * as Get from './customer/get.js' - -/** - * @param {API.RouteContext} context - */ -export const provide = (context) => ({ - get: Get.provide(context), -}) diff --git a/packages/access-api/src/service/delegations.js b/packages/access-api/src/service/delegations.js deleted file mode 100644 index 0e99544ba..000000000 --- a/packages/access-api/src/service/delegations.js +++ /dev/null @@ -1,56 +0,0 @@ -import * as Ucanto from '@ucanto/interface' - -// without this alias, eslint jsdoc/check-types will complain below -/* eslint-disable jsdoc/check-types */ -/** - * @typedef {typeof Symbol.iterator} SymbolIterator - */ -/* eslint-enable jsdoc/check-types */ - -/** - * DelegationsStorage that stores in-memory. - * - * @param {Pick, 'length' | 'push' | SymbolIterator>} storage - * @returns {import("../types/delegations").DelegationsStorage} - */ -export function createDelegationsStorage(storage = []) { - /** @type {import("../types/delegations").DelegationsStorage[typeof Symbol.asyncIterator]} */ - async function* asyncIterator() { - for (const delegation of storage) { - yield delegation - } - } - /** @type {import("../types/delegations").DelegationsStorage['count']} */ - async function count() { - return BigInt(storage.length) - } - /** @type {import("../types/delegations").DelegationsStorage['find']} */ - async function* find(query) { - for (const d of storage) { - if (d.audience.did() === query.audience) { - yield d - } - } - } - /** @type {import("../types/delegations").DelegationsStorage['putMany']} */ - async function putMany(...args) { - return storage.push(...args) - } - /** @type {import('../types/delegations').DelegationsStorage} */ - const delegations = { - [Symbol.asyncIterator]: asyncIterator, - count, - find, - putMany, - } - return delegations -} - -/** - * Given array of delegations, return a valid value for access/delegate nb.delegations - * - * @param {Array} delegations - */ -export function toDelegationsDict(delegations) { - return Object.fromEntries(delegations.map((d) => [d.cid.toString(), d.cid])) -} diff --git a/packages/access-api/src/service/index.js b/packages/access-api/src/service/index.js deleted file mode 100644 index 693da77a7..000000000 --- a/packages/access-api/src/service/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import * as API from '../api.js' -import * as uploadApi from './upload-api-proxy.js' -import * as Access from './access.js' -import * as Consumer from './consumer.js' -import * as Customer from './customer.js' -import * as Provider from './provider.js' -import * as Voucher from './voucher.js' -import * as Space from './space.js' -import * as Console from './console.js' - -/** - * @param {API.RouteContext} ctx - * @returns {API.Service} - */ -export const provide = (ctx) => ({ - store: uploadApi.createStoreProxy(ctx), - upload: uploadApi.createUploadProxy(ctx), - access: Access.provide(ctx), - provider: Provider.provide(ctx), - voucher: Voucher.provide(ctx), - space: Space.provide(ctx), - consumer: Consumer.provide(ctx), - customer: Customer.provide(ctx), - console: Console.provide(ctx), -}) diff --git a/packages/access-api/src/service/provider.js b/packages/access-api/src/service/provider.js deleted file mode 100644 index e2275d8a6..000000000 --- a/packages/access-api/src/service/provider.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as API from '../api.js' -import * as Add from './provider-add.js' - -/** - * @param {API.RouteContext} context - */ -export const provide = (context) => ({ - add: Add.provide(context), -}) diff --git a/packages/access-api/src/service/space.js b/packages/access-api/src/service/space.js deleted file mode 100644 index 7c09e85be..000000000 --- a/packages/access-api/src/service/space.js +++ /dev/null @@ -1,162 +0,0 @@ -import { Space } from '@web3-storage/capabilities' -import { DID, fail } from '@ucanto/validator' -import { - delegationToString, - stringToDelegation, -} from '@web3-storage/access/encoding' -import * as Provider from '@ucanto/server' -import * as API from '../api.js' - -/** - * @param {API.Input} input - * @param {API.RouteContext} ctx - * @returns {Promise>} - */ -export const info = async ({ capability }, ctx) => { - const { provisions, spaces } = ctx.models - - const spaceDid = capability.with - if (!DID.match({ method: 'key' }).is(spaceDid)) { - /** @type {API.SpaceUnknown} */ - const unexpectedSpaceDidFailure = { - name: 'SpaceUnknown', - message: `can only get info for did:key spaces`, - } - return { - error: unexpectedSpaceDidFailure, - } - } - - const result = await provisions.hasStorageProvider(spaceDid) - if (result.ok) { - return { - ok: { did: spaceDid }, - } - } - - // this only exists if the space was registered via voucher/redeem - const space = await spaces.get(capability.with) - if (!space) { - /** @type {import('@web3-storage/access/types').SpaceUnknown} */ - const spaceUnknownFailure = { - name: 'SpaceUnknown', - message: `Space not found.`, - } - return { - error: spaceUnknownFailure, - } - } - - return { ok: space } -} - -/** - * @param {API.Input} input - * @param {API.RouteContext} ctx - * @returns {Promise>} - */ -export const recover = async ({ capability, invocation }, ctx) => { - if (capability.with !== ctx.signer.did()) { - return fail( - `Resource ${ - capability.with - } does not match service did ${ctx.signer.did()}` - ) - } - const spaces = await ctx.models.spaces.getByEmail(capability.nb.identity) - if (!spaces) { - return fail(`No delegations found for ${capability.nb.identity}`) - } - const results = [] - for (const { delegation, metadata } of spaces) { - if (delegation) { - const proof = stringToDelegation( - /** @type {API.EncodedDelegation<[API.Top]>} */ (delegation) - ) - - const del = await Space.top.delegate({ - audience: invocation.issuer, - issuer: ctx.signer, - with: proof.capabilities[0].with, - expiration: Infinity, - proofs: [proof], - // @ts-ignore - facts: metadata ? [metadata] : undefined, - }) - results.push(delegationToString(del)) - } - } - return { - ok: results, - } -} - -/** - * @param {API.Input} input - * @param {API.RouteContext} ctx - * @returns {Promise>} - */ -export const recoverValidation = async ({ capability }, ctx) => { - // check if we have delegations in the KV for the email - // if yes send email with space/recover - // if not error "no spaces for email X" - - const spaces = await ctx.models.spaces.getByEmail(capability.nb.identity) - if (!spaces) { - return fail( - `No spaces found for email: ${capability.nb.identity.replace( - 'mailto:', - '' - )}.` - ) - } - - const inv = await Space.recover - .invoke({ - issuer: ctx.signer, - audience: Provider.DID.parse(capability.with), - with: ctx.signer.did(), - lifetimeInSeconds: 60 * 10, - nb: { - identity: capability.nb.identity, - }, - proofs: [ - await Space.recover.delegate({ - audience: ctx.signer, - issuer: ctx.signer, - expiration: Infinity, - with: ctx.signer.did(), - nb: { - identity: 'mailto:*', - }, - }), - ], - }) - .delegate() - - const encoded = delegationToString(inv) - const url = `${ctx.url.protocol}//${ctx.url.host}/validate-email?ucan=${encoded}&mode=recover` - - // For testing - if (ctx.config.ENV === 'test') { - return { ok: url } - } - - await ctx.email.sendValidation({ - to: capability.nb.identity.replace('mailto:', ''), - url, - }) - - return { ok: '' } -} - -/** - * @param {API.RouteContext} ctx - */ -export const provide = (ctx) => ({ - info: Provider.provide(Space.info, (input) => info(input, ctx)), - recover: Provider.provide(Space.recover, (input) => recover(input, ctx)), - 'recover-validation': Provider.provide(Space.recoverValidation, (input) => - recoverValidation(input, ctx) - ), -}) diff --git a/packages/access-api/src/service/upload-api-proxy.js b/packages/access-api/src/service/upload-api-proxy.js deleted file mode 100644 index a3fa091d3..000000000 --- a/packages/access-api/src/service/upload-api-proxy.js +++ /dev/null @@ -1,82 +0,0 @@ -import * as Client from '@ucanto/client' -import { CAR } from '@ucanto/transport' -import { DID } from '@ucanto/core' -import * as HTTP from '@ucanto/transport/http' -import * as API from '../api.js' -import { createProxyHandler } from '../ucanto/proxy.js' - -/** - * @typedef {import('../ucanto/types.js').InferService>} StoreServiceInferred - * @typedef {import('../ucanto/types.js').InferService>} UploadServiceInferred - * @typedef {{ store: StoreServiceInferred, upload: UploadServiceInferred }} Service - */ - -/** - * @template {string|number|symbol} M - * @template {API.ConnectionView} [Connection=API.ConnectionView] - * @param {object} options - * @param {Array} options.methods - * @param {{ default: Connection } & Record} options.connections - */ -function createProxyService(options) { - const handleInvocation = createProxyHandler(options) - // eslint-disable-next-line unicorn/no-array-reduce - const service = options.methods.reduce((obj, method) => { - obj[method] = handleInvocation - return obj - }, /** @type {Record>} */ ({})) - return service -} - -/** - * @typedef UcantoHttpConnectionOptions - * @property {API.UCAN.DID} audience - * @property {typeof globalThis.fetch} options.fetch - * @property {URL} options.url - */ - -/** - * Create a ucanto connection to an upload api url. - * Assumes upload-api at that URL decodes requests as CAR and encodes responses as CBOR. - * - * @param {UcantoHttpConnectionOptions} options - * @returns {API.ConnectionView} - */ -export function createUploadApiConnection(options) { - return Client.connect({ - id: DID.parse(options.audience), - codec: CAR.outbound, - channel: HTTP.open({ - fetch: options.fetch, - url: options.url, - }), - }) -} - -/** - * @param {object} options - * @param {API.RouteContext['uploadApi']} options.uploadApi - */ -export function createUploadProxy(options) { - return createProxyService({ - ...options, - connections: { - default: options.uploadApi, - }, - methods: ['list', 'add', 'remove', 'upload'], - }) -} - -/** - * @param {object} options - * @param {API.RouteContext['uploadApi']} options.uploadApi - */ -export function createStoreProxy(options) { - return createProxyService({ - ...options, - connections: { - default: options.uploadApi, - }, - methods: ['list', 'add', 'remove', 'store'], - }) -} diff --git a/packages/access-api/src/service/voucher-claim.js b/packages/access-api/src/service/voucher-claim.js deleted file mode 100644 index 29fc1a776..000000000 --- a/packages/access-api/src/service/voucher-claim.js +++ /dev/null @@ -1,58 +0,0 @@ -import * as API from '../api.js' -import * as Provider from '@ucanto/server' -import * as Voucher from '@web3-storage/capabilities/voucher' -import { delegationToString } from '@web3-storage/access/encoding' - -/** - * @param {API.RouteContext} ctx - */ -export const provide = (ctx) => - Provider.provide(Voucher.claim, (input) => claim(input, ctx)) - -/** - * @param {API.Input} input - * @param {API.RouteContext} ctx - */ -export const claim = async ({ capability, invocation }, ctx) => { - const proof = await Voucher.redeem.delegate({ - audience: ctx.signer, - issuer: ctx.signer, - expiration: Infinity, - with: ctx.signer.did(), - nb: { - product: 'product:*', - identity: 'mailto:*', - space: 'did:*', - }, - }) - - const inv = await Voucher.redeem - .invoke({ - issuer: ctx.signer, - audience: invocation.issuer, - with: ctx.signer.did(), - lifetimeInSeconds: 60 * 10, // 10 mins - nb: { - space: capability.with, - identity: capability.nb.identity, - product: capability.nb.product, - }, - proofs: [proof], - }) - .delegate() - - const encoded = delegationToString(inv) - // For testing - if (ctx.config.ENV === 'test') { - return { ok: encoded } - } - - const url = `${ctx.url.protocol}//${ctx.url.host}/validate-email?ucan=${encoded}` - - await ctx.email.sendValidation({ - to: capability.nb.identity.replace('mailto:', ''), - url, - }) - - return { ok: '' } -} diff --git a/packages/access-api/src/service/voucher-redeem.js b/packages/access-api/src/service/voucher-redeem.js deleted file mode 100644 index f983c3839..000000000 --- a/packages/access-api/src/service/voucher-redeem.js +++ /dev/null @@ -1,100 +0,0 @@ -import * as API from '../api.js' -import * as Provider from '@ucanto/server' -import * as Voucher from '@web3-storage/capabilities/voucher' -import { Delegation } from '@ucanto/core' -import { Failure } from '@ucanto/server' -import { D1Error } from '../utils/d1.js' - -/** - * @param {API.RouteContext} ctx - */ -export const provide = (ctx) => - Provider.provide(Voucher.redeem, (input) => redeem(input, ctx)) - -/** - * @param {API.Input} input - * @param {API.RouteContext} ctx - */ -export const redeem = async ({ capability, invocation }, ctx) => { - if (capability.with !== ctx.signer.did()) { - return { - error: new Failure( - `Resource ${ - capability.with - } does not match service did ${ctx.signer.did()}` - ), - } - } - - /** @type {API.Delegation[]} */ - const delegations = [] - // We should only save delegation for email identities - if (capability.nb.identity.startsWith('mailto:')) { - for (const p of invocation.proofs) { - if ( - Delegation.isDelegation(p) && - p.audience.did() === ctx.signer.did() && - p.capabilities[0].with === capability.nb.space && - p.capabilities[0].can === '*' - ) { - delegations.push(p) - } - } - } - - if (delegations.length > 1) { - return { - error: new Failure('Multiple space delegations not suppported.'), - } - } - - const { error } = await ctx.models.spaces.create( - capability, - // @ts-ignore - TODO fix this - invocation, - delegations[0] - ) - - if (error) { - if (isSpaceAlreadyRegisteredError(error)) { - return { - error: new Failure(`Space ${capability.nb.space} already registered.`), - } - } else { - throw error - } - } - - ctx.config.METRICS.writeDataPoint({ - blobs: [ctx.config.ENV, 'new_space_v1'], - doubles: [1], - }) - - if (ctx.config.ENV === 'production') { - ctx.email.send({ - to: 'david@dag.house,jchris@dag.house', - subject: 'New Space Created', - textBody: `New space v1 registered for ${ - capability.nb.space - } with email ${capability.nb.identity.replace('mailto:', '')}`, - }) - } - - return { ok: {} } -} - -/** - * @param {D1Error} error - */ -function isSpaceAlreadyRegisteredError(error) { - if ('code' in error && error.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') { - return true - } - if ( - 'cause' in error && - /UNIQUE constraint failed: spaces.did/.test(String(error.cause)) - ) { - return true - } - return false -} diff --git a/packages/access-api/src/service/voucher.js b/packages/access-api/src/service/voucher.js deleted file mode 100644 index 63a5609b3..000000000 --- a/packages/access-api/src/service/voucher.js +++ /dev/null @@ -1,11 +0,0 @@ -import * as API from '../api.js' -import * as Claim from './voucher-claim.js' -import * as Redeem from './voucher-redeem.js' - -/** - * @param {API.RouteContext} context - */ -export const provide = (context) => ({ - claim: Claim.provide(context), - redeem: Redeem.provide(context), -}) diff --git a/packages/access-api/src/types/access-api-cf-db.ts b/packages/access-api/src/types/access-api-cf-db.ts deleted file mode 100644 index c67667383..000000000 --- a/packages/access-api/src/types/access-api-cf-db.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ColumnType, Generated } from 'kysely' - -export { R2Bucket } from '@miniflare/r2' - -// v3 -// * dropped 'bytes' column in favor of storing them in R2 (see DbDelegationsStorageWithR2) - -export interface DelegationsV3Row { - cid: string - audience: `did:${string}` - issuer: `did:${string}` - expires_at: Date | null - inserted_at: Generated - updated_at: ColumnType -} - -export interface DelegationsV3Tables { - delegations_v3: DelegationsV3Row -} diff --git a/packages/access-api/src/types/database.ts b/packages/access-api/src/types/database.ts deleted file mode 100644 index bdaa04929..000000000 --- a/packages/access-api/src/types/database.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Kysely } from 'kysely' - -export type Database = Kysely & { - /** - * whether or not this Databse supports Kysely stream() asyncIterator - * (kysely-d1 dialect does not) - */ - canStream: boolean -} diff --git a/packages/access-api/src/types/delegations.ts b/packages/access-api/src/types/delegations.ts deleted file mode 100644 index b72b439c4..000000000 --- a/packages/access-api/src/types/delegations.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Ucanto from '@ucanto/interface' - -interface ByAudience { - audience: Ucanto.DID<'key' | 'mailto'> -} -export type Query = ByAudience - -export interface DelegationsStorage< - Cap extends Ucanto.Capability = Ucanto.Capability -> { - /** - * write several items into storage - * - * @param delegations - delegations to store - */ - putMany: ( - ...delegations: Array>> - ) => Promise - - /** - * get number of stored items - */ - count: () => Promise - - /** - * iterate through all stored items - */ - [Symbol.asyncIterator]: () => AsyncIterableIterator< - Ucanto.Delegation> - > - - /** - * find all items that match the query - */ - find: (query: Query) => AsyncIterable>> -} diff --git a/packages/access-api/src/types/provisions.ts b/packages/access-api/src/types/provisions.ts deleted file mode 100644 index c886bc623..000000000 --- a/packages/access-api/src/types/provisions.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as Ucanto from '@ucanto/interface' -import { ProviderAdd } from '@web3-storage/capabilities/src/types' - -/** - * action which results in provisionment of a space consuming a storage provider - */ -export interface Provision> { - invocation: Ucanto.Invocation - space: Ucanto.DID<'key'> - account: Ucanto.DID<'mailto'> - provider: ServiceDID -} - -/** - * stores instances of a storage provider being consumed by a consumer - */ -export interface ProvisionsStorage< - ServiceDID extends Ucanto.DID<'web'> = Ucanto.DID<'web'> -> { - services: ServiceDID[] - hasStorageProvider: ( - consumer: Ucanto.DID<'key'> - ) => Promise> - /** - * ensure item is stored - * - * @param item - provision to store - */ - put: ( - item: Provision - ) => Promise> - - /** - * get number of stored items - */ - count: () => Promise -} diff --git a/packages/access-api/src/types/ucanto.ts b/packages/access-api/src/types/ucanto.ts deleted file mode 100644 index 5bc5ea5d3..000000000 --- a/packages/access-api/src/types/ucanto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as Ucanto from '@ucanto/interface' - -export type ServiceInvoke< - Service extends Record, - InvocationCapabilities extends Ucanto.Capability = Ucanto.Capability -> = ( - invocation: Ucanto.ServiceInvocation -) => Promise> diff --git a/packages/access-api/src/ucanto/proxy.js b/packages/access-api/src/ucanto/proxy.js deleted file mode 100644 index 7d1c37071..000000000 --- a/packages/access-api/src/ucanto/proxy.js +++ /dev/null @@ -1,81 +0,0 @@ -// eslint-disable-next-line no-unused-vars -import * as Ucanto from '@ucanto/interface' -import * as Client from '@ucanto/client' - -const BadGatewayHTTPErrorResult = { - /** - * Given unknown error, detect whether it is an upstream HTTPError. - * If so, return a result object indicating generic 'Bad Gateway'. - * Otherwise, if error is not a bad gateway error, return undefined - * - * @param {unknown} error - error encountered when proxying a ucanto invocation to an upstream url - */ - catch(error) { - if (!error || typeof error !== 'object') { - return { ok: {} } - } - const status = 'status' in error ? Number(error.status) : undefined - const isServerError = status !== undefined && status >= 500 && status < 600 - if (!isServerError) { - return { ok: {} } - } - - return { - error: { - status: 502, - statusText: 'Bad Gateway', - 'x-proxy-error': error, - }, - } - }, -} - -/** - * default catchInvocationError value for createProxyHandler. - * It catches `HTTPError` errors to an error result with status=502 and statusText='Bad Gateway' - * - * @param {unknown} error - */ -function defaultCatchInvocationError(error) { - const badGatewayResult = BadGatewayHTTPErrorResult.catch(error) - if (badGatewayResult) { - return badGatewayResult - } - throw error -} - -/** - * @template {Ucanto.ConnectionView} [Connection=Ucanto.ConnectionView] - * @param {object} options - * @param {(error: unknown) => Promise} [options.catchInvocationError] - catches any error that comes from invoking the proxy invocation on the connection. If it returns a value, that value will be the proxied invocation result. - * @param {{ default: Connection, [K: Ucanto.UCAN.DID]: Connection }} options.connections - */ -export function createProxyHandler(options) { - /** - * @template {import('@ucanto/interface').Capability} Capability - * @param {Ucanto.Invocation} invocation - * @param {Ucanto.InvocationContext} context - * @returns {Promise>} - */ - return async function handleInvocation(invocation, context) { - const { connections, catchInvocationError = defaultCatchInvocationError } = - options - const connection = - connections[invocation.audience.did()] ?? connections.default - try { - const [result] = await Client.execute( - [await invocation.delegate()], - connection - ) - - return result.out - } catch (error) { - if (catchInvocationError) { - const caughtResult = await catchInvocationError(error) - - return caughtResult - } - throw error - } - } -} diff --git a/packages/access-api/src/ucanto/server-codec.js b/packages/access-api/src/ucanto/server-codec.js deleted file mode 100644 index 0cb1e2361..000000000 --- a/packages/access-api/src/ucanto/server-codec.js +++ /dev/null @@ -1,148 +0,0 @@ -import { Message, Invocation } from '@ucanto/core' -import * as UCAN from '@ipld/dag-ucan' -import { UTF8 } from '@ucanto/transport' -import { HTTPError } from '@web3-storage/worker-utils/error' -import * as API from '../api.js' - -export const contentType = 'application/json' - -const HEADERS = Object.freeze({ - 'content-type': 'application/json', -}) - -/** - * Split multi value headers into an array - * - * @param {Headers} headers - * @param {string} name - */ -function multiValueHeader(headers, name) { - const out = headers - .get(name) - ?.split(',') - .map((v) => v.trimStart()) - - return out || [] -} - -/** - * @param {Record | Headers} headers - */ -async function parseHeaders(headers) { - const h = new Headers(headers) - - try { - /** @type Map */ - const proofs = new Map() - const proofsRaw = multiValueHeader(h, 'ucan') - - for (const cidUcan of proofsRaw) { - const [cid, ucan] = cidUcan.trim().split(/\s+/) - const ucanView = UCAN.parse(/** @type {UCAN.JWT} */ (ucan)) - const block = await UCAN.write(ucanView) - - if (cid !== block.cid.toString()) { - throw new TypeError( - `Invalid request, proof with cid ${block.cid.toString()} has mismatching cid ${cid} in the header` - ) - } - - proofs.set(cid, block) - } - - /** @type {UCAN.View[]} */ - const ucans = [] - const auths = multiValueHeader(h, 'Authorization') - - for (const auth of auths) { - if (auth.toLowerCase().startsWith('bearer ')) { - const ucanView = UCAN.parse( - /** @type {UCAN.JWT} */ (auth.slice(7)) - ) - ucans.push(ucanView) - } - } - - return { proofs, ucans } - } catch (error) { - throw new HTTPError('Malformed UCAN headers data.', { - status: 401, - cause: /** @type {Error} */ (error), - }) - } -} - -/** - * Decodes `AgentMessage` from the received `HTTPRequest`. - * - * @template {import('@ucanto/interface').AgentMessage} Message - * @param {import('@ucanto/interface').HTTPRequest} request - * @returns {Promise} - */ -export const decode = async ({ body, headers }) => { - const headersData = await parseHeaders(headers) - if (headersData.ucans.length === 0) { - throw new HTTPError( - 'The required "Authorization: Bearer" header is missing.', - { status: 400 } - ) - } - - const invocations = [] - - // Iterate ucan invocations from the headers - for (const ucanView of headersData.ucans) { - const blocks = new Map() - const missing = [] - - // Check all the proofs for each invocation - for (const proofCID of ucanView.proofs) { - const proof = headersData.proofs.get(proofCID.toString()) - if (!proof) { - missing.push(proofCID.toString()) - } - - blocks.set(proofCID.toString(), proof) - // TODO implement caching of proofs https://github.com/ucan-wg/ucan-as-bearer-token#32-cache-and-expiry - } - - if (missing.length > 0) { - throw new HTTPError('Missing Proofs', { - status: 510, - // @ts-ignore - Error.cause type is a mess - cause: { prf: missing }, - }) - } - - // Build the full ucan chain for each invocation from headers data - invocations.push( - Invocation.create({ root: await UCAN.write(ucanView), blocks }) - ) - } - - const message = await Message.build({ - // eslint-disable-next-line object-shorthand - invocations: /** @type {API.Tuple} */ (invocations), - }) - return /** @type {Message} */ (message) -} - -/** - * Encodes `AgentMessage` into an `HTTPRequest`. - * - * @template {import('@ucanto/interface').AgentMessage} Message - * @param {Message} message - * @returns {import('@ucanto/interface').HTTPRequest} - */ -export const encode = (message) => { - /** @type {Record} */ - const outs = {} - for (const receipt of message.receipts.values()) { - outs[receipt.ran.link().toString()] = receipt.out - } - - return { - headers: Object.fromEntries(new Headers(HEADERS).entries()), - body: UTF8.encode(JSON.stringify(outs)), - } -} diff --git a/packages/access-api/src/ucanto/types.ts b/packages/access-api/src/ucanto/types.ts deleted file mode 100644 index d2ed202d2..000000000 --- a/packages/access-api/src/ucanto/types.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - InferInvokedCapability, - Match, - ParsedCapability, - RequestDecoder, - RequestEncoder, - ResponseDecoder, - ResponseEncoder, - ServiceMethod, - TheCapabilityParser, - Failure, -} from '@ucanto/interface' - -export interface ClientCodec extends RequestEncoder, ResponseDecoder {} - -export interface ServerCodec extends RequestDecoder, ResponseEncoder {} - -/** - * Select from T the property names whose values are of type V - */ -export type KeysWithValue = { - [K in keyof T]-?: T[K] extends V ? K : never -}[keyof T] - -/** - * Infer ucanto service object from a namespace object containing CapabilityParsers - * - * @example InferService - */ -export type InferService< - S extends Record, - CP extends TheCapabilityParser> = TheCapabilityParser< - Match - >, - Success extends {} = {} -> = { - [K in KeysWithValue]: ServiceMethod< - InferInvokedCapability, - Success, - Failure - > -} diff --git a/packages/access-api/src/utils/common.js b/packages/access-api/src/utils/common.js deleted file mode 100644 index d438098ff..000000000 --- a/packages/access-api/src/utils/common.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * - * @param {unknown} obj - * @returns {obj is Record} - */ -export function isObject(obj) { - return typeof obj === 'object' && obj !== null -} - -/** - * - * @param {unknown} value - * @returns {value is Record} - */ -export function isPlainObject(value) { - if (typeof value !== 'object' || value === null) { - return false - } - - const prototype = Object.getPrototypeOf(value) - return ( - (prototype === null || - prototype === Object.prototype || - Object.getPrototypeOf(prototype) === null) && - !(Symbol.toStringTag in value) && - !(Symbol.iterator in value) - ) -} - -/** - * @param {unknown} value - * @returns {value is Date} - */ -export function isDate(value) { - const objectTypeName = toString.call(value).slice(8, -1) - - return objectTypeName === 'Date' -} - -/** - * - * @param {unknown} value - * @returns {value is Buffer} - */ -export function isBuffer(value) { - // @ts-ignore - return value?.constructor?.isBuffer?.(value) ?? false -} diff --git a/packages/access-api/src/utils/context.js b/packages/access-api/src/utils/context.js deleted file mode 100644 index 33562a5f4..000000000 --- a/packages/access-api/src/utils/context.js +++ /dev/null @@ -1,105 +0,0 @@ -import { Logging } from '@web3-storage/worker-utils/logging' -import { Toucan, RequestData } from 'toucan-js' -import { Signer } from '@ucanto/principal/ed25519' -import { loadConfig } from '../config.js' -import { Accounts } from '../models/accounts.js' -import { Spaces } from '../models/spaces.js' -import { Validations } from '../models/validations.js' -import * as Email from './email.js' -import * as UCANLog from './ucan-log.js' -import { createUploadApiConnection } from '../service/upload-api-proxy.js' -import { DID } from '@ucanto/core' -import { - DbDelegationsStorageWithR2, - delegationsTableBytesToArrayBuffer, -} from '../models/delegations.js' -import { createD1Database } from './d1.js' -import { DbProvisions } from '../models/provisions.js' -import * as release from './release.build.js' - -/** - * Obtains a route context object. - * - * @param {Request} request - * @param {import('../bindings').Env} env - * @param {Pick} ctx - * @returns {import('../bindings').RouteContext} - */ -export function getContext(request, env, ctx) { - const config = loadConfig(env) - const email = - config.ENV === 'test' || - (config.ENV === 'dev' && env.DEBUG_EMAIL === 'true') - ? Email.debug() - : Email.configure({ - token: config.POSTMARK_TOKEN, - sender: config.POSTMARK_SENDER, - }) - - const ucanLog = - config.UCAN_LOG_URL && config.UCAN_LOG_BASIC_AUTH - ? UCANLog.connect({ - url: new URL(config.UCAN_LOG_URL), - auth: config.UCAN_LOG_BASIC_AUTH, - }) - : UCANLog.debug() - - // Sentry - const sentry = new Toucan({ - context: ctx, - integrations: [ - new RequestData({ - allowedHeaders: ['user-agent', 'x-client'], - allowedSearchParams: /(.*)/, - }), - ], - request, - dsn: config.SENTRY_DSN, - debug: false, - environment: config.ENV, - release: release.name, - }) - - // Logging - const log = new Logging(request, ctx, { - token: config.LOGTAIL_TOKEN, - debug: config.DEBUG, - sentry: ['test'].includes(config.ENV) ? undefined : sentry, - branch: config.BRANCH, - version: config.VERSION, - commit: config.COMMITHASH, - env: config.ENV, - }) - const url = new URL(request.url) - const signer = Signer.parse(config.PRIVATE_KEY).withDID(config.DID) - return { - log, - signer, - config, - url, - models: { - delegations: new DbDelegationsStorageWithR2( - createD1Database(config.DB, { - bytes: (v) => { - return delegationsTableBytesToArrayBuffer(v) ?? v - }, - }), - config.DELEGATIONS_BUCKET - ), - spaces: new Spaces(config.DB), - validations: new Validations(config.VALIDATIONS), - accounts: new Accounts(config.DB), - provisions: new DbProvisions( - config.PROVIDERS, - createD1Database(config.DB) - ), - }, - email, - ucanLog, - uploadApi: createUploadApiConnection({ - audience: DID.parse(config.DID).did(), - url: new URL(config.UPLOAD_API_URL), - fetch: globalThis.fetch.bind(globalThis), - }), - } -} diff --git a/packages/access-api/src/utils/d1.js b/packages/access-api/src/utils/d1.js deleted file mode 100644 index 3d17bb6a1..000000000 --- a/packages/access-api/src/utils/d1.js +++ /dev/null @@ -1,157 +0,0 @@ -// @ts-ignore -// eslint-disable-next-line no-unused-vars -import * as Ucanto from '@ucanto/interface' -import { Kysely, OperationNodeTransformer } from 'kysely' -import { D1Dialect } from 'kysely-d1' -import { isPlainObject, isDate } from './common.js' - -/** - * @typedef {import('kysely').KyselyPlugin} KyselyPlugin - */ - -export class ObjectLikeTransformer extends OperationNodeTransformer { - /** - * @param {import('kysely').PrimitiveValueListNode} node - */ - transformPrimitiveValueList(node) { - return { - ...node, - values: node.values.map((v) => { - if (isPlainObject(v)) { - return JSON.stringify(v) - } - - if (isDate(v)) { - return v.toISOString() - } - - return v - }), - } - } - - /** - * @param {import('kysely').ValueNode} node - */ - transformValue(node) { - if (isPlainObject(node.value)) { - return { kind: node.kind, value: JSON.stringify(node.value) } - } - - if (isDate(node.value)) { - return { kind: node.kind, value: node.value.toISOString() } - } - - return node - } -} - -/** - * Plugin to transform queries and results to and from js object types - * - * @template R - * @implements {KyselyPlugin} - */ -export class GenericPlugin { - /** - * - * @param {Partial<{[Key in keyof R]: (value: any) => R[Key]}>} resultTransforms - */ - constructor(resultTransforms) { - this.transformer = new ObjectLikeTransformer() - this.resultTransforms = resultTransforms - } - - /** - * Transform result from D1 results into JS instances like JSON strings into objects and date string into Date - * - * @param {import('kysely').PluginTransformResultArgs} args - */ - async transformResult(args) { - if (args.result.rows && Array.isArray(args.result.rows)) { - return { - ...args.result, - rows: args.result.rows.map((row) => { - const custom = {} - for (const [key, value] of Object.entries(row)) { - // @ts-ignore - if (this.resultTransforms[key]) { - // @ts-ignore - custom[key] = this.resultTransforms[key](value) - } - } - - return { - ...row, - ...custom, - } - }), - } - } - - return args.result - } - - /** - * Transforms objects in the query into string with JSON.stringify - * - * @param {import('kysely').PluginTransformQueryArgs} args - * @returns - */ - transformQuery(args) { - return this.transformer.transformNode(args.node) - } -} - -/** - * @implements {Ucanto.Failure} - */ -export class D1Error extends Error { - /** @type {true} */ - get error() { - return true - } - - /** - * - * @param {import('../bindings').D1ErrorRaw} error - */ - constructor(error) { - super( - `${error.cause ? error.cause.message : error.message} (${ - error.cause ? error.cause.code : '' - })`, - { - cause: error.cause, - } - ) - this.name = 'D1Error' - this.code = error.cause.code - } -} - -/** - * @template S - * @param {D1Database} d1 - * @param {Record unknown>} [resultTransforms] - * @returns {import('../types/database.js').Database} - */ -export function createD1Database(d1, resultTransforms = {}) { - /** @type {Kysely} */ - const kdb = new Kysely({ - dialect: new D1Dialect({ database: d1 }), - plugins: [ - new GenericPlugin({ - // eslint-disable-next-line unicorn/no-null - expires_at: (v) => (typeof v === 'string' ? new Date(v) : null), - inserted_at: (v) => new Date(v), - updated_at: (v) => new Date(v), - ...resultTransforms, - }), - ], - }) - const db = Object.assign(kdb, { - canStream: false, - }) - return db -} diff --git a/packages/access-api/src/utils/email.js b/packages/access-api/src/utils/email.js deleted file mode 100644 index cc2b6362c..000000000 --- a/packages/access-api/src/utils/email.js +++ /dev/null @@ -1,129 +0,0 @@ -export const debug = () => new DebugEmail() - -/** - * @typedef ValidationEmailSend - * @property {string} to - * @property {string} url - */ - -/** - * @param {{token:string, sender?:string}} opts - */ -export const configure = (opts) => new Email(opts) - -export class Email { - /** - * @param {object} opts - * @param {string} opts.token - * @param {string} [opts.sender] - */ - constructor(opts) { - this.sender = opts.sender || 'web3.storage ' - this.headers = { - Accept: 'text/json', - 'Content-Type': 'text/json', - 'X-Postmark-Server-Token': opts.token, - } - } - - /** - * Send validation email with ucan to register - * - * @param {ValidationEmailSend} opts - */ - async sendValidation(opts) { - const rsp = await fetch('https://api.postmarkapp.com/email/withTemplate', { - method: 'POST', - headers: this.headers, - body: JSON.stringify({ - From: this.sender, - To: opts.to, - TemplateAlias: 'welcome', - TemplateModel: { - product_url: 'https://web3.storage', - product_name: 'Web3 Storage', - email: opts.to, - action_url: opts.url, - }, - }), - }) - - if (!rsp.ok) { - throw new Error( - `Send email failed with status: ${ - rsp.status - }, body: ${await rsp.text()}` - ) - } - } - - /** - * Send email - * - * @param {object} opts - * @param {string} opts.to - * @param {string} opts.textBody - * @param {string} opts.subject - * - */ - async send({ to, textBody, subject }) { - const rsp = await fetch('https://api.postmarkapp.com/email', { - method: 'POST', - headers: this.headers, - body: JSON.stringify({ - From: this.sender, - To: to, - TextBody: textBody, - Subject: subject, - }), - }) - - if (!rsp.ok) { - throw new Error( - `Send email failed with status: ${ - rsp.status - }, body: ${await rsp.text()}` - ) - } - } -} - -/** - * This is API compatible version of Email class that can be used during - * tests and debugging. - */ -export class DebugEmail { - /** - * Send validation email with ucan to register - * - * @param {ValidationEmailSend} opts - */ - async sendValidation(opts) { - try { - // @ts-expect-error - globalThis.email.sendValidation(opts) - } catch { - // eslint-disable-next-line no-console - console.log('email.sendValidation', opts) - } - } - - /** - * Send email - * - * @param {object} opts - * @param {string} opts.to - * @param {string} opts.textBody - * @param {string} opts.subject - * - */ - async send(opts) { - try { - // @ts-expect-error - globalThis.email.send(opts) - } catch { - // eslint-disable-next-line no-console - console.log('email.send', opts) - } - } -} diff --git a/packages/access-api/src/utils/html.js b/packages/access-api/src/utils/html.js deleted file mode 100644 index bd76e6d90..000000000 --- a/packages/access-api/src/utils/html.js +++ /dev/null @@ -1,215 +0,0 @@ -import { render } from 'preact-render-to-string' - -/** - * Build HTML document - * - * @param {string} body - */ -export function buildDocument(body) { - return ` - - - - - - w3up Email Validation - - - - - - - -${body} - -` -} - -export class HtmlResponse extends Response { - /** - * - * @param {import('preact').VNode<{}>} body - * @param {ResponseInit} [init] - */ - constructor(body, init = {}) { - const headers = { - headers: { - 'content-type': 'text/html; charset=utf-8', - }, - } - super(buildDocument(render(body)), { ...init, ...headers }) - } - - /** - * @param {import('preact').VNode<{}>} body - * @param {ResponseInit} [init] - */ - static respond(body, init = {}) { - return new HtmlResponse(body, init) - } -} - -/** - * - * @param {object} props - * @param {boolean} [props.autoApprove] - */ -export const PendingValidateEmail = ({ autoApprove }) => ( -
- -
-

Validating Email

-
- -
- {autoApprove ? ( -