diff --git a/src/lib/vana/capability.ts b/src/lib/vana/capability.ts index b5cbee6..540090d 100644 --- a/src/lib/vana/capability.ts +++ b/src/lib/vana/capability.ts @@ -3,6 +3,8 @@ import { type AccessRequestStatus, } from "@opendatalabs/vana-sdk/server"; +import { shouldUseEnclaveRead } from "./enclave"; + /** * A DCR requesting multiple scopes mints ONE grant covering all of them, so * readiness is a grant-level check, not a single-scope match. Direct reads @@ -17,7 +19,8 @@ export function assertGrantReadReady( if ( (status.status !== "approved" && status.status !== "ready_for_read") || !status.grantId || - (options.requirePersonalServerUrl !== false && !status.personalServerUrl) + ((options.requirePersonalServerUrl ?? !shouldUseEnclaveRead(status)) && + !status.personalServerUrl) ) { throw new AccessNotApprovedError("The approved grant is not ready to read."); } diff --git a/test/contract.test.ts b/test/contract.test.ts index c4902af..21e6da8 100644 --- a/test/contract.test.ts +++ b/test/contract.test.ts @@ -1208,3 +1208,19 @@ test("marks JSON responses as non-cacheable", async () => { assert.equal(response.headers.get("Cache-Control"), "no-store"); assert.deepEqual(await response.json(), { error: "Sanitized failure" }); }); + +test("accepts the URL-less enclave grant used by the consent return page", () => { + assert.doesNotThrow(() => assertGrantReadReady({ + status: "ready_for_read", + grantId: `0x${"a".repeat(64)}`, + delivery: "enclave", + scopes: ["spotify.profile"], + })); +}); + +test("still rejects unapproved or missing enclave grants and explicit URL requirements", () => { + const enclave = { status: "ready_for_read" as const, delivery: "enclave" as const, grantId: "0xgrant" }; + assert.throws(() => assertGrantReadReady({ ...enclave, status: "pending" }), AccessNotApprovedError); + assert.throws(() => assertGrantReadReady({ ...enclave, grantId: undefined }), AccessNotApprovedError); + assert.throws(() => assertGrantReadReady(enclave, { requirePersonalServerUrl: true }), AccessNotApprovedError); +});