@agent-native/core 0.201.0 → 0.201.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/dist/a2a/handlers.js +96 -44
- package/dist/a2a/organization-identity.d.ts +9 -0
- package/dist/a2a/organization-identity.js +34 -0
- package/dist/a2a/server.d.ts +13 -7
- package/dist/a2a/server.js +187 -88
- package/dist/a2a/task-store.d.ts +7 -2
- package/dist/a2a/task-store.js +23 -8
- package/dist/a2a-claims.js +12 -2
- package/dist/app-config/mcp.d.ts +4 -0
- package/dist/app-config/mcp.js +7 -0
- package/dist/app-config/schema.d.ts +3 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/route-chunk-recovery.js +75 -11
- package/dist/client/route-warmup.d.ts +2 -0
- package/dist/client/route-warmup.js +3 -0
- package/dist/client/vite-dev-recovery-script.js +85 -17
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +3 -1
- package/dist/feature-flags/a2a-action-route.js +3 -4
- package/dist/mcp/build-server.d.ts +4 -0
- package/dist/mcp/build-server.js +152 -43
- package/dist/mcp/embed-app.js +5 -5
- package/dist/mcp/server.d.ts +0 -18
- package/dist/mcp/server.js +99 -9
- package/dist/onboarding/app-profile.js +2 -1
- package/dist/onboarding/workspace-services.js +3 -0
- package/dist/org/context.d.ts +13 -0
- package/dist/org/context.js +60 -0
- package/dist/org/handlers.d.ts +1 -1
- package/dist/org/index.d.ts +1 -1
- package/dist/org/membership.d.ts +6 -0
- package/dist/org/membership.js +23 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-routes.js +12 -5
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/builder-browser.d.ts +4 -2
- package/dist/server/builder-browser.js +147 -9
- package/dist/server/core-routes-plugin.js +13 -6
- package/dist/server/entry-server.js +43 -2
- package/dist/server/sentry.js +9 -1
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.js +3 -7
- package/dist/shared/cache-control.d.ts +4 -1
- package/dist/shared/cache-control.js +11 -6
- package/dist/shared/route-chunk-recovery-bootstrap.d.ts +7 -0
- package/dist/shared/route-chunk-recovery-bootstrap.js +38 -0
- package/package.json +5 -3
package/dist/org/membership.d.ts
CHANGED
|
@@ -2,3 +2,9 @@ export declare function isMissingOrganizationTableError(error: unknown): boolean
|
|
|
2
2
|
export declare function isOrgMember(orgId: string, email: string, options?: {
|
|
3
3
|
requireOrganizationMetadata?: boolean;
|
|
4
4
|
}): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* Checks membership for an A2A identity authenticated by an organization
|
|
7
|
+
* secret. Unlike legacy callers, this requires readable organization metadata
|
|
8
|
+
* as well as the membership row before the token subject can become an owner.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isOrgMemberForA2A(orgId: string, email: string): Promise<boolean>;
|
package/dist/org/membership.js
CHANGED
|
@@ -17,7 +17,7 @@ export function isMissingOrganizationTableError(error) {
|
|
|
17
17
|
}
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
async function isOrgMemberWithPolicy(orgId, email, options) {
|
|
21
21
|
const normalized = email.trim().toLowerCase();
|
|
22
22
|
if (!orgId || !normalized)
|
|
23
23
|
return false;
|
|
@@ -41,13 +41,14 @@ export async function isOrgMember(orgId, email, options = {}) {
|
|
|
41
41
|
})).rows;
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
|
-
if (
|
|
45
|
-
!isMissingOrganizationTableError(error))
|
|
44
|
+
if (!isMissingOrganizationTableError(error))
|
|
46
45
|
throw error;
|
|
47
|
-
|
|
46
|
+
if (options.allowMissingOrganizationTable)
|
|
47
|
+
return true;
|
|
48
|
+
throw error;
|
|
48
49
|
}
|
|
49
50
|
const organization = organizationRows[0];
|
|
50
|
-
if (!organization && options.
|
|
51
|
+
if (!organization && options.requireOrganizationRecord)
|
|
51
52
|
return false;
|
|
52
53
|
const linked = String(organization?.identity_authority ?? "").trim() ||
|
|
53
54
|
String(organization?.identity_id ?? "").trim();
|
|
@@ -67,3 +68,20 @@ export async function isOrgMember(orgId, email, options = {}) {
|
|
|
67
68
|
});
|
|
68
69
|
return validation.active;
|
|
69
70
|
}
|
|
71
|
+
export function isOrgMember(orgId, email, options = {}) {
|
|
72
|
+
return isOrgMemberWithPolicy(orgId, email, {
|
|
73
|
+
allowMissingOrganizationTable: !options.requireOrganizationMetadata,
|
|
74
|
+
requireOrganizationRecord: !!options.requireOrganizationMetadata,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks membership for an A2A identity authenticated by an organization
|
|
79
|
+
* secret. Unlike legacy callers, this requires readable organization metadata
|
|
80
|
+
* as well as the membership row before the token subject can become an owner.
|
|
81
|
+
*/
|
|
82
|
+
export function isOrgMemberForA2A(orgId, email) {
|
|
83
|
+
return isOrgMemberWithPolicy(orgId, email, {
|
|
84
|
+
allowMissingOrganizationTable: false,
|
|
85
|
+
requireOrganizationRecord: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -75,9 +75,10 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
+
message?: undefined;
|
|
78
79
|
id?: undefined;
|
|
79
|
-
deleted?: undefined;
|
|
80
80
|
provider?: undefined;
|
|
81
|
+
deleted?: undefined;
|
|
81
82
|
providers: {
|
|
82
83
|
id: string;
|
|
83
84
|
label: string;
|
|
@@ -91,8 +92,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
91
92
|
found?: undefined;
|
|
92
93
|
registered?: undefined;
|
|
93
94
|
label?: undefined;
|
|
94
|
-
message?: undefined;
|
|
95
95
|
} | {
|
|
96
|
+
message?: undefined;
|
|
96
97
|
id?: undefined;
|
|
97
98
|
deleted?: undefined;
|
|
98
99
|
providers?: undefined;
|
|
@@ -101,18 +102,18 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
101
102
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
102
103
|
registered?: undefined;
|
|
103
104
|
label?: undefined;
|
|
104
|
-
message?: undefined;
|
|
105
105
|
} | {
|
|
106
|
-
|
|
106
|
+
message?: undefined;
|
|
107
107
|
provider?: undefined;
|
|
108
|
+
deleted?: undefined;
|
|
108
109
|
providers?: undefined;
|
|
109
110
|
count?: undefined;
|
|
110
111
|
found: boolean;
|
|
111
112
|
id: string;
|
|
112
113
|
registered?: undefined;
|
|
113
114
|
label?: undefined;
|
|
114
|
-
message?: undefined;
|
|
115
115
|
} | {
|
|
116
|
+
message?: undefined;
|
|
116
117
|
provider?: undefined;
|
|
117
118
|
providers?: undefined;
|
|
118
119
|
count?: undefined;
|
|
@@ -121,10 +122,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
121
122
|
id: string;
|
|
122
123
|
registered?: undefined;
|
|
123
124
|
label?: undefined;
|
|
124
|
-
message?: undefined;
|
|
125
125
|
} | {
|
|
126
|
-
deleted?: undefined;
|
|
127
126
|
provider?: undefined;
|
|
127
|
+
deleted?: undefined;
|
|
128
128
|
providers?: undefined;
|
|
129
129
|
count?: undefined;
|
|
130
130
|
found?: undefined;
|
|
@@ -311,10 +311,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
311
311
|
notes?: string | undefined;
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
|
-
|
|
314
|
+
id?: undefined;
|
|
315
315
|
deleted?: undefined;
|
|
316
316
|
message?: undefined;
|
|
317
|
-
id?: undefined;
|
|
318
317
|
providers: {
|
|
319
318
|
id: string;
|
|
320
319
|
label: string;
|
|
@@ -325,44 +324,45 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
325
324
|
updatedAt: number;
|
|
326
325
|
}[];
|
|
327
326
|
count: number;
|
|
327
|
+
provider?: undefined;
|
|
328
328
|
found?: undefined;
|
|
329
329
|
registered?: undefined;
|
|
330
330
|
label?: undefined;
|
|
331
331
|
} | {
|
|
332
|
+
id?: undefined;
|
|
332
333
|
count?: undefined;
|
|
333
334
|
deleted?: undefined;
|
|
334
335
|
message?: undefined;
|
|
335
|
-
id?: undefined;
|
|
336
336
|
providers?: undefined;
|
|
337
337
|
found: boolean;
|
|
338
338
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
339
339
|
registered?: undefined;
|
|
340
340
|
label?: undefined;
|
|
341
341
|
} | {
|
|
342
|
-
provider?: undefined;
|
|
343
342
|
count?: undefined;
|
|
344
343
|
deleted?: undefined;
|
|
345
344
|
message?: undefined;
|
|
346
345
|
providers?: undefined;
|
|
346
|
+
provider?: undefined;
|
|
347
347
|
found: boolean;
|
|
348
348
|
id: string;
|
|
349
349
|
registered?: undefined;
|
|
350
350
|
label?: undefined;
|
|
351
351
|
} | {
|
|
352
|
-
provider?: undefined;
|
|
353
352
|
count?: undefined;
|
|
354
353
|
message?: undefined;
|
|
355
354
|
providers?: undefined;
|
|
355
|
+
provider?: undefined;
|
|
356
356
|
found?: undefined;
|
|
357
357
|
deleted: boolean;
|
|
358
358
|
id: string;
|
|
359
359
|
registered?: undefined;
|
|
360
360
|
label?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
provider?: undefined;
|
|
363
362
|
count?: undefined;
|
|
364
363
|
deleted?: undefined;
|
|
365
364
|
providers?: undefined;
|
|
365
|
+
provider?: undefined;
|
|
366
366
|
found?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -58,47 +58,47 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
58
58
|
error: string;
|
|
59
59
|
errorCode?: undefined;
|
|
60
60
|
retryable?: undefined;
|
|
61
|
-
ok?: undefined;
|
|
62
61
|
status?: undefined;
|
|
62
|
+
ok?: undefined;
|
|
63
63
|
} | {
|
|
64
64
|
error: string;
|
|
65
65
|
errorCode: string;
|
|
66
66
|
retryable?: undefined;
|
|
67
|
-
ok?: undefined;
|
|
68
67
|
status?: undefined;
|
|
68
|
+
ok?: undefined;
|
|
69
69
|
} | {
|
|
70
70
|
error: string;
|
|
71
71
|
errorCode: string;
|
|
72
72
|
retryable: boolean;
|
|
73
|
-
ok?: undefined;
|
|
74
73
|
status?: undefined;
|
|
74
|
+
ok?: undefined;
|
|
75
75
|
} | {
|
|
76
|
-
error?: undefined;
|
|
77
76
|
errorCode?: undefined;
|
|
78
77
|
retryable?: undefined;
|
|
79
78
|
ok: boolean;
|
|
80
79
|
status: string;
|
|
80
|
+
error?: undefined;
|
|
81
81
|
} | {
|
|
82
|
-
ok?: undefined;
|
|
83
82
|
error: string;
|
|
84
83
|
removed?: undefined;
|
|
84
|
+
ok?: undefined;
|
|
85
85
|
} | {
|
|
86
|
-
error?: undefined;
|
|
87
86
|
ok: boolean;
|
|
88
87
|
removed: boolean;
|
|
88
|
+
error?: undefined;
|
|
89
89
|
}>>;
|
|
90
90
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
91
|
-
ok?: undefined;
|
|
92
91
|
error: string;
|
|
93
92
|
note?: undefined;
|
|
93
|
+
ok?: undefined;
|
|
94
94
|
} | {
|
|
95
|
-
error?: undefined;
|
|
96
95
|
ok: boolean;
|
|
97
96
|
note?: undefined;
|
|
98
|
-
} | {
|
|
99
97
|
error?: undefined;
|
|
98
|
+
} | {
|
|
100
99
|
ok: boolean;
|
|
101
100
|
note: string;
|
|
101
|
+
error?: undefined;
|
|
102
102
|
} | {
|
|
103
103
|
note?: undefined;
|
|
104
104
|
ok: boolean;
|
|
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
|
|
|
126
126
|
} | {
|
|
127
127
|
error: string;
|
|
128
128
|
} | {
|
|
129
|
-
error?: undefined;
|
|
130
129
|
ok: boolean;
|
|
131
130
|
key: string;
|
|
132
|
-
} | {
|
|
133
131
|
error?: undefined;
|
|
132
|
+
} | {
|
|
134
133
|
ok: boolean;
|
|
135
134
|
removed: boolean;
|
|
135
|
+
error?: undefined;
|
|
136
136
|
}>>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { createError, defineEventHandler, setResponseStatus, setResponseHeader, getMethod, getQuery, getHeader, readBody as readH3Body, } from "h3";
|
|
2
2
|
import "../authorization/check-action.js";
|
|
3
3
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
4
|
+
import { isA2AIdentityVerificationUnavailableError } from "../a2a/server.js";
|
|
4
5
|
import { actionCallEmitsChange, actionChangeResource, } from "../action-call-classification.js";
|
|
5
6
|
import { ActionContractError, isActionContractError, isActionExposedToExternalAgents, isAgentActionStopError, validateActionArgs, } from "../action.js";
|
|
6
7
|
import { LLM_CREDENTIAL_KEYS, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "../agent/engine/credential-errors.js";
|
|
7
8
|
import { isTransientDatabaseError } from "../db/client.js";
|
|
8
9
|
import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
|
|
9
10
|
import { isFeatureFlagAdminEmail } from "../feature-flags/permissions.js";
|
|
10
|
-
import { isFederationMembershipValidatedForEvent,
|
|
11
|
+
import { isFederationMembershipValidatedForEvent, resolveOrgIdForEmail, } from "../org/context.js";
|
|
11
12
|
import { LLM_PROVIDER_MISSING_ERROR_CODE, LLM_PROVIDER_MISSING_STATUS, } from "../shared/action-error-codes.js";
|
|
12
13
|
import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
|
|
13
14
|
import { EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
|
|
@@ -66,8 +67,8 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
|
|
|
66
67
|
const claims = await verifyA2ATokenWithClaims(token, event);
|
|
67
68
|
if (!claims || !claims.scope.includes(required))
|
|
68
69
|
throw new Error("Invalid feature flag delegation");
|
|
69
|
-
const
|
|
70
|
-
if (!
|
|
70
|
+
const orgId = claims.orgId?.trim() || null;
|
|
71
|
+
if (!orgId && !isFeatureFlagAdminEmail(claims.email))
|
|
71
72
|
throw new Error("Invalid feature flag delegation");
|
|
72
73
|
if (actionName === "set-feature-flag" &&
|
|
73
74
|
(await consumeOneTimeJti(claims.jti))) {
|
|
@@ -75,7 +76,7 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
|
|
|
75
76
|
}
|
|
76
77
|
return {
|
|
77
78
|
owner: claims.email,
|
|
78
|
-
orgId
|
|
79
|
+
orgId,
|
|
79
80
|
anonymous: false,
|
|
80
81
|
delegationJti: claims.jti,
|
|
81
82
|
delegationIssuer: claims.issuer,
|
|
@@ -386,7 +387,13 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
|
|
|
386
387
|
if (!caller)
|
|
387
388
|
caller = await resolveFeatureFlagA2ACaller(event, name);
|
|
388
389
|
}
|
|
389
|
-
catch {
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (isA2AIdentityVerificationUnavailableError(error)) {
|
|
392
|
+
throw createError({
|
|
393
|
+
statusCode: 503,
|
|
394
|
+
statusMessage: "Identity verification temporarily unavailable",
|
|
395
|
+
});
|
|
396
|
+
}
|
|
390
397
|
throw createError({
|
|
391
398
|
statusCode: 401,
|
|
392
399
|
statusMessage: "Unauthorized",
|
|
@@ -65,6 +65,7 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
|
|
|
65
65
|
error: any;
|
|
66
66
|
code: any;
|
|
67
67
|
} | {
|
|
68
|
+
error?: undefined;
|
|
68
69
|
errorCode?: undefined;
|
|
69
70
|
code?: undefined;
|
|
70
71
|
ok: boolean;
|
|
@@ -72,6 +73,5 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
|
|
|
72
73
|
baseUrlKey?: string;
|
|
73
74
|
scope: AgentEngineApiKeyScope;
|
|
74
75
|
defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
|
|
75
|
-
error?: undefined;
|
|
76
76
|
}>>;
|
|
77
77
|
export {};
|
|
@@ -8,8 +8,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
|
|
|
8
8
|
* resolveEngine's credential detection.
|
|
9
9
|
*/
|
|
10
10
|
export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
11
|
-
error: string;
|
|
12
11
|
ok?: undefined;
|
|
12
|
+
error: string;
|
|
13
13
|
scope?: undefined;
|
|
14
14
|
} | {
|
|
15
15
|
ok: boolean;
|
|
@@ -43,8 +43,8 @@ export declare class BuilderAccountProvisioningError extends Error {
|
|
|
43
43
|
export declare function isBuilderAccountAlreadyExistsError(error: unknown): boolean;
|
|
44
44
|
export declare function createBuilderConnectState(): string;
|
|
45
45
|
export declare function isSignedBuilderConnectState(value: string | null | undefined): boolean;
|
|
46
|
-
export declare function isBuilderConnectCallbackUrlAllowed(candidate: string, event: H3Event): boolean;
|
|
47
|
-
export declare function resolveBuilderConnectCallbackUrl(event: H3Event, state?: string): string | null;
|
|
46
|
+
export declare function isBuilderConnectCallbackUrlAllowed(candidate: string, event: H3Event, callbackOriginOverride?: string): boolean;
|
|
47
|
+
export declare function resolveBuilderConnectCallbackUrl(event: H3Event, state?: string, callbackOriginOverride?: string): string | null;
|
|
48
48
|
export declare function isSafeBuilderRelayTargetOrigin(value: string): boolean;
|
|
49
49
|
export declare function isTrustedBuilderRelayTargetOrigin(value: string): boolean;
|
|
50
50
|
export declare function resolveBuilderPreviewRelayTargetOrigin(previewOrigin: string): string;
|
|
@@ -94,6 +94,7 @@ export declare const BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM = "agentNativeCon
|
|
|
94
94
|
export declare const BUILDER_AGENT_NATIVE_APP_PARAM = "agentNativeApp";
|
|
95
95
|
export declare const BUILDER_AGENT_NATIVE_TEMPLATE_PARAM = "agentNativeTemplate";
|
|
96
96
|
export declare const BUILDER_CONNECT_MODE_PARAM = "_an_mode";
|
|
97
|
+
export declare const BUILDER_PREVIEW_ORIGIN_HEADER = "x-agent-native-preview-origin";
|
|
97
98
|
export declare const BUILDER_AGENT_NATIVE_PROVISION_MODE = "agent-native";
|
|
98
99
|
export declare const BUILDER_PROVISIONING_TOKEN_PARAM = "_an_provision";
|
|
99
100
|
export declare const BUILDER_CONNECT_ATTEMPT_PARAM = "_an_connect_attempt";
|
|
@@ -207,6 +208,7 @@ export declare function buildBuilderCliAuthUrl(callbackOrigin: string, state?: s
|
|
|
207
208
|
}): string;
|
|
208
209
|
export declare function getBuilderBrowserConnectUrl(origin: string): string;
|
|
209
210
|
export declare function getBuilderBrowserConnectUrlForOwner(origin: string, ownerEmail: string | null | undefined): string;
|
|
211
|
+
export declare function getBuilderConnectCallbackOriginFromUrl(callbackUrl: string): string | null;
|
|
210
212
|
export declare function getBuilderBrowserOriginForEvent(event: H3Event): string;
|
|
211
213
|
export declare function getBuilderCliAuthCallbackOriginForEvent(event: H3Event): string;
|
|
212
214
|
export declare function getBuilderBrowserStatus(origin: string): BuilderBrowserStatus;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, createHmac, randomBytes, timingSafeEqual, } from "node:crypto";
|
|
2
|
-
import { getHeader, getRequestIP, setResponseHeader, setResponseStatus, } from "h3";
|
|
2
|
+
import { getHeader, getQuery, getRequestIP, setResponseHeader, setResponseStatus, } from "h3";
|
|
3
3
|
import { ActionContractError } from "../action.js";
|
|
4
4
|
import { getSetting } from "../settings/store.js";
|
|
5
5
|
import { applyBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
|
|
@@ -112,8 +112,8 @@ export function isSignedBuilderConnectState(value) {
|
|
|
112
112
|
return safeEqualText(expected, signature);
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
|
-
export function isBuilderConnectCallbackUrlAllowed(candidate, event) {
|
|
116
|
-
const callbackOrigin = getBuilderConnectCallbackOrigin(event);
|
|
115
|
+
export function isBuilderConnectCallbackUrlAllowed(candidate, event, callbackOriginOverride) {
|
|
116
|
+
const callbackOrigin = getBuilderConnectCallbackOrigin(event, callbackOriginOverride);
|
|
117
117
|
if (!callbackOrigin ||
|
|
118
118
|
!isAllowedOAuthRedirectUri(candidate, event, callbackOrigin))
|
|
119
119
|
return false;
|
|
@@ -137,16 +137,18 @@ export function isBuilderConnectCallbackUrlAllowed(candidate, event) {
|
|
|
137
137
|
}
|
|
138
138
|
return url.protocol === "https:";
|
|
139
139
|
}
|
|
140
|
-
export function resolveBuilderConnectCallbackUrl(event, state) {
|
|
140
|
+
export function resolveBuilderConnectCallbackUrl(event, state, callbackOriginOverride) {
|
|
141
141
|
const stateSuffix = state ? `?state=${encodeURIComponent(state)}` : "";
|
|
142
|
-
const callbackOrigin = getBuilderConnectCallbackOrigin(event);
|
|
142
|
+
const callbackOrigin = getBuilderConnectCallbackOrigin(event, callbackOriginOverride);
|
|
143
143
|
if (!callbackOrigin)
|
|
144
144
|
return null;
|
|
145
145
|
const withBase = `${callbackOrigin}${getAppBasePath()}${BUILDER_CALLBACK_PATH}${stateSuffix}`;
|
|
146
|
-
if (isBuilderConnectCallbackUrlAllowed(withBase, event))
|
|
146
|
+
if (isBuilderConnectCallbackUrlAllowed(withBase, event, callbackOriginOverride)) {
|
|
147
147
|
return withBase;
|
|
148
|
+
}
|
|
148
149
|
const root = `${callbackOrigin}${BUILDER_CALLBACK_PATH}${stateSuffix}`;
|
|
149
|
-
if (root !== withBase &&
|
|
150
|
+
if (root !== withBase &&
|
|
151
|
+
isBuilderConnectCallbackUrlAllowed(root, event, callbackOriginOverride)) {
|
|
150
152
|
return root;
|
|
151
153
|
}
|
|
152
154
|
return null;
|
|
@@ -429,6 +431,9 @@ export const BUILDER_AGENT_NATIVE_CONNECT_SOURCE_PARAM = "agentNativeConnectSour
|
|
|
429
431
|
export const BUILDER_AGENT_NATIVE_APP_PARAM = "agentNativeApp";
|
|
430
432
|
export const BUILDER_AGENT_NATIVE_TEMPLATE_PARAM = "agentNativeTemplate";
|
|
431
433
|
export const BUILDER_CONNECT_MODE_PARAM = "_an_mode";
|
|
434
|
+
export const BUILDER_PREVIEW_ORIGIN_HEADER = "x-agent-native-preview-origin";
|
|
435
|
+
const BUILDER_PREVIEW_ORIGIN_PARAM = "_an_preview_origin";
|
|
436
|
+
const BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM = "_an_preview_origin_signature";
|
|
432
437
|
export const BUILDER_AGENT_NATIVE_PROVISION_MODE = "agent-native";
|
|
433
438
|
export const BUILDER_PROVISIONING_TOKEN_PARAM = "_an_provision";
|
|
434
439
|
export const BUILDER_CONNECT_ATTEMPT_PARAM = "_an_connect_attempt";
|
|
@@ -958,7 +963,15 @@ function isConfiguredBuilderRequestHost(event, host) {
|
|
|
958
963
|
(process.env.NODE_ENV === "production" ? "https" : "http");
|
|
959
964
|
return isConfiguredAppOrigin(`${proto}://${host}`);
|
|
960
965
|
}
|
|
961
|
-
function getBuilderConnectCallbackOrigin(event) {
|
|
966
|
+
function getBuilderConnectCallbackOrigin(event, originOverride) {
|
|
967
|
+
if (originOverride) {
|
|
968
|
+
return isAllowedBuilderConnectOrigin(originOverride)
|
|
969
|
+
? originOverride
|
|
970
|
+
: null;
|
|
971
|
+
}
|
|
972
|
+
const signedPreviewOrigin = getSignedBuilderPreviewOriginFromEvent(event);
|
|
973
|
+
if (signedPreviewOrigin)
|
|
974
|
+
return signedPreviewOrigin;
|
|
962
975
|
const requestHost = firstHeaderValue(readEventHeader(event, "host"));
|
|
963
976
|
const headerHost = getBuilderRequestHost(event);
|
|
964
977
|
if (isRejectedDirectBuilderCloudHost(requestHost, headerHost)) {
|
|
@@ -978,6 +991,123 @@ function getBuilderConnectCallbackOrigin(event) {
|
|
|
978
991
|
}
|
|
979
992
|
return configuredOrigin;
|
|
980
993
|
}
|
|
994
|
+
function isAllowedBuilderConnectOrigin(origin) {
|
|
995
|
+
let url;
|
|
996
|
+
try {
|
|
997
|
+
url = new URL(origin);
|
|
998
|
+
}
|
|
999
|
+
catch {
|
|
1000
|
+
// coercion-ok: a malformed callback origin is explicitly rejected.
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
if (url.origin !== origin ||
|
|
1004
|
+
url.username ||
|
|
1005
|
+
url.password ||
|
|
1006
|
+
url.pathname !== "/" ||
|
|
1007
|
+
url.search ||
|
|
1008
|
+
url.hash) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
if (isTrustedBuilderRequestHost(url.host)) {
|
|
1012
|
+
return (url.protocol === "https:" ||
|
|
1013
|
+
(url.protocol === "http:" && process.env.NODE_ENV !== "production"));
|
|
1014
|
+
}
|
|
1015
|
+
return isConfiguredAppOrigin(origin);
|
|
1016
|
+
}
|
|
1017
|
+
function normalizeBuilderPreviewOrigin(value) {
|
|
1018
|
+
try {
|
|
1019
|
+
const url = new URL(value);
|
|
1020
|
+
if (url.origin !== value ||
|
|
1021
|
+
url.username ||
|
|
1022
|
+
url.password ||
|
|
1023
|
+
url.pathname !== "/" ||
|
|
1024
|
+
url.search ||
|
|
1025
|
+
url.hash ||
|
|
1026
|
+
(url.protocol !== "https:" &&
|
|
1027
|
+
!(url.protocol === "http:" && process.env.NODE_ENV !== "production"))) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
return url.origin;
|
|
1031
|
+
}
|
|
1032
|
+
catch {
|
|
1033
|
+
// coercion-ok: a malformed preview origin is not an allowed origin.
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function getSameOriginBuilderPreviewOrigin(event) {
|
|
1038
|
+
const rawOrigin = firstHeaderValue(readEventHeader(event, BUILDER_PREVIEW_ORIGIN_HEADER));
|
|
1039
|
+
if (!rawOrigin || readEventHeader(event, "sec-fetch-site") === "cross-site") {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
const origin = normalizeBuilderPreviewOrigin(rawOrigin);
|
|
1043
|
+
if (!origin || !isTrustedBuilderRequestHost(new URL(origin).host)) {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
const requestHost = firstHeaderValue(readEventHeader(event, "host"));
|
|
1047
|
+
if (!requestHost ||
|
|
1048
|
+
(!isLoopbackBuilderRequestHost(requestHost) &&
|
|
1049
|
+
requestHost.toLowerCase() !== new URL(origin).host.toLowerCase())) {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
const fetchSite = readEventHeader(event, "sec-fetch-site");
|
|
1053
|
+
const requestOrigin = firstHeaderValue(readEventHeader(event, "origin"));
|
|
1054
|
+
if (fetchSite === "same-origin" || requestOrigin === origin)
|
|
1055
|
+
return origin;
|
|
1056
|
+
const referer = firstHeaderValue(readEventHeader(event, "referer"));
|
|
1057
|
+
if (!referer)
|
|
1058
|
+
return null;
|
|
1059
|
+
try {
|
|
1060
|
+
return new URL(referer).origin === origin ? origin : null;
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
// coercion-ok: an unreadable Referer cannot authorize a preview origin.
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
function signBuilderPreviewOrigin(origin) {
|
|
1068
|
+
return createHmac("sha256", builderConnectStateSigningKeys()[0])
|
|
1069
|
+
.update(`builder-connect-preview-origin\0${origin}`)
|
|
1070
|
+
.digest("base64url");
|
|
1071
|
+
}
|
|
1072
|
+
function getSignedBuilderPreviewOriginFromEvent(event) {
|
|
1073
|
+
const query = getQuery(event);
|
|
1074
|
+
const rawOrigin = query[BUILDER_PREVIEW_ORIGIN_PARAM];
|
|
1075
|
+
const signature = query[BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM];
|
|
1076
|
+
if (typeof rawOrigin !== "string" || typeof signature !== "string") {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
const origin = normalizeBuilderPreviewOrigin(rawOrigin);
|
|
1080
|
+
if (!origin || !isTrustedBuilderRequestHost(new URL(origin).host)) {
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
const requestHost = firstHeaderValue(readEventHeader(event, "host"));
|
|
1084
|
+
if (!requestHost ||
|
|
1085
|
+
(!isLoopbackBuilderRequestHost(requestHost) &&
|
|
1086
|
+
requestHost.toLowerCase() !== new URL(origin).host.toLowerCase())) {
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
return builderConnectStateSigningKeys().some((key) => safeEqualText(createHmac("sha256", key)
|
|
1090
|
+
.update(`builder-connect-preview-origin\0${origin}`)
|
|
1091
|
+
.digest("base64url"), signature))
|
|
1092
|
+
? origin
|
|
1093
|
+
: null;
|
|
1094
|
+
}
|
|
1095
|
+
function addSignedBuilderPreviewOrigin(url, origin) {
|
|
1096
|
+
const parsed = new URL(url);
|
|
1097
|
+
parsed.searchParams.set(BUILDER_PREVIEW_ORIGIN_PARAM, origin);
|
|
1098
|
+
parsed.searchParams.set(BUILDER_PREVIEW_ORIGIN_SIGNATURE_PARAM, signBuilderPreviewOrigin(origin));
|
|
1099
|
+
return parsed.toString();
|
|
1100
|
+
}
|
|
1101
|
+
export function getBuilderConnectCallbackOriginFromUrl(callbackUrl) {
|
|
1102
|
+
try {
|
|
1103
|
+
const origin = new URL(callbackUrl).origin;
|
|
1104
|
+
return isAllowedBuilderConnectOrigin(origin) ? origin : null;
|
|
1105
|
+
}
|
|
1106
|
+
catch {
|
|
1107
|
+
// coercion-ok: a malformed stored callback URL must fail verification.
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
981
1111
|
function isRejectedDirectBuilderCloudHost(requestHost, resolvedHost) {
|
|
982
1112
|
return isBuilderCloudRequestHost(requestHost) && requestHost !== resolvedHost;
|
|
983
1113
|
}
|
|
@@ -986,6 +1116,9 @@ function getConfiguredBuilderFallbackOrigin(event) {
|
|
|
986
1116
|
return isConfiguredAppOrigin(origin) ? origin : null;
|
|
987
1117
|
}
|
|
988
1118
|
export function getBuilderBrowserOriginForEvent(event) {
|
|
1119
|
+
const signedPreviewOrigin = getSignedBuilderPreviewOriginFromEvent(event);
|
|
1120
|
+
if (signedPreviewOrigin)
|
|
1121
|
+
return signedPreviewOrigin;
|
|
989
1122
|
const requestHost = firstHeaderValue(readEventHeader(event, "host"));
|
|
990
1123
|
const headerHost = getBuilderRequestHost(event);
|
|
991
1124
|
if (isRejectedDirectBuilderCloudHost(requestHost, headerHost)) {
|
|
@@ -1057,7 +1190,12 @@ function parseOptionalEnvBoolean(value) {
|
|
|
1057
1190
|
return /^(1|true)$/i.test(value);
|
|
1058
1191
|
}
|
|
1059
1192
|
export function getBuilderBrowserStatusForEvent(event) {
|
|
1060
|
-
|
|
1193
|
+
const previewOrigin = getSameOriginBuilderPreviewOrigin(event);
|
|
1194
|
+
const status = getBuilderBrowserStatus(previewOrigin ?? getBuilderBrowserOriginForEvent(event));
|
|
1195
|
+
if (previewOrigin && status.connectUrl) {
|
|
1196
|
+
status.connectUrl = addSignedBuilderPreviewOrigin(status.connectUrl, previewOrigin);
|
|
1197
|
+
}
|
|
1198
|
+
return status;
|
|
1061
1199
|
}
|
|
1062
1200
|
export const BUILDER_ENV_KEYS = [
|
|
1063
1201
|
"BUILDER_PRIVATE_KEY",
|
|
@@ -59,7 +59,7 @@ import { getSession } from "./auth.js";
|
|
|
59
59
|
import { createAutomationFailureUnsubscribeHandler } from "./automation-failure-notifications.js";
|
|
60
60
|
import { getBetterAuthInternalAdapter, getBetterAuthSync, } from "./better-auth-instance.js";
|
|
61
61
|
import { resolveBuilderRequestAuthorization } from "./builder-api-auth.js";
|
|
62
|
-
import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_MODE_PARAM, BUILDER_AGENT_NATIVE_PROVISION_MODE, BUILDER_CONNECT_ATTEMPT_PARAM, BUILDER_CONNECT_STATE_COOKIE, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, appendBuilderConnectStateCookie, builderConnectTrackingProperties, BUILDER_UPSTREAM_FAILURE_STATUS, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderAccountAlreadyExistsError, isBuilderAccountProvisioningEnabled, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, parseBuilderConnectStateCookie, provisionBuilderAccount, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderConnectCallbackState, resolveBuilderPreviewRelayParentOrigin, removeBuilderConnectStateCookie, runBuilderAgent, sendBuilderPopupErrorPage, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, signBuilderProvisioningToken, verifyBuilderProvisioningToken, withBuilderConnectTrackingParams, } from "./builder-browser.js";
|
|
62
|
+
import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_MODE_PARAM, BUILDER_AGENT_NATIVE_PROVISION_MODE, BUILDER_CONNECT_ATTEMPT_PARAM, BUILDER_CONNECT_STATE_COOKIE, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, appendBuilderConnectStateCookie, builderConnectTrackingProperties, BUILDER_UPSTREAM_FAILURE_STATUS, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectCallbackOriginFromUrl, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderAccountAlreadyExistsError, isBuilderAccountProvisioningEnabled, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, parseBuilderConnectStateCookie, provisionBuilderAccount, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderConnectCallbackState, resolveBuilderPreviewRelayParentOrigin, removeBuilderConnectStateCookie, runBuilderAgent, sendBuilderPopupErrorPage, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, signBuilderProvisioningToken, verifyBuilderProvisioningToken, withBuilderConnectTrackingParams, } from "./builder-browser.js";
|
|
63
63
|
import { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, builderOAuthScopeFor, canRoleConnectPersonalBuilder, deleteBuilderOAuthSession, exchangeBuilderOAuthAuthorization, getBuilderOAuthGrants, getBuilderOAuthStoredScope, hasStoredBuilderOAuthGrant, isBuilderOrgManagerRole, isPersonalBuilderGrantAllowed, saveBuilderOAuthCredentials, startBuilderOAuthAuthorization, } from "./builder-oauth.js";
|
|
64
64
|
import { captureError, registerErrorCaptureProvider } from "./capture-error.js";
|
|
65
65
|
import { createChatGPTSubscriptionOAuthCallbackHandler, createChatGPTSubscriptionOAuthStartHandler, } from "./chatgpt-subscription-oauth.js";
|
|
@@ -3472,7 +3472,8 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3472
3472
|
? await selectLiveBuilderConnectStates(cookieStates)
|
|
3473
3473
|
: cookieStates;
|
|
3474
3474
|
const { state, resetStateCookie } = resolveBuilderConnectCallbackState(queryState, liveStates ? liveStates.join(",") : rawStateCookie);
|
|
3475
|
-
|
|
3475
|
+
let parentOrigin = getBuilderBrowserOriginForEvent(event);
|
|
3476
|
+
let callbackOriginOverride;
|
|
3476
3477
|
let callbackAttemptId = requestConnectAttemptId;
|
|
3477
3478
|
// A finished attempt — succeeded or failed — must not leave its
|
|
3478
3479
|
// state in the cookie, or the next restart resolves against two
|
|
@@ -3492,8 +3493,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3492
3493
|
}
|
|
3493
3494
|
setCookie(event, BUILDER_CONNECT_STATE_COOKIE, remaining, {
|
|
3494
3495
|
httpOnly: true,
|
|
3495
|
-
secure: (resolveBuilderConnectCallbackUrl(event, finishedState) ??
|
|
3496
|
-
parentOrigin).startsWith("https://"),
|
|
3496
|
+
secure: (resolveBuilderConnectCallbackUrl(event, finishedState, callbackOriginOverride) ?? parentOrigin).startsWith("https://"),
|
|
3497
3497
|
sameSite: "lax",
|
|
3498
3498
|
path: "/",
|
|
3499
3499
|
maxAge: Math.ceil(BUILDER_CONNECT_PENDING_TTL_MS / 1_000),
|
|
@@ -3553,7 +3553,14 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3553
3553
|
const redirectUri = typeof pending.redirectUri === "string"
|
|
3554
3554
|
? pending.redirectUri
|
|
3555
3555
|
: null;
|
|
3556
|
-
const
|
|
3556
|
+
const storedCallbackOrigin = redirectUri
|
|
3557
|
+
? getBuilderConnectCallbackOriginFromUrl(redirectUri)
|
|
3558
|
+
: null;
|
|
3559
|
+
if (storedCallbackOrigin) {
|
|
3560
|
+
callbackOriginOverride = storedCallbackOrigin;
|
|
3561
|
+
parentOrigin = storedCallbackOrigin;
|
|
3562
|
+
}
|
|
3563
|
+
const expectedRedirectUri = resolveBuilderConnectCallbackUrl(event, state, callbackOriginOverride);
|
|
3557
3564
|
if (!ownerEmail ||
|
|
3558
3565
|
!isBuilderConnectCallbackOwner(ownerEmail, session?.email) ||
|
|
3559
3566
|
Date.now() >= expiresAt ||
|
|
@@ -3561,7 +3568,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3561
3568
|
!redirectUri ||
|
|
3562
3569
|
!expectedRedirectUri ||
|
|
3563
3570
|
redirectUri !== expectedRedirectUri ||
|
|
3564
|
-
!isBuilderConnectCallbackUrlAllowed(redirectUri, event)) {
|
|
3571
|
+
!isBuilderConnectCallbackUrlAllowed(redirectUri, event, callbackOriginOverride)) {
|
|
3565
3572
|
if (Date.now() >= expiresAt) {
|
|
3566
3573
|
await deleteSetting(`builder-connect-pending:${state}`).catch(() => false); // coercion-ok: expired pending cleanup is best-effort; callback already failed verification
|
|
3567
3574
|
}
|