@agent-native/core 0.101.7 → 0.101.11
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 +1 -1
- package/corpus/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +23 -1
- package/corpus/core/src/a2a/handlers.ts +65 -1
- package/corpus/core/src/a2a/server.ts +124 -0
- package/corpus/core/src/a2a/task-store.ts +253 -0
- package/corpus/core/src/a2a/types.ts +26 -0
- package/corpus/core/src/agent/production-agent.ts +5 -2
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/mcp/builtin-tools.ts +25 -1
- package/corpus/core/src/scripts/call-agent.ts +22 -3
- package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
- package/corpus/templates/content/AGENTS.md +3 -2
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
- package/corpus/templates/content/actions/_database-utils.ts +89 -12
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
- package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +71 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +38 -0
- package/corpus/templates/content/app/i18n-data.ts +450 -0
- package/corpus/templates/content/app/root.tsx +2 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
- package/corpus/templates/content/package.json +1 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +47 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +53 -0
- package/corpus/templates/content/shared/api.ts +70 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/document-export.ts +296 -25
- package/corpus/templates/content/shared/inline-math.ts +128 -0
- package/corpus/templates/content/shared/math-rendering.ts +50 -0
- package/corpus/templates/content/shared/nfm.ts +20 -17
- package/corpus/templates/content/vite.config.ts +0 -1
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
- package/corpus/templates/mail/AGENTS.md +21 -19
- package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
- package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
- package/corpus/templates/mail/server/handlers/media.ts +84 -61
- package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
- package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
- package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
- package/corpus/templates/mail/server/plugins/auth.ts +9 -1
- package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/a2a/client.d.ts +5 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +18 -1
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +57 -8
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/server.d.ts.map +1 -1
- package/dist/a2a/server.js +87 -1
- package/dist/a2a/server.js.map +1 -1
- package/dist/a2a/task-store.d.ts +27 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +216 -0
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/a2a/types.d.ts +24 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +5 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +22 -3
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts +1 -1
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +19 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +71 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createHash,
|
|
3
|
+
createHmac,
|
|
4
|
+
randomBytes,
|
|
5
|
+
timingSafeEqual,
|
|
6
|
+
} from "node:crypto";
|
|
2
7
|
|
|
3
8
|
import type { H3Event } from "h3";
|
|
4
9
|
import { getHeader } from "h3";
|
|
@@ -15,6 +20,349 @@ const BUILDER_BROWSER_HOST = "agent-native-browser";
|
|
|
15
20
|
const BUILDER_BROWSER_CLIENT_ID = "Agent Native Browser";
|
|
16
21
|
|
|
17
22
|
export const BUILDER_CALLBACK_PATH = "/_agent-native/builder/callback";
|
|
23
|
+
export const BUILDER_RELAY_PATH = "/_agent-native/builder/relay";
|
|
24
|
+
export const BUILDER_RELAY_STATE_PARAM = "_an_relay";
|
|
25
|
+
export const BUILDER_RELAY_SECRET_ENV = "AGENT_NATIVE_BUILDER_RELAY_SECRET";
|
|
26
|
+
export const BUILDER_RELAY_TARGET_ORIGINS_ENV =
|
|
27
|
+
"AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS";
|
|
28
|
+
export const BUILDER_RELAY_TIMESTAMP_HEADER = "x-agent-native-relay-timestamp";
|
|
29
|
+
export const BUILDER_RELAY_FLOW_HEADER = "x-agent-native-relay-flow";
|
|
30
|
+
export const BUILDER_RELAY_SIGNATURE_HEADER = "x-agent-native-relay-signature";
|
|
31
|
+
|
|
32
|
+
const BUILDER_RELAY_PURPOSE = "builder-preview-callback-relay";
|
|
33
|
+
const BUILDER_RELAY_STATE_VERSION = 1;
|
|
34
|
+
const BUILDER_RELAY_TTL_MS = 10 * 60 * 1000;
|
|
35
|
+
const BUILDER_RELAY_REQUEST_SKEW_MS = 2 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
export interface BuilderPreviewRelayState {
|
|
38
|
+
v: 1;
|
|
39
|
+
purpose: typeof BUILDER_RELAY_PURPOSE;
|
|
40
|
+
flowId: string;
|
|
41
|
+
ownerEmail: string;
|
|
42
|
+
targetOrigin: string;
|
|
43
|
+
basePath: string;
|
|
44
|
+
iat: number;
|
|
45
|
+
exp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BuilderRelayCredentials {
|
|
49
|
+
privateKey: string;
|
|
50
|
+
publicKey: string;
|
|
51
|
+
userId: string | null;
|
|
52
|
+
orgName: string | null;
|
|
53
|
+
orgKind: string | null;
|
|
54
|
+
subscription: string | null;
|
|
55
|
+
subscriptionLevel: string | null;
|
|
56
|
+
subscriptionName: string | null;
|
|
57
|
+
isEnterprise: boolean | null;
|
|
58
|
+
isFreeAccount: boolean | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BuilderRelayRequestBody {
|
|
62
|
+
relayState: string;
|
|
63
|
+
credentials: BuilderRelayCredentials;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function builderRelaySecret(): string {
|
|
67
|
+
const secret = process.env[BUILDER_RELAY_SECRET_ENV]?.trim();
|
|
68
|
+
if (!secret) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${BUILDER_RELAY_SECRET_ENV} is required for Builder preview authorization relay.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (secret.length < 32) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${BUILDER_RELAY_SECRET_ENV} must be at least 32 characters long.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return secret;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function builderRelayMac(value: string): string {
|
|
82
|
+
return createHmac("sha256", builderRelaySecret())
|
|
83
|
+
.update(value)
|
|
84
|
+
.digest("base64url");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function safeEqualText(expected: string, actual: string): boolean {
|
|
88
|
+
const expectedBuffer = Buffer.from(expected);
|
|
89
|
+
const actualBuffer = Buffer.from(actual);
|
|
90
|
+
return (
|
|
91
|
+
expectedBuffer.length === actualBuffer.length &&
|
|
92
|
+
timingSafeEqual(expectedBuffer, actualBuffer)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeBuilderRelayBasePath(value: string): string | null {
|
|
97
|
+
if (!value) return "";
|
|
98
|
+
if (!value.startsWith("/") || value.includes("?") || value.includes("#")) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const normalized = value.replace(/\/+$/, "");
|
|
102
|
+
if (normalized.split("/").some((part) => part === "." || part === "..")) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isSafeBuilderRelayTargetOrigin(value: string): boolean {
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(value);
|
|
111
|
+
if (
|
|
112
|
+
url.origin !== value ||
|
|
113
|
+
url.username ||
|
|
114
|
+
url.password ||
|
|
115
|
+
url.hostname.includes("*")
|
|
116
|
+
)
|
|
117
|
+
return false;
|
|
118
|
+
const hostname = url.hostname.toLowerCase();
|
|
119
|
+
const loopback =
|
|
120
|
+
hostname === "localhost" ||
|
|
121
|
+
hostname === "127.0.0.1" ||
|
|
122
|
+
hostname === "::1" ||
|
|
123
|
+
hostname === "[::1]";
|
|
124
|
+
if (loopback)
|
|
125
|
+
return url.protocol === "http:" && process.env.NODE_ENV !== "production";
|
|
126
|
+
if (url.protocol !== "https:") return false;
|
|
127
|
+
return (
|
|
128
|
+
hostname.endsWith(".netlify.app") ||
|
|
129
|
+
hostname.endsWith(".vercel.app") ||
|
|
130
|
+
hostname === "agent-native.com" ||
|
|
131
|
+
hostname.endsWith(".agent-native.com") ||
|
|
132
|
+
hostname.endsWith(".builder.io") ||
|
|
133
|
+
hostname.endsWith(".builderio.xyz") ||
|
|
134
|
+
hostname.endsWith(".builderio.dev") ||
|
|
135
|
+
hostname.endsWith(".builder.codes")
|
|
136
|
+
);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
|
|
143
|
+
if (!isSafeBuilderRelayTargetOrigin(value)) return false;
|
|
144
|
+
const hostname = new URL(value).hostname.toLowerCase();
|
|
145
|
+
if (
|
|
146
|
+
hostname.endsWith(".netlify.app") &&
|
|
147
|
+
!/^[a-f0-9]{24}--[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.netlify\.app$/.test(
|
|
148
|
+
hostname,
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const configured = process.env[BUILDER_RELAY_TARGET_ORIGINS_ENV];
|
|
154
|
+
if (!configured) return false;
|
|
155
|
+
return configured
|
|
156
|
+
.split(",")
|
|
157
|
+
.map((origin) => origin.trim())
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
.some((origin) => origin === value && !origin.includes("*"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function signBuilderPreviewRelayState(input: {
|
|
163
|
+
ownerEmail: string;
|
|
164
|
+
targetOrigin: string;
|
|
165
|
+
basePath?: string;
|
|
166
|
+
flowId?: string;
|
|
167
|
+
now?: number;
|
|
168
|
+
}): { state: string; payload: BuilderPreviewRelayState } {
|
|
169
|
+
if (!isSafeBuilderRelayTargetOrigin(input.targetOrigin)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"Builder relay target origin is not an approved preview origin.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const basePath = normalizeBuilderRelayBasePath(input.basePath ?? "");
|
|
175
|
+
if (basePath === null) throw new Error("Builder relay base path is invalid.");
|
|
176
|
+
const now = input.now ?? Date.now();
|
|
177
|
+
const payload: BuilderPreviewRelayState = {
|
|
178
|
+
v: BUILDER_RELAY_STATE_VERSION,
|
|
179
|
+
purpose: BUILDER_RELAY_PURPOSE,
|
|
180
|
+
flowId: input.flowId ?? randomBytes(24).toString("base64url"),
|
|
181
|
+
ownerEmail: input.ownerEmail,
|
|
182
|
+
targetOrigin: input.targetOrigin,
|
|
183
|
+
basePath,
|
|
184
|
+
iat: now,
|
|
185
|
+
exp: now + BUILDER_RELAY_TTL_MS,
|
|
186
|
+
};
|
|
187
|
+
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString(
|
|
188
|
+
"base64url",
|
|
189
|
+
);
|
|
190
|
+
return { state: `${encoded}.${builderRelayMac(encoded)}`, payload };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function verifyBuilderPreviewRelayState(
|
|
194
|
+
state: string | null | undefined,
|
|
195
|
+
options: { now?: number } = {},
|
|
196
|
+
): BuilderPreviewRelayState | null {
|
|
197
|
+
if (!state) return null;
|
|
198
|
+
const parts = state.split(".");
|
|
199
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
200
|
+
if (!safeEqualText(builderRelayMac(parts[0]), parts[1])) return null;
|
|
201
|
+
let value: unknown;
|
|
202
|
+
try {
|
|
203
|
+
value = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (!value || typeof value !== "object") return null;
|
|
208
|
+
const payload = value as Partial<BuilderPreviewRelayState>;
|
|
209
|
+
const now = options.now ?? Date.now();
|
|
210
|
+
if (
|
|
211
|
+
payload.v !== BUILDER_RELAY_STATE_VERSION ||
|
|
212
|
+
payload.purpose !== BUILDER_RELAY_PURPOSE ||
|
|
213
|
+
typeof payload.flowId !== "string" ||
|
|
214
|
+
!/^[A-Za-z0-9_-]{24,128}$/.test(payload.flowId) ||
|
|
215
|
+
typeof payload.ownerEmail !== "string" ||
|
|
216
|
+
!payload.ownerEmail.includes("@") ||
|
|
217
|
+
typeof payload.targetOrigin !== "string" ||
|
|
218
|
+
!isSafeBuilderRelayTargetOrigin(payload.targetOrigin) ||
|
|
219
|
+
typeof payload.basePath !== "string" ||
|
|
220
|
+
normalizeBuilderRelayBasePath(payload.basePath) !== payload.basePath ||
|
|
221
|
+
typeof payload.iat !== "number" ||
|
|
222
|
+
typeof payload.exp !== "number" ||
|
|
223
|
+
payload.exp <= payload.iat ||
|
|
224
|
+
payload.exp - payload.iat > BUILDER_RELAY_TTL_MS ||
|
|
225
|
+
payload.iat > now + BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
226
|
+
payload.exp < now
|
|
227
|
+
) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return payload as BuilderPreviewRelayState;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Corporate callback trust check. Preview-side state verification and relay
|
|
235
|
+
* receipt deliberately do not require this callback-only allowlist.
|
|
236
|
+
*/
|
|
237
|
+
export function verifyBuilderPreviewRelayStateForCallback(
|
|
238
|
+
state: string | null | undefined,
|
|
239
|
+
options: { now?: number } = {},
|
|
240
|
+
): BuilderPreviewRelayState | null {
|
|
241
|
+
const payload = verifyBuilderPreviewRelayState(state, options);
|
|
242
|
+
return payload && isTrustedBuilderRelayTargetOrigin(payload.targetOrigin)
|
|
243
|
+
? payload
|
|
244
|
+
: null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getBuilderPreviewRelayUrl(
|
|
248
|
+
payload: BuilderPreviewRelayState,
|
|
249
|
+
): string {
|
|
250
|
+
return `${payload.targetOrigin}${payload.basePath}${BUILDER_RELAY_PATH}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function builderRelayBodyDigest(body: string): string {
|
|
254
|
+
return createHash("sha256").update(body).digest("base64url");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function builderRelayRequestSignature(
|
|
258
|
+
timestamp: number,
|
|
259
|
+
flowId: string,
|
|
260
|
+
body: string,
|
|
261
|
+
): string {
|
|
262
|
+
return builderRelayMac(
|
|
263
|
+
`v1.${timestamp}.${flowId}.${builderRelayBodyDigest(body)}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function createBuilderRelayRequest(
|
|
268
|
+
relayState: string,
|
|
269
|
+
credentials: BuilderRelayCredentials,
|
|
270
|
+
options: { now?: number } = {},
|
|
271
|
+
): { body: string; headers: Record<string, string>; url: string } {
|
|
272
|
+
const payload = verifyBuilderPreviewRelayState(relayState, options);
|
|
273
|
+
if (!payload) throw new Error("Builder relay state is invalid or expired.");
|
|
274
|
+
const body = JSON.stringify({
|
|
275
|
+
relayState,
|
|
276
|
+
credentials,
|
|
277
|
+
} satisfies BuilderRelayRequestBody);
|
|
278
|
+
const timestamp = options.now ?? Date.now();
|
|
279
|
+
return {
|
|
280
|
+
body,
|
|
281
|
+
url: getBuilderPreviewRelayUrl(payload),
|
|
282
|
+
headers: {
|
|
283
|
+
"content-type": "application/json",
|
|
284
|
+
[BUILDER_RELAY_TIMESTAMP_HEADER]: String(timestamp),
|
|
285
|
+
[BUILDER_RELAY_FLOW_HEADER]: payload.flowId,
|
|
286
|
+
[BUILDER_RELAY_SIGNATURE_HEADER]: builderRelayRequestSignature(
|
|
287
|
+
timestamp,
|
|
288
|
+
payload.flowId,
|
|
289
|
+
body,
|
|
290
|
+
),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function verifyBuilderRelayRequest(input: {
|
|
296
|
+
body: string;
|
|
297
|
+
timestamp: string | null | undefined;
|
|
298
|
+
flowId: string | null | undefined;
|
|
299
|
+
signature: string | null | undefined;
|
|
300
|
+
requestOrigin: string;
|
|
301
|
+
requestBasePath: string;
|
|
302
|
+
now?: number;
|
|
303
|
+
}): {
|
|
304
|
+
payload: BuilderPreviewRelayState;
|
|
305
|
+
body: BuilderRelayRequestBody;
|
|
306
|
+
} | null {
|
|
307
|
+
const timestamp = Number(input.timestamp);
|
|
308
|
+
const now = input.now ?? Date.now();
|
|
309
|
+
if (
|
|
310
|
+
!Number.isFinite(timestamp) ||
|
|
311
|
+
Math.abs(now - timestamp) > BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
312
|
+
!input.flowId ||
|
|
313
|
+
!input.signature ||
|
|
314
|
+
!safeEqualText(
|
|
315
|
+
builderRelayRequestSignature(timestamp, input.flowId, input.body),
|
|
316
|
+
input.signature,
|
|
317
|
+
)
|
|
318
|
+
) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
let body: BuilderRelayRequestBody;
|
|
322
|
+
try {
|
|
323
|
+
body = JSON.parse(input.body) as BuilderRelayRequestBody;
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const payload = verifyBuilderPreviewRelayState(body.relayState, { now });
|
|
328
|
+
if (
|
|
329
|
+
!payload ||
|
|
330
|
+
payload.flowId !== input.flowId ||
|
|
331
|
+
payload.targetOrigin !== input.requestOrigin ||
|
|
332
|
+
payload.basePath !== input.requestBasePath ||
|
|
333
|
+
!body.credentials ||
|
|
334
|
+
typeof body.credentials.privateKey !== "string" ||
|
|
335
|
+
typeof body.credentials.publicKey !== "string" ||
|
|
336
|
+
!body.credentials.privateKey ||
|
|
337
|
+
!body.credentials.publicKey
|
|
338
|
+
) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const nullableString = (value: unknown): string | null =>
|
|
342
|
+
typeof value === "string" ? value : null;
|
|
343
|
+
const nullableBoolean = (value: unknown): boolean | null =>
|
|
344
|
+
typeof value === "boolean" ? value : null;
|
|
345
|
+
return {
|
|
346
|
+
payload,
|
|
347
|
+
body: {
|
|
348
|
+
relayState: body.relayState,
|
|
349
|
+
// Explicitly rebuild the credential payload. Extra fields such as an
|
|
350
|
+
// attacker-supplied ownerEmail/orgId never reach the credential writer.
|
|
351
|
+
credentials: {
|
|
352
|
+
privateKey: body.credentials.privateKey,
|
|
353
|
+
publicKey: body.credentials.publicKey,
|
|
354
|
+
userId: nullableString(body.credentials.userId),
|
|
355
|
+
orgName: nullableString(body.credentials.orgName),
|
|
356
|
+
orgKind: nullableString(body.credentials.orgKind),
|
|
357
|
+
subscription: nullableString(body.credentials.subscription),
|
|
358
|
+
subscriptionLevel: nullableString(body.credentials.subscriptionLevel),
|
|
359
|
+
subscriptionName: nullableString(body.credentials.subscriptionName),
|
|
360
|
+
isEnterprise: nullableBoolean(body.credentials.isEnterprise),
|
|
361
|
+
isFreeAccount: nullableBoolean(body.credentials.isFreeAccount),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
18
366
|
|
|
19
367
|
function escapeHtml(value: string): string {
|
|
20
368
|
return value.replace(/[&<>"']/g, (ch) => {
|
|
@@ -516,6 +864,7 @@ export function buildBuilderCliAuthUrl(
|
|
|
516
864
|
state: string | null = null,
|
|
517
865
|
options: {
|
|
518
866
|
previewOrigin?: string;
|
|
867
|
+
relayState?: string;
|
|
519
868
|
tracking?: BuilderConnectTrackingParams;
|
|
520
869
|
} = {},
|
|
521
870
|
): string {
|
|
@@ -536,6 +885,9 @@ export function buildBuilderCliAuthUrl(
|
|
|
536
885
|
if (state) {
|
|
537
886
|
callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);
|
|
538
887
|
}
|
|
888
|
+
if (options.relayState) {
|
|
889
|
+
callbackUrl.searchParams.set(BUILDER_RELAY_STATE_PARAM, options.relayState);
|
|
890
|
+
}
|
|
539
891
|
// When the cli-auth allow-list forces preview_url onto the gateway origin,
|
|
540
892
|
// the callback would otherwise lose the real opener origin and post its
|
|
541
893
|
// success message to the gateway instead of the preview tab. Embed the
|