@opendatalabs/vana-sdk 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,6 +34,30 @@ function base64urlDecode(input) {
34
34
  base64 += "=".repeat(padLength);
35
35
  return new TextDecoder().decode((0, import_encoding.fromBase64)(base64));
36
36
  }
37
+ function isFiniteNumber(value) {
38
+ return typeof value === "number" && Number.isFinite(value);
39
+ }
40
+ function parsePayload(value) {
41
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
42
+ throw new import_errors.InvalidSignatureError({ reason: "Invalid payload shape" });
43
+ }
44
+ const payload = value;
45
+ if (typeof payload["aud"] !== "string" || typeof payload["method"] !== "string" || typeof payload["uri"] !== "string" || typeof payload["bodyHash"] !== "string" || !isFiniteNumber(payload["iat"]) || !isFiniteNumber(payload["exp"])) {
46
+ throw new import_errors.InvalidSignatureError({ reason: "Invalid payload claims" });
47
+ }
48
+ if (payload["grantId"] !== void 0 && typeof payload["grantId"] !== "string") {
49
+ throw new import_errors.InvalidSignatureError({ reason: "Invalid grantId claim" });
50
+ }
51
+ return {
52
+ aud: payload["aud"],
53
+ method: payload["method"],
54
+ uri: payload["uri"],
55
+ bodyHash: payload["bodyHash"],
56
+ iat: payload["iat"],
57
+ exp: payload["exp"],
58
+ grantId: payload["grantId"]
59
+ };
60
+ }
37
61
  function parseWeb3SignedHeader(headerValue) {
38
62
  if (!headerValue) {
39
63
  throw new import_errors.MissingAuthError();
@@ -54,8 +78,9 @@ function parseWeb3SignedHeader(headerValue) {
54
78
  let payload;
55
79
  try {
56
80
  const decoded = base64urlDecode(payloadBase64);
57
- payload = JSON.parse(decoded);
58
- } catch {
81
+ payload = parsePayload(JSON.parse(decoded));
82
+ } catch (err) {
83
+ if (err instanceof import_errors.InvalidSignatureError) throw err;
59
84
  throw new import_errors.InvalidSignatureError({ reason: "Invalid payload encoding" });
60
85
  }
61
86
  return {
@@ -98,7 +123,7 @@ async function verifyWeb3Signed(params) {
98
123
  actual: payload.uri
99
124
  });
100
125
  }
101
- if (params.bodyBytes !== void 0 && params.bodyBytes.length > 0) {
126
+ if (params.bodyBytes !== void 0) {
102
127
  const expectedBodyHash = (0, import_web3_signed_builder.computeBodyHash)(params.bodyBytes);
103
128
  if (payload.bodyHash !== expectedBodyHash) {
104
129
  throw new import_errors.InvalidSignatureError({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/auth/web3-signed.ts"],"sourcesContent":["/**\n * Web3Signed Authorization header parsing and verification.\n *\n * @remarks\n * Header format: `\"Web3Signed {base64url(payload)}.{signature}\"`.\n * Payload is JSON with fields `aud`, `method`, `uri`, `bodyHash`, `iat`, `exp`,\n * and optional `grantId`. The signature is EIP-191 over the base64url-encoded\n * payload string.\n *\n * Ported from `personal-server-ts` (`packages/core/src/auth/web3-signed.ts`).\n * Adjusted to be isomorphic (no `Buffer`) and to use SDK-local error types.\n *\n * @category Auth\n */\n\nimport { recoverMessageAddress } from \"viem\";\nimport { fromBase64 } from \"../utils/encoding\";\nimport {\n MissingAuthError,\n InvalidSignatureError,\n ExpiredTokenError,\n} from \"./errors\";\nimport { computeBodyHash } from \"./web3-signed-builder\";\n\nexport interface Web3SignedPayload {\n aud: string;\n method: string;\n uri: string;\n bodyHash: string;\n iat: number;\n exp: number;\n grantId?: string;\n}\n\nexport interface VerifiedAuth {\n signer: `0x${string}`;\n payload: Web3SignedPayload;\n}\n\nconst WEB3_SIGNED_PREFIX = \"Web3Signed \";\nconst CLOCK_SKEW_SECONDS = 60;\n\n/** Decode a base64url string (no padding) to UTF-8. */\nfunction base64urlDecode(input: string): string {\n let base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padLength = (4 - (base64.length % 4)) % 4;\n base64 += \"=\".repeat(padLength);\n return new TextDecoder().decode(fromBase64(base64));\n}\n\n/**\n * Parse a `\"Web3Signed <base64url>.<signature>\"` header value.\n *\n * @throws {MissingAuthError} If the header is missing or empty.\n * @throws {InvalidSignatureError} If the format is invalid.\n */\nexport function parseWeb3SignedHeader(headerValue: string | undefined): {\n payloadBase64: string;\n payload: Web3SignedPayload;\n signature: `0x${string}`;\n} {\n if (!headerValue) {\n throw new MissingAuthError();\n }\n\n if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {\n throw new InvalidSignatureError({ reason: \"Missing Web3Signed prefix\" });\n }\n\n const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);\n const dotIndex = value.indexOf(\".\");\n if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {\n throw new InvalidSignatureError({ reason: \"Invalid header format\" });\n }\n\n const payloadBase64 = value.slice(0, dotIndex);\n const signatureStr = value.slice(dotIndex + 1);\n\n if (!signatureStr.startsWith(\"0x\")) {\n throw new InvalidSignatureError({ reason: \"Invalid signature format\" });\n }\n\n let payload: Web3SignedPayload;\n try {\n const decoded = base64urlDecode(payloadBase64);\n payload = JSON.parse(decoded) as Web3SignedPayload;\n } catch {\n throw new InvalidSignatureError({ reason: \"Invalid payload encoding\" });\n }\n\n return {\n payloadBase64,\n payload,\n signature: signatureStr as `0x${string}`,\n };\n}\n\n/**\n * Full verification: parse header, recover signer via EIP-191, check claims.\n *\n * @remarks\n * Steps:\n * 1. Parse header to base64url + signature.\n * 2. Recover signer via {@link recoverMessageAddress} (EIP-191) over the base64url payload string.\n * 3. Check `aud === expectedOrigin`, `method === expectedMethod`, `uri === expectedPath`.\n * 4. Optionally check `bodyHash` against `bodyBytes`.\n * 5. Check `iat`/`exp` within a 60s clock skew.\n *\n * @returns The recovered signer address and parsed payload.\n */\nexport async function verifyWeb3Signed(params: {\n headerValue: string | undefined;\n expectedOrigin: string;\n expectedMethod: string;\n expectedPath: string;\n bodyBytes?: Uint8Array;\n now?: number;\n}): Promise<VerifiedAuth> {\n const { payloadBase64, payload, signature } = parseWeb3SignedHeader(\n params.headerValue,\n );\n\n let signer: `0x${string}`;\n try {\n signer = await recoverMessageAddress({\n message: payloadBase64,\n signature,\n });\n } catch {\n throw new InvalidSignatureError({ reason: \"Signature recovery failed\" });\n }\n\n if (payload.aud !== params.expectedOrigin) {\n throw new InvalidSignatureError({\n reason: \"Audience mismatch\",\n expected: params.expectedOrigin,\n actual: payload.aud,\n });\n }\n\n if (payload.method !== params.expectedMethod) {\n throw new InvalidSignatureError({\n reason: \"Method mismatch\",\n expected: params.expectedMethod,\n actual: payload.method,\n });\n }\n\n if (payload.uri !== params.expectedPath) {\n throw new InvalidSignatureError({\n reason: \"URI mismatch\",\n expected: params.expectedPath,\n actual: payload.uri,\n });\n }\n\n if (params.bodyBytes !== undefined && params.bodyBytes.length > 0) {\n const expectedBodyHash = computeBodyHash(params.bodyBytes);\n if (payload.bodyHash !== expectedBodyHash) {\n throw new InvalidSignatureError({\n reason: \"Body hash mismatch\",\n expected: expectedBodyHash,\n actual: payload.bodyHash,\n });\n }\n }\n\n const now = params.now ?? Math.floor(Date.now() / 1000);\n\n if (payload.exp < now - CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token expired\" });\n }\n\n if (payload.iat > now + CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token issued in the future\" });\n }\n\n return { signer, payload };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/auth/web3-signed.ts"],"sourcesContent":["/**\n * Web3Signed Authorization header parsing and verification.\n *\n * @remarks\n * Header format: `\"Web3Signed {base64url(payload)}.{signature}\"`.\n * Payload is JSON with fields `aud`, `method`, `uri`, `bodyHash`, `iat`, `exp`,\n * and optional `grantId`. The signature is EIP-191 over the base64url-encoded\n * payload string.\n *\n * Ported from `personal-server-ts` (`packages/core/src/auth/web3-signed.ts`).\n * Adjusted to be isomorphic (no `Buffer`) and to use SDK-local error types.\n *\n * @category Auth\n */\n\nimport { recoverMessageAddress } from \"viem\";\nimport { fromBase64 } from \"../utils/encoding\";\nimport {\n MissingAuthError,\n InvalidSignatureError,\n ExpiredTokenError,\n} from \"./errors\";\nimport { computeBodyHash } from \"./web3-signed-builder\";\n\nexport interface Web3SignedPayload {\n aud: string;\n method: string;\n uri: string;\n bodyHash: string;\n iat: number;\n exp: number;\n grantId?: string;\n}\n\nexport interface VerifiedAuth {\n signer: `0x${string}`;\n payload: Web3SignedPayload;\n}\n\nconst WEB3_SIGNED_PREFIX = \"Web3Signed \";\nconst CLOCK_SKEW_SECONDS = 60;\n\n/** Decode a base64url string (no padding) to UTF-8. */\nfunction base64urlDecode(input: string): string {\n let base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padLength = (4 - (base64.length % 4)) % 4;\n base64 += \"=\".repeat(padLength);\n return new TextDecoder().decode(fromBase64(base64));\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction parsePayload(value: unknown): Web3SignedPayload {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new InvalidSignatureError({ reason: \"Invalid payload shape\" });\n }\n\n const payload = value as Record<string, unknown>;\n if (\n typeof payload[\"aud\"] !== \"string\" ||\n typeof payload[\"method\"] !== \"string\" ||\n typeof payload[\"uri\"] !== \"string\" ||\n typeof payload[\"bodyHash\"] !== \"string\" ||\n !isFiniteNumber(payload[\"iat\"]) ||\n !isFiniteNumber(payload[\"exp\"])\n ) {\n throw new InvalidSignatureError({ reason: \"Invalid payload claims\" });\n }\n\n if (\n payload[\"grantId\"] !== undefined &&\n typeof payload[\"grantId\"] !== \"string\"\n ) {\n throw new InvalidSignatureError({ reason: \"Invalid grantId claim\" });\n }\n\n return {\n aud: payload[\"aud\"],\n method: payload[\"method\"],\n uri: payload[\"uri\"],\n bodyHash: payload[\"bodyHash\"],\n iat: payload[\"iat\"],\n exp: payload[\"exp\"],\n grantId: payload[\"grantId\"],\n };\n}\n\n/**\n * Parse a `\"Web3Signed <base64url>.<signature>\"` header value.\n *\n * @throws {MissingAuthError} If the header is missing or empty.\n * @throws {InvalidSignatureError} If the format is invalid.\n */\nexport function parseWeb3SignedHeader(headerValue: string | undefined): {\n payloadBase64: string;\n payload: Web3SignedPayload;\n signature: `0x${string}`;\n} {\n if (!headerValue) {\n throw new MissingAuthError();\n }\n\n if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {\n throw new InvalidSignatureError({ reason: \"Missing Web3Signed prefix\" });\n }\n\n const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);\n const dotIndex = value.indexOf(\".\");\n if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {\n throw new InvalidSignatureError({ reason: \"Invalid header format\" });\n }\n\n const payloadBase64 = value.slice(0, dotIndex);\n const signatureStr = value.slice(dotIndex + 1);\n\n if (!signatureStr.startsWith(\"0x\")) {\n throw new InvalidSignatureError({ reason: \"Invalid signature format\" });\n }\n\n let payload: Web3SignedPayload;\n try {\n const decoded = base64urlDecode(payloadBase64);\n payload = parsePayload(JSON.parse(decoded));\n } catch (err) {\n if (err instanceof InvalidSignatureError) throw err;\n throw new InvalidSignatureError({ reason: \"Invalid payload encoding\" });\n }\n\n return {\n payloadBase64,\n payload,\n signature: signatureStr as `0x${string}`,\n };\n}\n\n/**\n * Full verification: parse header, recover signer via EIP-191, check claims.\n *\n * @remarks\n * Steps:\n * 1. Parse header to base64url + signature.\n * 2. Recover signer via {@link recoverMessageAddress} (EIP-191) over the base64url payload string.\n * 3. Check `aud === expectedOrigin`, `method === expectedMethod`, `uri === expectedPath`.\n * 4. Optionally check `bodyHash` against `bodyBytes`.\n * 5. Check `iat`/`exp` within a 60s clock skew.\n *\n * @returns The recovered signer address and parsed payload.\n */\nexport async function verifyWeb3Signed(params: {\n headerValue: string | undefined;\n expectedOrigin: string;\n expectedMethod: string;\n expectedPath: string;\n bodyBytes?: Uint8Array;\n now?: number;\n}): Promise<VerifiedAuth> {\n const { payloadBase64, payload, signature } = parseWeb3SignedHeader(\n params.headerValue,\n );\n\n let signer: `0x${string}`;\n try {\n signer = await recoverMessageAddress({\n message: payloadBase64,\n signature,\n });\n } catch {\n throw new InvalidSignatureError({ reason: \"Signature recovery failed\" });\n }\n\n if (payload.aud !== params.expectedOrigin) {\n throw new InvalidSignatureError({\n reason: \"Audience mismatch\",\n expected: params.expectedOrigin,\n actual: payload.aud,\n });\n }\n\n if (payload.method !== params.expectedMethod) {\n throw new InvalidSignatureError({\n reason: \"Method mismatch\",\n expected: params.expectedMethod,\n actual: payload.method,\n });\n }\n\n if (payload.uri !== params.expectedPath) {\n throw new InvalidSignatureError({\n reason: \"URI mismatch\",\n expected: params.expectedPath,\n actual: payload.uri,\n });\n }\n\n if (params.bodyBytes !== undefined) {\n const expectedBodyHash = computeBodyHash(params.bodyBytes);\n if (payload.bodyHash !== expectedBodyHash) {\n throw new InvalidSignatureError({\n reason: \"Body hash mismatch\",\n expected: expectedBodyHash,\n actual: payload.bodyHash,\n });\n }\n }\n\n const now = params.now ?? Math.floor(Date.now() / 1000);\n\n if (payload.exp < now - CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token expired\" });\n }\n\n if (payload.iat > now + CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token issued in the future\" });\n }\n\n return { signer, payload };\n}\n"],"mappings":"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","names":[]}
@@ -14,6 +14,30 @@ function base64urlDecode(input) {
14
14
  base64 += "=".repeat(padLength);
15
15
  return new TextDecoder().decode(fromBase64(base64));
16
16
  }
17
+ function isFiniteNumber(value) {
18
+ return typeof value === "number" && Number.isFinite(value);
19
+ }
20
+ function parsePayload(value) {
21
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
22
+ throw new InvalidSignatureError({ reason: "Invalid payload shape" });
23
+ }
24
+ const payload = value;
25
+ if (typeof payload["aud"] !== "string" || typeof payload["method"] !== "string" || typeof payload["uri"] !== "string" || typeof payload["bodyHash"] !== "string" || !isFiniteNumber(payload["iat"]) || !isFiniteNumber(payload["exp"])) {
26
+ throw new InvalidSignatureError({ reason: "Invalid payload claims" });
27
+ }
28
+ if (payload["grantId"] !== void 0 && typeof payload["grantId"] !== "string") {
29
+ throw new InvalidSignatureError({ reason: "Invalid grantId claim" });
30
+ }
31
+ return {
32
+ aud: payload["aud"],
33
+ method: payload["method"],
34
+ uri: payload["uri"],
35
+ bodyHash: payload["bodyHash"],
36
+ iat: payload["iat"],
37
+ exp: payload["exp"],
38
+ grantId: payload["grantId"]
39
+ };
40
+ }
17
41
  function parseWeb3SignedHeader(headerValue) {
18
42
  if (!headerValue) {
19
43
  throw new MissingAuthError();
@@ -34,8 +58,9 @@ function parseWeb3SignedHeader(headerValue) {
34
58
  let payload;
35
59
  try {
36
60
  const decoded = base64urlDecode(payloadBase64);
37
- payload = JSON.parse(decoded);
38
- } catch {
61
+ payload = parsePayload(JSON.parse(decoded));
62
+ } catch (err) {
63
+ if (err instanceof InvalidSignatureError) throw err;
39
64
  throw new InvalidSignatureError({ reason: "Invalid payload encoding" });
40
65
  }
41
66
  return {
@@ -78,7 +103,7 @@ async function verifyWeb3Signed(params) {
78
103
  actual: payload.uri
79
104
  });
80
105
  }
81
- if (params.bodyBytes !== void 0 && params.bodyBytes.length > 0) {
106
+ if (params.bodyBytes !== void 0) {
82
107
  const expectedBodyHash = computeBodyHash(params.bodyBytes);
83
108
  if (payload.bodyHash !== expectedBodyHash) {
84
109
  throw new InvalidSignatureError({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/auth/web3-signed.ts"],"sourcesContent":["/**\n * Web3Signed Authorization header parsing and verification.\n *\n * @remarks\n * Header format: `\"Web3Signed {base64url(payload)}.{signature}\"`.\n * Payload is JSON with fields `aud`, `method`, `uri`, `bodyHash`, `iat`, `exp`,\n * and optional `grantId`. The signature is EIP-191 over the base64url-encoded\n * payload string.\n *\n * Ported from `personal-server-ts` (`packages/core/src/auth/web3-signed.ts`).\n * Adjusted to be isomorphic (no `Buffer`) and to use SDK-local error types.\n *\n * @category Auth\n */\n\nimport { recoverMessageAddress } from \"viem\";\nimport { fromBase64 } from \"../utils/encoding\";\nimport {\n MissingAuthError,\n InvalidSignatureError,\n ExpiredTokenError,\n} from \"./errors\";\nimport { computeBodyHash } from \"./web3-signed-builder\";\n\nexport interface Web3SignedPayload {\n aud: string;\n method: string;\n uri: string;\n bodyHash: string;\n iat: number;\n exp: number;\n grantId?: string;\n}\n\nexport interface VerifiedAuth {\n signer: `0x${string}`;\n payload: Web3SignedPayload;\n}\n\nconst WEB3_SIGNED_PREFIX = \"Web3Signed \";\nconst CLOCK_SKEW_SECONDS = 60;\n\n/** Decode a base64url string (no padding) to UTF-8. */\nfunction base64urlDecode(input: string): string {\n let base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padLength = (4 - (base64.length % 4)) % 4;\n base64 += \"=\".repeat(padLength);\n return new TextDecoder().decode(fromBase64(base64));\n}\n\n/**\n * Parse a `\"Web3Signed <base64url>.<signature>\"` header value.\n *\n * @throws {MissingAuthError} If the header is missing or empty.\n * @throws {InvalidSignatureError} If the format is invalid.\n */\nexport function parseWeb3SignedHeader(headerValue: string | undefined): {\n payloadBase64: string;\n payload: Web3SignedPayload;\n signature: `0x${string}`;\n} {\n if (!headerValue) {\n throw new MissingAuthError();\n }\n\n if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {\n throw new InvalidSignatureError({ reason: \"Missing Web3Signed prefix\" });\n }\n\n const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);\n const dotIndex = value.indexOf(\".\");\n if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {\n throw new InvalidSignatureError({ reason: \"Invalid header format\" });\n }\n\n const payloadBase64 = value.slice(0, dotIndex);\n const signatureStr = value.slice(dotIndex + 1);\n\n if (!signatureStr.startsWith(\"0x\")) {\n throw new InvalidSignatureError({ reason: \"Invalid signature format\" });\n }\n\n let payload: Web3SignedPayload;\n try {\n const decoded = base64urlDecode(payloadBase64);\n payload = JSON.parse(decoded) as Web3SignedPayload;\n } catch {\n throw new InvalidSignatureError({ reason: \"Invalid payload encoding\" });\n }\n\n return {\n payloadBase64,\n payload,\n signature: signatureStr as `0x${string}`,\n };\n}\n\n/**\n * Full verification: parse header, recover signer via EIP-191, check claims.\n *\n * @remarks\n * Steps:\n * 1. Parse header to base64url + signature.\n * 2. Recover signer via {@link recoverMessageAddress} (EIP-191) over the base64url payload string.\n * 3. Check `aud === expectedOrigin`, `method === expectedMethod`, `uri === expectedPath`.\n * 4. Optionally check `bodyHash` against `bodyBytes`.\n * 5. Check `iat`/`exp` within a 60s clock skew.\n *\n * @returns The recovered signer address and parsed payload.\n */\nexport async function verifyWeb3Signed(params: {\n headerValue: string | undefined;\n expectedOrigin: string;\n expectedMethod: string;\n expectedPath: string;\n bodyBytes?: Uint8Array;\n now?: number;\n}): Promise<VerifiedAuth> {\n const { payloadBase64, payload, signature } = parseWeb3SignedHeader(\n params.headerValue,\n );\n\n let signer: `0x${string}`;\n try {\n signer = await recoverMessageAddress({\n message: payloadBase64,\n signature,\n });\n } catch {\n throw new InvalidSignatureError({ reason: \"Signature recovery failed\" });\n }\n\n if (payload.aud !== params.expectedOrigin) {\n throw new InvalidSignatureError({\n reason: \"Audience mismatch\",\n expected: params.expectedOrigin,\n actual: payload.aud,\n });\n }\n\n if (payload.method !== params.expectedMethod) {\n throw new InvalidSignatureError({\n reason: \"Method mismatch\",\n expected: params.expectedMethod,\n actual: payload.method,\n });\n }\n\n if (payload.uri !== params.expectedPath) {\n throw new InvalidSignatureError({\n reason: \"URI mismatch\",\n expected: params.expectedPath,\n actual: payload.uri,\n });\n }\n\n if (params.bodyBytes !== undefined && params.bodyBytes.length > 0) {\n const expectedBodyHash = computeBodyHash(params.bodyBytes);\n if (payload.bodyHash !== expectedBodyHash) {\n throw new InvalidSignatureError({\n reason: \"Body hash mismatch\",\n expected: expectedBodyHash,\n actual: payload.bodyHash,\n });\n }\n }\n\n const now = params.now ?? Math.floor(Date.now() / 1000);\n\n if (payload.exp < now - CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token expired\" });\n }\n\n if (payload.iat > now + CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token issued in the future\" });\n }\n\n return { signer, payload };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/auth/web3-signed.ts"],"sourcesContent":["/**\n * Web3Signed Authorization header parsing and verification.\n *\n * @remarks\n * Header format: `\"Web3Signed {base64url(payload)}.{signature}\"`.\n * Payload is JSON with fields `aud`, `method`, `uri`, `bodyHash`, `iat`, `exp`,\n * and optional `grantId`. The signature is EIP-191 over the base64url-encoded\n * payload string.\n *\n * Ported from `personal-server-ts` (`packages/core/src/auth/web3-signed.ts`).\n * Adjusted to be isomorphic (no `Buffer`) and to use SDK-local error types.\n *\n * @category Auth\n */\n\nimport { recoverMessageAddress } from \"viem\";\nimport { fromBase64 } from \"../utils/encoding\";\nimport {\n MissingAuthError,\n InvalidSignatureError,\n ExpiredTokenError,\n} from \"./errors\";\nimport { computeBodyHash } from \"./web3-signed-builder\";\n\nexport interface Web3SignedPayload {\n aud: string;\n method: string;\n uri: string;\n bodyHash: string;\n iat: number;\n exp: number;\n grantId?: string;\n}\n\nexport interface VerifiedAuth {\n signer: `0x${string}`;\n payload: Web3SignedPayload;\n}\n\nconst WEB3_SIGNED_PREFIX = \"Web3Signed \";\nconst CLOCK_SKEW_SECONDS = 60;\n\n/** Decode a base64url string (no padding) to UTF-8. */\nfunction base64urlDecode(input: string): string {\n let base64 = input.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padLength = (4 - (base64.length % 4)) % 4;\n base64 += \"=\".repeat(padLength);\n return new TextDecoder().decode(fromBase64(base64));\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nfunction parsePayload(value: unknown): Web3SignedPayload {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n throw new InvalidSignatureError({ reason: \"Invalid payload shape\" });\n }\n\n const payload = value as Record<string, unknown>;\n if (\n typeof payload[\"aud\"] !== \"string\" ||\n typeof payload[\"method\"] !== \"string\" ||\n typeof payload[\"uri\"] !== \"string\" ||\n typeof payload[\"bodyHash\"] !== \"string\" ||\n !isFiniteNumber(payload[\"iat\"]) ||\n !isFiniteNumber(payload[\"exp\"])\n ) {\n throw new InvalidSignatureError({ reason: \"Invalid payload claims\" });\n }\n\n if (\n payload[\"grantId\"] !== undefined &&\n typeof payload[\"grantId\"] !== \"string\"\n ) {\n throw new InvalidSignatureError({ reason: \"Invalid grantId claim\" });\n }\n\n return {\n aud: payload[\"aud\"],\n method: payload[\"method\"],\n uri: payload[\"uri\"],\n bodyHash: payload[\"bodyHash\"],\n iat: payload[\"iat\"],\n exp: payload[\"exp\"],\n grantId: payload[\"grantId\"],\n };\n}\n\n/**\n * Parse a `\"Web3Signed <base64url>.<signature>\"` header value.\n *\n * @throws {MissingAuthError} If the header is missing or empty.\n * @throws {InvalidSignatureError} If the format is invalid.\n */\nexport function parseWeb3SignedHeader(headerValue: string | undefined): {\n payloadBase64: string;\n payload: Web3SignedPayload;\n signature: `0x${string}`;\n} {\n if (!headerValue) {\n throw new MissingAuthError();\n }\n\n if (!headerValue.startsWith(WEB3_SIGNED_PREFIX)) {\n throw new InvalidSignatureError({ reason: \"Missing Web3Signed prefix\" });\n }\n\n const value = headerValue.slice(WEB3_SIGNED_PREFIX.length);\n const dotIndex = value.indexOf(\".\");\n if (dotIndex === -1 || dotIndex === 0 || dotIndex === value.length - 1) {\n throw new InvalidSignatureError({ reason: \"Invalid header format\" });\n }\n\n const payloadBase64 = value.slice(0, dotIndex);\n const signatureStr = value.slice(dotIndex + 1);\n\n if (!signatureStr.startsWith(\"0x\")) {\n throw new InvalidSignatureError({ reason: \"Invalid signature format\" });\n }\n\n let payload: Web3SignedPayload;\n try {\n const decoded = base64urlDecode(payloadBase64);\n payload = parsePayload(JSON.parse(decoded));\n } catch (err) {\n if (err instanceof InvalidSignatureError) throw err;\n throw new InvalidSignatureError({ reason: \"Invalid payload encoding\" });\n }\n\n return {\n payloadBase64,\n payload,\n signature: signatureStr as `0x${string}`,\n };\n}\n\n/**\n * Full verification: parse header, recover signer via EIP-191, check claims.\n *\n * @remarks\n * Steps:\n * 1. Parse header to base64url + signature.\n * 2. Recover signer via {@link recoverMessageAddress} (EIP-191) over the base64url payload string.\n * 3. Check `aud === expectedOrigin`, `method === expectedMethod`, `uri === expectedPath`.\n * 4. Optionally check `bodyHash` against `bodyBytes`.\n * 5. Check `iat`/`exp` within a 60s clock skew.\n *\n * @returns The recovered signer address and parsed payload.\n */\nexport async function verifyWeb3Signed(params: {\n headerValue: string | undefined;\n expectedOrigin: string;\n expectedMethod: string;\n expectedPath: string;\n bodyBytes?: Uint8Array;\n now?: number;\n}): Promise<VerifiedAuth> {\n const { payloadBase64, payload, signature } = parseWeb3SignedHeader(\n params.headerValue,\n );\n\n let signer: `0x${string}`;\n try {\n signer = await recoverMessageAddress({\n message: payloadBase64,\n signature,\n });\n } catch {\n throw new InvalidSignatureError({ reason: \"Signature recovery failed\" });\n }\n\n if (payload.aud !== params.expectedOrigin) {\n throw new InvalidSignatureError({\n reason: \"Audience mismatch\",\n expected: params.expectedOrigin,\n actual: payload.aud,\n });\n }\n\n if (payload.method !== params.expectedMethod) {\n throw new InvalidSignatureError({\n reason: \"Method mismatch\",\n expected: params.expectedMethod,\n actual: payload.method,\n });\n }\n\n if (payload.uri !== params.expectedPath) {\n throw new InvalidSignatureError({\n reason: \"URI mismatch\",\n expected: params.expectedPath,\n actual: payload.uri,\n });\n }\n\n if (params.bodyBytes !== undefined) {\n const expectedBodyHash = computeBodyHash(params.bodyBytes);\n if (payload.bodyHash !== expectedBodyHash) {\n throw new InvalidSignatureError({\n reason: \"Body hash mismatch\",\n expected: expectedBodyHash,\n actual: payload.bodyHash,\n });\n }\n }\n\n const now = params.now ?? Math.floor(Date.now() / 1000);\n\n if (payload.exp < now - CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token expired\" });\n }\n\n if (payload.iat > now + CLOCK_SKEW_SECONDS) {\n throw new ExpiredTokenError({ reason: \"Token issued in the future\" });\n }\n\n return { signer, payload };\n}\n"],"mappings":"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","names":[]}
@@ -35,6 +35,7 @@ export { parseWeb3SignedHeader, verifyWeb3Signed, type Web3SignedPayload, type V
35
35
  export { buildWeb3SignedHeader, computeBodyHash, type Web3SignedSignFn, } from "./auth/web3-signed-builder";
36
36
  export { MissingAuthError, InvalidSignatureError, ExpiredTokenError, } from "./auth/errors";
37
37
  export { fileRegistrationDomain, grantRegistrationDomain, grantRevocationDomain, serverRegistrationDomain, builderRegistrationDomain, FILE_REGISTRATION_TYPES, GRANT_REGISTRATION_TYPES, GRANT_REVOCATION_TYPES, SERVER_REGISTRATION_TYPES, BUILDER_REGISTRATION_TYPES, type DataPortabilityContracts, type DataPortabilityGatewayConfig, type FileRegistrationMessage, type GrantRegistrationMessage, type GrantRevocationMessage, type ServerRegistrationMessage, type BuilderRegistrationMessage, } from "./protocol/eip712";
38
+ export { isDataPortabilityGatewayConfig, parseGrantRegistrationPayload, verifyGrantRegistration, type DataPortabilityGrantPayload, type VerifyGrantRegistrationInput, type VerifyGrantRegistrationResult, } from "./protocol/grants";
38
39
  export { ScopeSchema, parseScope, scopeToPathSegments, scopeMatchesPattern, scopeCoveredByGrant, type Scope, type ParsedScope, } from "./protocol/scopes";
39
40
  export { DataFileEnvelopeSchema, createDataFileEnvelope, IngestResponseSchema, type DataFileEnvelope, type IngestResponse, } from "./protocol/data-file";
40
41
  export { createGatewayClient, type GatewayEnvelope, type GatewayProof, type Builder, type Schema, type ServerInfo, type GatewayGrantResponse, type GrantListItem, type FileRecord, type FileListResult, type RegisterServerParams, type RegisterServerResult, type RegisterFileParams, type CreateGrantParams, type RevokeGrantParams, type GatewayClient, } from "./protocol/gateway";
@@ -31548,6 +31548,30 @@ function base64urlDecode(input) {
31548
31548
  base64 += "=".repeat(padLength);
31549
31549
  return new TextDecoder().decode(fromBase64(base64));
31550
31550
  }
31551
+ function isFiniteNumber(value) {
31552
+ return typeof value === "number" && Number.isFinite(value);
31553
+ }
31554
+ function parsePayload(value) {
31555
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
31556
+ throw new InvalidSignatureError({ reason: "Invalid payload shape" });
31557
+ }
31558
+ const payload = value;
31559
+ if (typeof payload["aud"] !== "string" || typeof payload["method"] !== "string" || typeof payload["uri"] !== "string" || typeof payload["bodyHash"] !== "string" || !isFiniteNumber(payload["iat"]) || !isFiniteNumber(payload["exp"])) {
31560
+ throw new InvalidSignatureError({ reason: "Invalid payload claims" });
31561
+ }
31562
+ if (payload["grantId"] !== void 0 && typeof payload["grantId"] !== "string") {
31563
+ throw new InvalidSignatureError({ reason: "Invalid grantId claim" });
31564
+ }
31565
+ return {
31566
+ aud: payload["aud"],
31567
+ method: payload["method"],
31568
+ uri: payload["uri"],
31569
+ bodyHash: payload["bodyHash"],
31570
+ iat: payload["iat"],
31571
+ exp: payload["exp"],
31572
+ grantId: payload["grantId"]
31573
+ };
31574
+ }
31551
31575
  function parseWeb3SignedHeader(headerValue) {
31552
31576
  if (!headerValue) {
31553
31577
  throw new MissingAuthError();
@@ -31568,8 +31592,9 @@ function parseWeb3SignedHeader(headerValue) {
31568
31592
  let payload;
31569
31593
  try {
31570
31594
  const decoded = base64urlDecode(payloadBase64);
31571
- payload = JSON.parse(decoded);
31572
- } catch {
31595
+ payload = parsePayload(JSON.parse(decoded));
31596
+ } catch (err) {
31597
+ if (err instanceof InvalidSignatureError) throw err;
31573
31598
  throw new InvalidSignatureError({ reason: "Invalid payload encoding" });
31574
31599
  }
31575
31600
  return {
@@ -31612,7 +31637,7 @@ async function verifyWeb3Signed(params) {
31612
31637
  actual: payload.uri
31613
31638
  });
31614
31639
  }
31615
- if (params.bodyBytes !== void 0 && params.bodyBytes.length > 0) {
31640
+ if (params.bodyBytes !== void 0) {
31616
31641
  const expectedBodyHash = computeBodyHash(params.bodyBytes);
31617
31642
  if (payload.bodyHash !== expectedBodyHash) {
31618
31643
  throw new InvalidSignatureError({
@@ -31711,6 +31736,121 @@ var BUILDER_REGISTRATION_TYPES = {
31711
31736
  ]
31712
31737
  };
31713
31738
 
31739
+ // src/protocol/grants.ts
31740
+ import { verifyTypedData } from "viem";
31741
+ function isHexString(value) {
31742
+ return typeof value === "string" && value.startsWith("0x");
31743
+ }
31744
+ function isDataPortabilityGatewayConfig(value) {
31745
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
31746
+ return false;
31747
+ }
31748
+ const config = value;
31749
+ const contracts = config["contracts"];
31750
+ if (typeof config["chainId"] !== "number" || !Number.isInteger(config["chainId"]) || config["chainId"] <= 0 || contracts === null || typeof contracts !== "object" || Array.isArray(contracts)) {
31751
+ return false;
31752
+ }
31753
+ const c = contracts;
31754
+ return isHexString(c["dataRegistry"]) && isHexString(c["dataPortabilityPermissions"]) && isHexString(c["dataPortabilityServer"]) && isHexString(c["dataPortabilityGrantees"]);
31755
+ }
31756
+ function parseGrantRegistrationPayload(grant) {
31757
+ let parsed;
31758
+ try {
31759
+ parsed = JSON.parse(grant);
31760
+ } catch {
31761
+ return null;
31762
+ }
31763
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
31764
+ return null;
31765
+ }
31766
+ const value = parsed;
31767
+ if (!Array.isArray(value["scopes"]) || value["scopes"].length === 0) {
31768
+ return null;
31769
+ }
31770
+ if (!value["scopes"].every((scope) => typeof scope === "string")) {
31771
+ return null;
31772
+ }
31773
+ if (typeof value["expiresAt"] !== "number" || !Number.isFinite(value["expiresAt"])) {
31774
+ return null;
31775
+ }
31776
+ if (value["user"] !== void 0 && !isHexString(value["user"])) {
31777
+ return null;
31778
+ }
31779
+ if (value["builder"] !== void 0 && !isHexString(value["builder"])) {
31780
+ return null;
31781
+ }
31782
+ if (value["nonce"] !== void 0 && (typeof value["nonce"] !== "number" || !Number.isFinite(value["nonce"]))) {
31783
+ return null;
31784
+ }
31785
+ return {
31786
+ user: value["user"],
31787
+ builder: value["builder"],
31788
+ scopes: value["scopes"],
31789
+ expiresAt: value["expiresAt"],
31790
+ nonce: value["nonce"]
31791
+ };
31792
+ }
31793
+ function parseFileIds(fileIds) {
31794
+ try {
31795
+ const values = (fileIds ?? []).map((fileId) => BigInt(fileId));
31796
+ return {
31797
+ values,
31798
+ display: values.map((fileId) => fileId.toString())
31799
+ };
31800
+ } catch {
31801
+ return null;
31802
+ }
31803
+ }
31804
+ async function verifyGrantRegistration(input) {
31805
+ const payload = parseGrantRegistrationPayload(input.grant);
31806
+ if (!payload) {
31807
+ return {
31808
+ valid: false,
31809
+ error: "Grant must be JSON with scopes and expiresAt"
31810
+ };
31811
+ }
31812
+ const fileIds = parseFileIds(input.fileIds);
31813
+ if (!fileIds) {
31814
+ return { valid: false, error: "fileIds must contain integer values" };
31815
+ }
31816
+ let valid;
31817
+ try {
31818
+ valid = await verifyTypedData({
31819
+ address: input.grantorAddress,
31820
+ domain: grantRegistrationDomain(input.gatewayConfig),
31821
+ types: GRANT_REGISTRATION_TYPES,
31822
+ primaryType: "GrantRegistration",
31823
+ message: {
31824
+ grantorAddress: input.grantorAddress,
31825
+ granteeId: input.granteeId,
31826
+ grant: input.grant,
31827
+ fileIds: fileIds.values
31828
+ },
31829
+ signature: input.signature
31830
+ });
31831
+ } catch {
31832
+ return { valid: false, error: "EIP-712 signature verification failed" };
31833
+ }
31834
+ if (!valid) {
31835
+ return { valid: false, error: "Grant signature does not match grantor" };
31836
+ }
31837
+ const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1e3);
31838
+ if (payload.expiresAt > 0 && payload.expiresAt < nowSeconds) {
31839
+ return { valid: false, error: "Grant has expired" };
31840
+ }
31841
+ if (payload.user !== void 0 && payload.user.toLowerCase() !== input.grantorAddress.toLowerCase()) {
31842
+ return { valid: false, error: "Grant user does not match grantorAddress" };
31843
+ }
31844
+ return {
31845
+ valid: true,
31846
+ grantorAddress: input.grantorAddress,
31847
+ granteeId: input.granteeId,
31848
+ grant: input.grant,
31849
+ payload,
31850
+ fileIds: fileIds.display
31851
+ };
31852
+ }
31853
+
31714
31854
  // src/protocol/scopes.ts
31715
31855
  import { z } from "zod";
31716
31856
  var SEGMENT_RE = /^[a-z0-9][a-z0-9_]*$/;
@@ -31991,32 +32131,57 @@ var PSError = class extends Error {
31991
32131
  code;
31992
32132
  };
31993
32133
  var KNOWN_CODES = /* @__PURE__ */ new Set([
32134
+ "missing_auth",
32135
+ "invalid_signature",
32136
+ "unregistered_builder",
32137
+ "not_owner",
32138
+ "expired_token",
31994
32139
  "grant_invalid",
32140
+ "grant_required",
32141
+ "grant_expired",
31995
32142
  "grant_revoked",
32143
+ "scope_mismatch",
31996
32144
  "fee_required",
31997
- "ps_unavailable"
32145
+ "ps_unavailable",
32146
+ "server_not_configured",
32147
+ "content_too_large"
31998
32148
  ]);
31999
- async function parsePSError(response) {
32000
- if (response.ok) {
32149
+ function isRecord(value) {
32150
+ return value !== null && typeof value === "object" && !Array.isArray(value);
32151
+ }
32152
+ function normalizeCode(value) {
32153
+ if (typeof value !== "string") {
32001
32154
  return null;
32002
32155
  }
32003
- let body;
32004
- try {
32005
- body = await response.json();
32006
- } catch {
32156
+ const code = value.toLowerCase();
32157
+ return KNOWN_CODES.has(code) ? code : null;
32158
+ }
32159
+ function extractPSErrorBody(body) {
32160
+ if (!isRecord(body)) {
32007
32161
  return null;
32008
32162
  }
32009
- if (typeof body !== "object" || body === null) {
32163
+ const nested = isRecord(body.error) ? body.error : null;
32164
+ const code = normalizeCode(
32165
+ nested?.errorCode ?? nested?.code ?? body.errorCode ?? body.code
32166
+ );
32167
+ const message = nested?.message ?? body.message;
32168
+ if (!code || typeof message !== "string") {
32010
32169
  return null;
32011
32170
  }
32012
- const { code, message } = body;
32013
- if (typeof code !== "string" || typeof message !== "string") {
32171
+ return { code, message };
32172
+ }
32173
+ async function parsePSError(response) {
32174
+ if (response.ok) {
32014
32175
  return null;
32015
32176
  }
32016
- if (!KNOWN_CODES.has(code)) {
32177
+ let body;
32178
+ try {
32179
+ body = await response.json();
32180
+ } catch {
32017
32181
  return null;
32018
32182
  }
32019
- return new PSError(code, message);
32183
+ const errorBody = extractPSErrorBody(body);
32184
+ return errorBody ? new PSError(errorBody.code, errorBody.message) : null;
32020
32185
  }
32021
32186
  export {
32022
32187
  BUILDER_REGISTRATION_TYPES,
@@ -32095,12 +32260,14 @@ export {
32095
32260
  getServiceEndpoints,
32096
32261
  grantRegistrationDomain,
32097
32262
  grantRevocationDomain,
32263
+ isDataPortabilityGatewayConfig,
32098
32264
  isECIESEncrypted,
32099
32265
  isPlatformSupported,
32100
32266
  mainnetServices,
32101
32267
  moksha,
32102
32268
  mokshaServices,
32103
32269
  mokshaTestnet2 as mokshaTestnet,
32270
+ parseGrantRegistrationPayload,
32104
32271
  parsePSError,
32105
32272
  parseScope,
32106
32273
  parseWeb3SignedHeader,
@@ -32111,6 +32278,7 @@ export {
32111
32278
  serializeECIES,
32112
32279
  serverRegistrationDomain,
32113
32280
  vanaMainnet2 as vanaMainnet,
32281
+ verifyGrantRegistration,
32114
32282
  verifyPkceChallenge,
32115
32283
  verifyWeb3Signed
32116
32284
  };