@agent-native/core 0.120.4 → 0.121.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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +12 -4
- package/corpus/core/src/agent/run-manager.ts +3 -0
- package/corpus/core/src/client/use-db-sync.ts +23 -21
- package/corpus/core/src/deploy/build.ts +1 -0
- package/corpus/core/src/server/core-routes-plugin.ts +3 -0
- package/corpus/core/src/server/gateway-access-check.ts +67 -0
- package/corpus/core/src/server/poll.ts +295 -9
- package/corpus/core/src/server/short-lived-token.ts +122 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +29 -4
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +365 -9
- package/corpus/templates/slides/app/components/editor/bullet-editing.ts +350 -0
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +5 -0
- package/corpus/templates/slides/changelog/2026-07-23-new-bullet-rows-created-with-enter-now-keep-the-list-item-s-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-23-pressing-enter-in-generated-checkbox-shape-marker-lists-now-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-24-typing-a-markdown-style-dash-space-at-the-start-of-a-text-bl.md +6 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +16 -4
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +6 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +27 -22
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +1 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/gateway-access-check.d.ts +12 -0
- package/dist/server/gateway-access-check.d.ts.map +1 -0
- package/dist/server/gateway-access-check.js +49 -0
- package/dist/server/gateway-access-check.js.map +1 -0
- package/dist/server/poll.d.ts +55 -1
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +254 -7
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/short-lived-token.d.ts +28 -0
- package/dist/server/short-lived-token.d.ts.map +1 -1
- package/dist/server/short-lived-token.js +78 -0
- package/dist/server/short-lived-token.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent/durable-background.ts +12 -4
- package/src/agent/run-manager.ts +3 -0
- package/src/client/use-db-sync.ts +23 -21
- package/src/deploy/build.ts +1 -0
- package/src/server/core-routes-plugin.ts +3 -0
- package/src/server/gateway-access-check.ts +67 -0
- package/src/server/poll.ts +295 -9
- package/src/server/short-lived-token.ts +122 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"short-lived-token.js","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Short-lived HMAC-signed access tokens for media URLs.\n *\n * Used by clips and calls to mint a single-use bearer token after a password\n * gate passes, then bake `?t=<token>` into the video/blob URL handed to the\n * `<video>` element — instead of `?password=<plaintext>` (which ends up in\n * browser history, CDN logs, and Referer headers).\n *\n * Token shape: `<payloadB64Url>.<sigB64Url>`\n * payload = base64url(JSON.stringify({ resourceId, viewerEmail?, exp }))\n * sig = base64url(HMAC-SHA256(payload, key))\n *\n * Key resolution mirrors `google-oauth.ts:getStateSigningKey`:\n * 1. OAUTH_STATE_SECRET (preferred — dedicated to short-lived signing)\n * 2. BETTER_AUTH_SECRET (already used as a server secret)\n * 3. Hosted workspace deploys derive a per-purpose key from A2A_SECRET\n * 4. In dev only, an ephemeral random key (per-process)\n *\n * In production, throws if no usable server secret is set.\n */\n\nimport crypto from \"node:crypto\";\n\nimport { getWorkspaceA2ADerivedSecret } from \"./derived-secret.js\";\n\n/** Default token TTL, in seconds. 10 minutes covers a typical video session. */\nconst DEFAULT_TTL_SECONDS = 600;\n\n/**\n * Inputs for {@link signShortLivedToken}.\n */\nexport interface ShortLivedTokenClaims {\n /** Resource id the token authorises (recording id, call id, snippet id, …). */\n resourceId: string;\n /** Optional viewer email for audit / analytics — not used for authorisation. */\n viewerEmail?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedClaims {\n resourceId: string;\n viewerEmail?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyShortLivedToken}. Discriminated by the literal\n * `ok` field so callers can `if (!result.ok) return …`.\n */\nexport type VerifyResult =\n | { ok: true; viewerEmail?: string }\n | { ok: false; reason: string };\n\nlet _devSigningKey: string | undefined;\n\nfunction getSigningKey(): string {\n const secret =\n process.env.OAUTH_STATE_SECRET ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"short-lived-token\");\n if (secret) return secret;\n\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"Short-lived token signing requires a server secret. \" +\n \"Set OAUTH_STATE_SECRET, BETTER_AUTH_SECRET, or A2A_SECRET in production workspace deploys.\",\n );\n }\n\n if (!_devSigningKey) {\n _devSigningKey = crypto.randomBytes(32).toString(\"hex\");\n }\n return _devSigningKey;\n}\n\nfunction base64UrlEncode(buf: Buffer | string): string {\n const b = typeof buf === \"string\" ? Buffer.from(buf, \"utf8\") : buf;\n return b\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(s: string): Buffer {\n // Re-pad to a multiple of 4 so Buffer.from('base64') decodes cleanly.\n const padded = s + \"=\".repeat((4 - (s.length % 4)) % 4);\n return Buffer.from(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\n/**\n * Mint a signed token authorising read access to `claims.resourceId` until\n * `exp = now + ttl`. The result is safe to drop into a query string —\n * `?t=<token>` — and verified by {@link verifyShortLivedToken} on the\n * downstream route.\n */\nexport function signShortLivedToken(claims: ShortLivedTokenClaims): string {\n const ttl = claims.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const payload: DecodedClaims = {\n resourceId: claims.resourceId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.viewerEmail) payload.viewerEmail = claims.viewerEmail;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n const sig = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n return `${payloadStr}.${sig}`;\n}\n\n/**\n * Verify a token previously produced by {@link signShortLivedToken}.\n *\n * Returns `{ ok: true, viewerEmail? }` only when:\n * - the token has the expected shape (`<payload>.<sig>`),\n * - the signature matches via constant-time comparison,\n * - the token has not expired,\n * - the embedded `resourceId` matches `expectedResourceId`.\n *\n * Otherwise returns `{ ok: false, reason: <error string> }`. Callers should\n * not surface the reason to viewers (it's useful for server-side logs only).\n */\nexport function verifyShortLivedToken(\n token: string,\n expectedResourceId: string,\n): VerifyResult {\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n const expected = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n\n // Constant-time compare. Length-mismatched inputs would throw under\n // `crypto.timingSafeEqual`, so we check length first and fall back to a\n // dummy compare to keep timing roughly constant on the failure path.\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return { ok: false, reason: \"bad_signature\" };\n }\n if (!crypto.timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.resourceId !== expectedResourceId) {\n return { ok: false, reason: \"wrong_resource\" };\n }\n\n return { ok: true, viewerEmail: claims.viewerEmail };\n}\n\n// ── Realtime subscribe tokens ────────────────────────────────────────────────\n//\n// An identity-bearing extension of the same HMAC discipline, used by the hosted\n// Realtime Gateway. Differs from the media token above in three ways the gateway\n// depends on:\n// 1. It is signed with a caller-supplied PER-PROJECT key (the app's HMAC\n// secret), not the single deployment-wide `getSigningKey()`. The app server\n// mints with it; the gateway (a second issuer for stream rotation) verifies\n// and re-mints with the same per-project key. A token minted for project A\n// fails signature verification against project B's key.\n// 2. It carries authorization-bearing identity (`owner`/`orgId`) that the\n// gateway feeds to `canSeeChangeForUser` — the media token's `viewerEmail`\n// is audit-only by contract, so a new field is required, not repurposed.\n// 3. It binds `projectId` as the channel and stamps a `typ` discriminator, so\n// it is verified against the connect channel and cannot be replayed as a\n// media token (or vice-versa) even if keys ever overlapped.\n\n/** Payload `typ` discriminator for realtime subscribe tokens. */\nexport const REALTIME_SUBSCRIBE_TOKEN_TYPE = \"rt-subscribe\";\nconst DEFAULT_REALTIME_TTL_SECONDS = 600;\n\n/** Inputs for {@link signRealtimeSubscribeToken}. */\nexport interface RealtimeSubscribeClaims {\n /** Channel — one Neon project per app. Verified at connect, not just carried. */\n projectId: string;\n /**\n * App end-user session email (NOT a Builder.io account — see the tech spec).\n * Fed to `canSeeChangeForUser`. Present in v0.\n */\n owner?: string;\n /** Framework org id for the app's end-user. */\n orgId?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedRealtimeClaims {\n typ: string;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyRealtimeSubscribeToken}. On success it returns the\n * identity claims the gateway uses to scope delivery.\n */\nexport type RealtimeVerifyResult =\n | {\n ok: true;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n }\n | { ok: false; reason: string };\n\nfunction hmacB64(payloadStr: string, key: string): string {\n return base64UrlEncode(\n crypto.createHmac(\"sha256\", key).update(payloadStr).digest(),\n );\n}\n\nfunction timingSafeEqualB64(sig: string, expected: string): boolean {\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return false;\n }\n return crypto.timingSafeEqual(sigBuf, expBuf);\n}\n\n/**\n * Mint a realtime subscribe token for `claims.projectId`, signed with the\n * app's per-project `key`. Safe to place on the connect query string (short\n * TTL, single-purpose, channel-bound). Verified by\n * {@link verifyRealtimeSubscribeToken} at connect.\n */\nexport function signRealtimeSubscribeToken(\n claims: RealtimeSubscribeClaims,\n key: string,\n): string {\n if (!key) throw new Error(\"signRealtimeSubscribeToken requires a key\");\n // Fail closed: a token with neither owner nor orgId carries no authorization\n // identity, so canSeeChangeForUser would only ever match global/unowned\n // events. Every issuer (mint endpoint + the gateway's rotation re-mint) must\n // supply at least one identity claim.\n if (!claims.owner && !claims.orgId) {\n throw new Error(\n \"signRealtimeSubscribeToken requires an owner or orgId claim\",\n );\n }\n const ttl = claims.ttlSeconds ?? DEFAULT_REALTIME_TTL_SECONDS;\n const payload: DecodedRealtimeClaims = {\n typ: REALTIME_SUBSCRIBE_TOKEN_TYPE,\n projectId: claims.projectId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.owner) payload.owner = claims.owner;\n if (claims.orgId) payload.orgId = claims.orgId;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n return `${payloadStr}.${hmacB64(payloadStr, key)}`;\n}\n\n/**\n * Verify a realtime subscribe token against the app's per-project `key` and the\n * connect channel `projectId`. Returns the identity claims only when the shape,\n * signature (constant-time), `typ`, `exp`, and `projectId` binding all hold.\n */\nexport function verifyRealtimeSubscribeToken(\n token: string,\n expected: { projectId: string; key: string },\n): RealtimeVerifyResult {\n if (!expected.key) return { ok: false, reason: \"no_key\" };\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n if (!timingSafeEqualB64(sig, hmacB64(payloadStr, expected.key))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedRealtimeClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (claims.typ !== REALTIME_SUBSCRIBE_TOKEN_TYPE) {\n return { ok: false, reason: \"wrong_type\" };\n }\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.projectId !== expected.projectId) {\n return { ok: false, reason: \"wrong_project\" };\n }\n\n return {\n ok: true,\n projectId: claims.projectId,\n owner: claims.owner,\n orgId: claims.orgId,\n exp: claims.exp,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"short-lived-token.js","sourceRoot":"","sources":["../../src/server/short-lived-token.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Short-lived HMAC-signed access tokens for media URLs.\n *\n * Used by clips and calls to mint a single-use bearer token after a password\n * gate passes, then bake `?t=<token>` into the video/blob URL handed to the\n * `<video>` element — instead of `?password=<plaintext>` (which ends up in\n * browser history, CDN logs, and Referer headers).\n *\n * Token shape: `<payloadB64Url>.<sigB64Url>`\n * payload = base64url(JSON.stringify({ resourceId, viewerEmail?, exp }))\n * sig = base64url(HMAC-SHA256(payload, key))\n *\n * Key resolution mirrors `google-oauth.ts:getStateSigningKey`:\n * 1. OAUTH_STATE_SECRET (preferred — dedicated to short-lived signing)\n * 2. BETTER_AUTH_SECRET (already used as a server secret)\n * 3. Hosted workspace deploys derive a per-purpose key from A2A_SECRET\n * 4. In dev only, an ephemeral random key (per-process)\n *\n * In production, throws if no usable server secret is set.\n */\n\nimport crypto from \"node:crypto\";\n\nimport { getWorkspaceA2ADerivedSecret } from \"./derived-secret.js\";\n\n/** Default token TTL, in seconds. 10 minutes covers a typical video session. */\nconst DEFAULT_TTL_SECONDS = 600;\n\n/**\n * Inputs for {@link signShortLivedToken}.\n */\nexport interface ShortLivedTokenClaims {\n /** Resource id the token authorises (recording id, call id, snippet id, …). */\n resourceId: string;\n /** Optional viewer email for audit / analytics — not used for authorisation. */\n viewerEmail?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedClaims {\n resourceId: string;\n viewerEmail?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyShortLivedToken}. Discriminated by the literal\n * `ok` field so callers can `if (!result.ok) return …`.\n */\nexport type VerifyResult =\n | { ok: true; viewerEmail?: string }\n | { ok: false; reason: string };\n\nlet _devSigningKey: string | undefined;\n\nfunction getSigningKey(): string {\n const secret =\n process.env.OAUTH_STATE_SECRET ||\n process.env.BETTER_AUTH_SECRET ||\n getWorkspaceA2ADerivedSecret(\"short-lived-token\");\n if (secret) return secret;\n\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"Short-lived token signing requires a server secret. \" +\n \"Set OAUTH_STATE_SECRET, BETTER_AUTH_SECRET, or A2A_SECRET in production workspace deploys.\",\n );\n }\n\n if (!_devSigningKey) {\n _devSigningKey = crypto.randomBytes(32).toString(\"hex\");\n }\n return _devSigningKey;\n}\n\nfunction base64UrlEncode(buf: Buffer | string): string {\n const b = typeof buf === \"string\" ? Buffer.from(buf, \"utf8\") : buf;\n return b\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nfunction base64UrlDecode(s: string): Buffer {\n // Re-pad to a multiple of 4 so Buffer.from('base64') decodes cleanly.\n const padded = s + \"=\".repeat((4 - (s.length % 4)) % 4);\n return Buffer.from(padded.replace(/-/g, \"+\").replace(/_/g, \"/\"), \"base64\");\n}\n\n/**\n * Mint a signed token authorising read access to `claims.resourceId` until\n * `exp = now + ttl`. The result is safe to drop into a query string —\n * `?t=<token>` — and verified by {@link verifyShortLivedToken} on the\n * downstream route.\n */\nexport function signShortLivedToken(claims: ShortLivedTokenClaims): string {\n const ttl = claims.ttlSeconds ?? DEFAULT_TTL_SECONDS;\n const payload: DecodedClaims = {\n resourceId: claims.resourceId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.viewerEmail) payload.viewerEmail = claims.viewerEmail;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n const sig = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n return `${payloadStr}.${sig}`;\n}\n\n/**\n * Verify a token previously produced by {@link signShortLivedToken}.\n *\n * Returns `{ ok: true, viewerEmail? }` only when:\n * - the token has the expected shape (`<payload>.<sig>`),\n * - the signature matches via constant-time comparison,\n * - the token has not expired,\n * - the embedded `resourceId` matches `expectedResourceId`.\n *\n * Otherwise returns `{ ok: false, reason: <error string> }`. Callers should\n * not surface the reason to viewers (it's useful for server-side logs only).\n */\nexport function verifyShortLivedToken(\n token: string,\n expectedResourceId: string,\n): VerifyResult {\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n const expected = base64UrlEncode(\n crypto.createHmac(\"sha256\", getSigningKey()).update(payloadStr).digest(),\n );\n\n // Constant-time compare. Length-mismatched inputs would throw under\n // `crypto.timingSafeEqual`, so we check length first and fall back to a\n // dummy compare to keep timing roughly constant on the failure path.\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return { ok: false, reason: \"bad_signature\" };\n }\n if (!crypto.timingSafeEqual(sigBuf, expBuf)) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.resourceId !== expectedResourceId) {\n return { ok: false, reason: \"wrong_resource\" };\n }\n\n return { ok: true, viewerEmail: claims.viewerEmail };\n}\n\n// ── Realtime subscribe tokens ────────────────────────────────────────────────\n//\n// An identity-bearing extension of the same HMAC discipline, used by the hosted\n// Realtime Gateway. Differs from the media token above in three ways the gateway\n// depends on:\n// 1. It is signed with a caller-supplied PER-PROJECT key (the app's HMAC\n// secret), not the single deployment-wide `getSigningKey()`. The app server\n// mints with it; the gateway (a second issuer for stream rotation) verifies\n// and re-mints with the same per-project key. A token minted for project A\n// fails signature verification against project B's key.\n// 2. It carries authorization-bearing identity (`owner`/`orgId`) that the\n// gateway feeds to `canSeeChangeForUser` — the media token's `viewerEmail`\n// is audit-only by contract, so a new field is required, not repurposed.\n// 3. It binds `projectId` as the channel and stamps a `typ` discriminator, so\n// it is verified against the connect channel and cannot be replayed as a\n// media token (or vice-versa) even if keys ever overlapped.\n\n/** Payload `typ` discriminator for realtime subscribe tokens. */\nexport const REALTIME_SUBSCRIBE_TOKEN_TYPE = \"rt-subscribe\";\nconst DEFAULT_REALTIME_TTL_SECONDS = 600;\n\n/** Inputs for {@link signRealtimeSubscribeToken}. */\nexport interface RealtimeSubscribeClaims {\n /** Channel — one Neon project per app. Verified at connect, not just carried. */\n projectId: string;\n /**\n * App end-user session email (NOT a Builder.io account — see the tech spec).\n * Fed to `canSeeChangeForUser`. Present in v0.\n */\n owner?: string;\n /** Framework org id for the app's end-user. */\n orgId?: string;\n /** Override default TTL (seconds). */\n ttlSeconds?: number;\n}\n\ninterface DecodedRealtimeClaims {\n typ: string;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n}\n\n/**\n * Result of {@link verifyRealtimeSubscribeToken}. On success it returns the\n * identity claims the gateway uses to scope delivery.\n */\nexport type RealtimeVerifyResult =\n | {\n ok: true;\n projectId: string;\n owner?: string;\n orgId?: string;\n exp: number;\n }\n | { ok: false; reason: string };\n\nfunction hmacB64(payloadStr: string, key: string): string {\n return base64UrlEncode(\n crypto.createHmac(\"sha256\", key).update(payloadStr).digest(),\n );\n}\n\nfunction timingSafeEqualB64(sig: string, expected: string): boolean {\n const sigBuf = Buffer.from(sig, \"utf8\");\n const expBuf = Buffer.from(expected, \"utf8\");\n if (sigBuf.length !== expBuf.length) {\n crypto.timingSafeEqual(expBuf, expBuf); // burn ~equal cycles\n return false;\n }\n return crypto.timingSafeEqual(sigBuf, expBuf);\n}\n\n/**\n * Mint a realtime subscribe token for `claims.projectId`, signed with the\n * app's per-project `key`. Safe to place on the connect query string (short\n * TTL, single-purpose, channel-bound). Verified by\n * {@link verifyRealtimeSubscribeToken} at connect.\n */\nexport function signRealtimeSubscribeToken(\n claims: RealtimeSubscribeClaims,\n key: string,\n): string {\n if (!key) throw new Error(\"signRealtimeSubscribeToken requires a key\");\n // Fail closed: a token with neither owner nor orgId carries no authorization\n // identity, so canSeeChangeForUser would only ever match global/unowned\n // events. Every issuer (mint endpoint + the gateway's rotation re-mint) must\n // supply at least one identity claim.\n if (!claims.owner && !claims.orgId) {\n throw new Error(\n \"signRealtimeSubscribeToken requires an owner or orgId claim\",\n );\n }\n const ttl = claims.ttlSeconds ?? DEFAULT_REALTIME_TTL_SECONDS;\n const payload: DecodedRealtimeClaims = {\n typ: REALTIME_SUBSCRIBE_TOKEN_TYPE,\n projectId: claims.projectId,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.owner) payload.owner = claims.owner;\n if (claims.orgId) payload.orgId = claims.orgId;\n\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n return `${payloadStr}.${hmacB64(payloadStr, key)}`;\n}\n\n/**\n * Verify a realtime subscribe token against the app's per-project `key` and the\n * connect channel `projectId`. Returns the identity claims only when the shape,\n * signature (constant-time), `typ`, `exp`, and `projectId` binding all hold.\n */\nexport function verifyRealtimeSubscribeToken(\n token: string,\n expected: { projectId: string; key: string },\n): RealtimeVerifyResult {\n if (!expected.key) return { ok: false, reason: \"no_key\" };\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n if (!timingSafeEqualB64(sig, hmacB64(payloadStr, expected.key))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedRealtimeClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (claims.typ !== REALTIME_SUBSCRIBE_TOKEN_TYPE) {\n return { ok: false, reason: \"wrong_type\" };\n }\n if (typeof claims.exp !== \"number\") {\n return { ok: false, reason: \"bad_payload\" };\n }\n if (claims.exp * 1000 < Date.now()) {\n return { ok: false, reason: \"expired\" };\n }\n if (claims.projectId !== expected.projectId) {\n return { ok: false, reason: \"wrong_project\" };\n }\n\n return {\n ok: true,\n projectId: claims.projectId,\n owner: claims.owner,\n orgId: claims.orgId,\n exp: claims.exp,\n };\n}\n\n// ── Gateway access-check tokens ──────────────────────────────────────────────\n//\n// The hosted gateway has no access to an app's shareable-resource registry, so\n// it cannot resolve sharee visibility itself. It signs one of these with the\n// app's per-project key and calls the app's `/_agent-native/can-see`, which runs\n// `resolveAccess` and answers. The full access query is bound into the token so\n// the app authenticates the params, not merely the caller.\n\n/** Payload `typ` discriminator for gateway access-check tokens. */\nexport const GATEWAY_ACCESS_TOKEN_TYPE = \"rt-access-check\";\n/** Short TTL: minted per check, used immediately server-to-server. */\nconst DEFAULT_GATEWAY_ACCESS_TTL_SECONDS = 60;\n\n/** Inputs for {@link signGatewayAccessToken}. */\nexport interface GatewayAccessClaims {\n projectId: string;\n resourceType: string;\n resourceId: string;\n /** App end-user whose visibility of the resource is being checked. */\n userEmail: string;\n orgId?: string;\n ttlSeconds?: number;\n}\n\ninterface DecodedGatewayAccessClaims {\n typ: string;\n projectId: string;\n resourceType: string;\n resourceId: string;\n userEmail: string;\n orgId?: string;\n exp: number;\n}\n\n/** Result of {@link verifyGatewayAccessToken}; on success carries the bound query. */\nexport type GatewayAccessVerifyResult =\n | {\n ok: true;\n projectId: string;\n resourceType: string;\n resourceId: string;\n userEmail: string;\n orgId?: string;\n }\n | { ok: false; reason: string };\n\n/** Mint a gateway access-check token, signed with the app's per-project `key`. */\nexport function signGatewayAccessToken(\n claims: GatewayAccessClaims,\n key: string,\n): string {\n if (!key) throw new Error(\"signGatewayAccessToken requires a key\");\n const ttl = claims.ttlSeconds ?? DEFAULT_GATEWAY_ACCESS_TTL_SECONDS;\n const payload: DecodedGatewayAccessClaims = {\n typ: GATEWAY_ACCESS_TOKEN_TYPE,\n projectId: claims.projectId,\n resourceType: claims.resourceType,\n resourceId: claims.resourceId,\n userEmail: claims.userEmail,\n exp: Math.floor(Date.now() / 1000) + ttl,\n };\n if (claims.orgId) payload.orgId = claims.orgId;\n const payloadStr = base64UrlEncode(JSON.stringify(payload));\n return `${payloadStr}.${hmacB64(payloadStr, key)}`;\n}\n\n/** Verify a gateway access-check token against the app's per-project `key`. */\nexport function verifyGatewayAccessToken(\n token: string,\n key: string,\n expectedProjectId?: string,\n): GatewayAccessVerifyResult {\n if (!key) return { ok: false, reason: \"no_key\" };\n if (typeof token !== \"string\" || !token.includes(\".\")) {\n return { ok: false, reason: \"malformed\" };\n }\n const [payloadStr, sig] = token.split(\".\", 2);\n if (!payloadStr || !sig) return { ok: false, reason: \"malformed\" };\n\n if (!timingSafeEqualB64(sig, hmacB64(payloadStr, key))) {\n return { ok: false, reason: \"bad_signature\" };\n }\n\n let claims: DecodedGatewayAccessClaims;\n try {\n claims = JSON.parse(base64UrlDecode(payloadStr).toString(\"utf8\"));\n } catch {\n return { ok: false, reason: \"bad_payload\" };\n }\n\n if (claims.typ !== GATEWAY_ACCESS_TOKEN_TYPE) {\n return { ok: false, reason: \"wrong_type\" };\n }\n if (typeof claims.exp !== \"number\")\n return { ok: false, reason: \"bad_payload\" };\n if (claims.exp * 1000 < Date.now()) return { ok: false, reason: \"expired\" };\n if (\n !claims.projectId ||\n !claims.resourceType ||\n !claims.resourceId ||\n !claims.userEmail\n ) {\n return { ok: false, reason: \"bad_payload\" };\n }\n // Optional channel binding, mirroring verifyRealtimeSubscribeToken. The\n // per-project key already scopes verification to one app; this is belt-and-\n // suspenders for a future multi-tenant secret store. Skipped when the caller\n // can't cheaply resolve its own project id (scoped-secret apps).\n if (expectedProjectId && claims.projectId !== expectedProjectId) {\n return { ok: false, reason: \"wrong_project\" };\n }\n\n return {\n ok: true,\n projectId: claims.projectId,\n resourceType: claims.resourceType,\n resourceId: claims.resourceId,\n userEmail: claims.userEmail,\n orgId: claims.orgId,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.121.1",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -114,10 +114,15 @@ function isNetlifyHostedRuntimeForDispatch(): boolean {
|
|
|
114
114
|
if (process.env.NETLIFY_LOCAL === "true") return false;
|
|
115
115
|
if (process.env.NETLIFY === "false") return false;
|
|
116
116
|
if (process.env.NETLIFY && process.env.NETLIFY !== "false") return true;
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
117
|
+
// NETLIFY is a build-only read-only variable. In deployed Functions Netlify
|
|
118
|
+
// documents URL, SITE_NAME, and SITE_ID as the runtime read-only variables;
|
|
119
|
+
// SITE_ID is the unambiguous host marker. Lambda compatibility mode also
|
|
120
|
+
// exposes AWS runtime variables, so keep the function-name fallback for older
|
|
121
|
+
// deploys. Without either check a modern Netlify Function silently selects the
|
|
122
|
+
// portable framework route even though the emitted background function exists.
|
|
123
|
+
if (process.env.SITE_ID) return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
|
|
124
|
+
// Non-Netlify AWS falls back inline if the /.netlify/functions dispatch
|
|
125
|
+
// fast-fails.
|
|
121
126
|
return Boolean(process.env.AWS_LAMBDA_FUNCTION_NAME);
|
|
122
127
|
}
|
|
123
128
|
|
|
@@ -202,6 +207,9 @@ export const AGENT_CHAT_BACKGROUND_RUN_FIELD = "__backgroundRun";
|
|
|
202
207
|
* what "hosted" means.
|
|
203
208
|
*/
|
|
204
209
|
export function isHostedRuntimeForDurableBackground(): boolean {
|
|
210
|
+
if (process.env.NETLIFY_LOCAL === "true") return false;
|
|
211
|
+
if (process.env.NETLIFY === "false") return false;
|
|
212
|
+
if (process.env.SITE_ID) return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
|
|
205
213
|
if (
|
|
206
214
|
process.env.NETLIFY &&
|
|
207
215
|
process.env.NETLIFY !== "false" &&
|
package/src/agent/run-manager.ts
CHANGED
|
@@ -319,6 +319,9 @@ export interface ResolveRunSoftTimeoutOptions {
|
|
|
319
319
|
* that clamp (and the platform wall behind it) exists.
|
|
320
320
|
*/
|
|
321
321
|
export function isHostedRuntime(): boolean {
|
|
322
|
+
if (process.env.NETLIFY_LOCAL === "true") return false;
|
|
323
|
+
if (process.env.NETLIFY === "false") return false;
|
|
324
|
+
if (process.env.SITE_ID) return true; // guard:allow-env-credential -- Netlify's read-only public site identifier is a runtime host marker, not a user credential.
|
|
322
325
|
if (
|
|
323
326
|
process.env.NETLIFY &&
|
|
324
327
|
process.env.NETLIFY !== "false" &&
|
|
@@ -330,9 +330,11 @@ class SyncTransport {
|
|
|
330
330
|
|
|
331
331
|
private get activeSseUrl(): string | false {
|
|
332
332
|
if (this.mode === "hosted" && this.gateway) {
|
|
333
|
-
return this.
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
if (!this.token) return this.gateway.sseUrl;
|
|
334
|
+
const base = `${this.gateway.sseUrl}?token=${encodeURIComponent(this.token)}`;
|
|
335
|
+
// Cursor lets the gateway replay the reconnect gap on connect instead of
|
|
336
|
+
// deferring it to the next poll; 0 on first connect means nothing to replay.
|
|
337
|
+
return this.versionRef > 0 ? `${base}&since=${this.versionRef}` : base;
|
|
336
338
|
}
|
|
337
339
|
return this.sseUrl;
|
|
338
340
|
}
|
|
@@ -656,27 +658,27 @@ class SyncTransport {
|
|
|
656
658
|
this.schedulePoll();
|
|
657
659
|
};
|
|
658
660
|
source.onerror = () => {
|
|
661
|
+
// A replaced/closed source can still fire late; ignore it so it can't
|
|
662
|
+
// flip the connected state or tear down the current stream.
|
|
663
|
+
if (this.eventSource !== source) return;
|
|
659
664
|
this.setSseConnected(false);
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
+
if (this.mode === "hosted" && this.gateway) {
|
|
666
|
+
// Browser auto-reconnect reuses the URL frozen at construction, so it
|
|
667
|
+
// would replay from a stale `since`. Own the reconnect so the next
|
|
668
|
+
// connect rebuilds activeSseUrl from the current versionRef. CLOSED also
|
|
669
|
+
// refreshes the token (expired/rotated/deploy); CONNECTING keeps it. A
|
|
670
|
+
// successful reconnect resets the count in onopen; a hard-down gateway
|
|
671
|
+
// trips the threshold and health-gates to local.
|
|
672
|
+
if (source.readyState === EventSource.CLOSED) this.token = null;
|
|
673
|
+
this.closeEvents();
|
|
674
|
+
this.onGatewayTransientFailure();
|
|
675
|
+
if (this.mode === "hosted") this.scheduleGatewayReconnect();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
// Local mode: native EventSource reconnect is fine. Drop a CLOSED ref so a
|
|
679
|
+
// later connectEvents() (focus/visibility) can establish a fresh stream.
|
|
665
680
|
if (source.readyState === EventSource.CLOSED) {
|
|
666
681
|
this.eventSource = null;
|
|
667
|
-
if (this.mode === "hosted" && this.gateway) {
|
|
668
|
-
// A closed gateway stream is most likely an expired token or a
|
|
669
|
-
// request-timeout/deploy cycle. Re-mint and reconnect with jitter;
|
|
670
|
-
// this is NOT the poll-401 cooldown path. Each closed stream counts
|
|
671
|
-
// toward the unhealthy threshold so a hard-down gateway (or one
|
|
672
|
-
// rejecting our tokens) health-gates to local instead of looping
|
|
673
|
-
// mint+connect forever; a successful reconnect resets the count in
|
|
674
|
-
// onopen above.
|
|
675
|
-
this.token = null;
|
|
676
|
-
this.onGatewayTransientFailure();
|
|
677
|
-
if (this.mode === "hosted") this.scheduleGatewayReconnect();
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
682
|
}
|
|
681
683
|
this.schedulePoll();
|
|
682
684
|
};
|
package/src/deploy/build.ts
CHANGED
|
@@ -159,6 +159,7 @@ import {
|
|
|
159
159
|
markDefaultPluginProvided,
|
|
160
160
|
trackPluginInit,
|
|
161
161
|
} from "./framework-request-handler.js";
|
|
162
|
+
import { createGatewayAccessCheckHandler } from "./gateway-access-check.js";
|
|
162
163
|
import { getAppBasePath, getOrigin } from "./google-oauth.js";
|
|
163
164
|
import { createGoogleRealtimeSessionHandler } from "./google-realtime-session.js";
|
|
164
165
|
import {
|
|
@@ -1429,6 +1430,8 @@ export function createCoreRoutesPlugin(
|
|
|
1429
1430
|
`${P}/realtime-token`,
|
|
1430
1431
|
createRealtimeTokenHandler(),
|
|
1431
1432
|
);
|
|
1433
|
+
// Sharee visibility check for the hosted gateway
|
|
1434
|
+
getH3App(nitroApp).use(`${P}/can-see`, createGatewayAccessCheckHandler());
|
|
1432
1435
|
|
|
1433
1436
|
// SSE
|
|
1434
1437
|
if (!options.disableSSE) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /_agent-native/can-see` — the hosted Realtime Gateway's sharee-visibility
|
|
3
|
+
* check. Verifies a gateway access-check token (rationale in
|
|
4
|
+
* short-lived-token.ts), runs the app's own `resolveAccess`, answers
|
|
5
|
+
* `{ allowed }`, and fails closed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
defineEventHandler,
|
|
10
|
+
getMethod,
|
|
11
|
+
getQuery,
|
|
12
|
+
type H3Event,
|
|
13
|
+
setResponseHeader,
|
|
14
|
+
setResponseStatus,
|
|
15
|
+
} from "h3";
|
|
16
|
+
|
|
17
|
+
import { resolveAccess } from "../sharing/access.js";
|
|
18
|
+
import { getBuilderBranchProjectId } from "./builder-browser.js";
|
|
19
|
+
import { getRealtimeSigningSecret } from "./realtime-token.js";
|
|
20
|
+
import { runWithRequestContext } from "./request-context.js";
|
|
21
|
+
import { verifyGatewayAccessToken } from "./short-lived-token.js";
|
|
22
|
+
|
|
23
|
+
export function createGatewayAccessCheckHandler() {
|
|
24
|
+
return defineEventHandler(async (event: H3Event) => {
|
|
25
|
+
setResponseHeader(event, "Cache-Control", "private, no-store");
|
|
26
|
+
|
|
27
|
+
if (getMethod(event) !== "GET") {
|
|
28
|
+
setResponseStatus(event, 405);
|
|
29
|
+
return { error: "Method not allowed" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const secret = getRealtimeSigningSecret();
|
|
33
|
+
if (!secret) {
|
|
34
|
+
setResponseStatus(event, 404);
|
|
35
|
+
return { error: "Realtime gateway not configured" };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const token = getQuery(event).token;
|
|
39
|
+
// Sync, env-only: binds the token's channel when this app's project id is
|
|
40
|
+
// known, and no-ops (undefined) for scoped-secret apps where it isn't.
|
|
41
|
+
const expectedProjectId = getBuilderBranchProjectId() || undefined;
|
|
42
|
+
const verified =
|
|
43
|
+
typeof token === "string"
|
|
44
|
+
? verifyGatewayAccessToken(token, secret, expectedProjectId)
|
|
45
|
+
: ({ ok: false, reason: "missing" } as const);
|
|
46
|
+
if (!verified.ok) {
|
|
47
|
+
setResponseStatus(event, 401);
|
|
48
|
+
return { error: "Unauthorized" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const { resourceType, resourceId, userEmail, orgId } = verified;
|
|
52
|
+
return runWithRequestContext({ userEmail, orgId }, async () => {
|
|
53
|
+
try {
|
|
54
|
+
const access = await resolveAccess(
|
|
55
|
+
resourceType,
|
|
56
|
+
resourceId,
|
|
57
|
+
{ userEmail, orgId },
|
|
58
|
+
{ skipResourceBody: true },
|
|
59
|
+
);
|
|
60
|
+
return { allowed: access != null };
|
|
61
|
+
} catch {
|
|
62
|
+
// Unknown resource type or lookup failure: fail closed.
|
|
63
|
+
return { allowed: false };
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|