SIGN IN SIGN UP

Add Vercel OIDC auth and Presigned URLs (#1056)

* Add Vercel OIDC auth

* up

* header

* up

* prioritize token

* normalize storeId in OIDC auth resolution

BLOB_STORE_ID and the storeId option are accepted in either store_<id>
or <id> form (Vercel env pull writes the prefixed form), and may be
mixed-case. resolveBlobAuth was passing those through verbatim, so the
storeId in API headers and CDN host subdomains could be malformed —
e.g. blob.get against a private store with `store_WdsHBk1w9fDO4vPW`
built `https://store_WdsHBk1w9fDO4vPW.private.blob.vercel-storage.com/...`
and 404'd. The RW path was unaffected because parseStoreIdFromReadWriteToken
yields a bare lowercase id from the token's structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* preserve case when normalizing storeId

The first pass also lowercased — that breaks API requests, since the
Vercel Blob API is case-sensitive on the storeId (header and bearer
parsing). The CDN host accepts either case, so prefix-strip alone is
sufficient and works for both consumers. Verified end-to-end against
a private store: blob get and blob list both succeed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* add oidcToken option

* minor version

* Vercel Presigned URLs (#1057)

* presigned urls

* up

* normalize

* fix

* update to new signing string

* [wip] presigned url version of handleUpload (#1059)

* [wip] presigned url version of handleUpload

* up

* up

* up

* add presigned urls for mpu

* update options shape

* webhook signature

* update example

* up

* up

* up

* change to operation

* up

* up

* verify webhook signature

* up

* callbackUrl

* non-null

* presigned url opts

* new delegation token + url opts

* dont lowercase store id

* validUntil instead of ttlSeconds

* up

* up

* Add 'delete' to DelegationOperation for presigned DELETE URLs (#1061)

Mirrors the API-side change that adds `delete` to the issue_signed_token
allowed operations. `presignUrl` now signs canonical
`operation=delete\npathname=...` against the public blob object URL
(same host shape as `get`/`head`, just used with HTTP DELETE).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* change upload to put

* delete

* up

* presigned url payload

* update app

* fix

* docs

* refactor presign url

* up

* up

* up

* add head

* up

* add params to url

* add keys first

* token payload

* add buildPresignedGetUrl

* up

* presignUrl returns url, not accepted in sdk methods

* up

* up

* feat(blob): presigned HEAD & DELETE URLs (#1064)

* feat(blob): presigned DELETE URLs

Wires the `'delete'` DelegationOperation (added in #1061, since rebased
out of elliot/presigned-urls) through `presignUrl`. A token issued with
`operations: ['delete']` can now mint a presigned `DELETE /?pathname=…`
against the control-plane API.

- `PresignDeleteUrlOptions` accepts `pathname`, optional `validUntil`,
  optional `ifMatch`. Upload-only fields are rejected at the type level.
- `presign()` gates on the delegation scope including `'delete'`.
- `buildPresignedDeleteUrl()` mirrors the PUT URL shape; the HTTP method
  is the discriminator (canonical signing string carries `operation=delete`).
- `buildPresignCanonicalQueryEntries()` for `delete` emits only
  `validUntil` (when below the delegation ceiling) and `ifMatch`.
- E2E test route + delete button on the presigned-upload demo page.

Based on `elliot/presigned-urls`, not `main`.

* feat(blob): presigned HEAD URLs (#1065)

`HEAD` mirrors `GET` against the blob object host
(`<storeId>.<access>.blob.vercel-storage.com/<pathname>`); the URL shape
is identical and the HTTP method is the discriminator. `operation=head`
goes into the canonical signing string so a GET-signed URL cannot be
replayed as a HEAD (and vice versa).

- `'head'` added to `DelegationOperation`.
- `PresignHeadUrlOptions` — same shape as `PresignGetUrlOptions`
  (`pathname`, optional `validUntil`).
- `presign()` gates on the delegation scope including `'head'`.
- `presignUrl()` reuses `buildPresignedGetUrl()` for `operation: 'head'`.
- `buildPresignCanonicalQueryEntries()` for `head` emits only
  `validUntil` (when below the delegation ceiling) — same as `get`.
- E2E test route + HEAD button on the presigned-upload demo page.

Stacked on `falcoagustin/presigned-delete-impl` (#1064).

* environment error

* up

* cleanup

---------

Co-authored-by: Agustin Falco <agusfalco_11@hotmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* up

* rm dead code

* throw on manual token

* up

* up

---------

Co-authored-by: Agustin Falco <agusfalco_11@hotmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E
Elliot Dauber committed
20eeaffead76d8e1a0e8a11108cca5a4e0596974
Parent: f23cb89
Committed by GitHub <noreply@github.com> on 5/18/2026, 8:37:28 PM