@agent-native/core 0.212.1 → 0.213.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/anthropic-managed-agents.js +1 -0
- package/dist/a2a/caller-auth.js +7 -2
- package/dist/a2a/client.d.ts +1 -0
- package/dist/a2a/client.js +27 -0
- package/dist/a2a/handlers.js +15 -3
- package/dist/a2a/organization-identity.d.ts +4 -1
- package/dist/a2a/organization-identity.js +3 -1
- package/dist/a2a/server.d.ts +6 -5
- package/dist/a2a/server.js +79 -6
- package/dist/a2a-claims.d.ts +1 -0
- package/dist/a2a-claims.js +1 -0
- package/dist/agent/actions/manage-provider-key-policy.js +4 -2
- package/dist/agent/app-model-defaults.js +2 -10
- package/dist/agent/loop-settings.js +2 -10
- package/dist/audit/actions/export-audit-events.js +1 -0
- package/dist/audit/attribution.d.ts +5 -4
- package/dist/audit/attribution.js +2 -1
- package/dist/audit/index.d.ts +1 -1
- package/dist/audit/ocsf.js +1 -0
- package/dist/audit/read-scope.js +2 -12
- package/dist/audit/record.js +1 -0
- package/dist/audit/store.js +12 -3
- package/dist/audit/types.d.ts +4 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/automations/service.js +3 -10
- package/dist/client/org/hooks.d.ts +3 -0
- package/dist/client/org/hooks.js +12 -0
- package/dist/connections/catalog.d.ts +1 -1
- package/dist/connections/catalog.js +7 -1
- package/dist/credentials/index.js +18 -2
- package/dist/db/schema-snapshot.generated.js +5 -2
- package/dist/feature-flags/a2a-action-route.js +1 -0
- package/dist/file-upload/storage-settings.js +2 -9
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/jobs/tools.js +2 -12
- package/dist/mcp/actions/service-token-access.js +15 -11
- package/dist/mcp/credential-issuance.js +9 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/org/actions.d.ts +1 -1
- package/dist/org/actions.js +3 -7
- package/dist/org/app-roles.js +10 -1
- package/dist/org/context.d.ts +1 -0
- package/dist/org/context.js +122 -24
- package/dist/org/federation.d.ts +1 -1
- package/dist/org/federation.js +2 -2
- package/dist/org/handlers.d.ts +4 -0
- package/dist/org/handlers.js +140 -44
- package/dist/org/index.d.ts +2 -1
- package/dist/org/membership.d.ts +6 -4
- package/dist/org/membership.js +7 -5
- package/dist/org/migrations.js +6 -0
- package/dist/org/plugin.d.ts +1 -0
- package/dist/org/plugin.js +9 -1
- package/dist/org/private-icon-handlers.js +4 -10
- package/dist/org/types.d.ts +2 -0
- package/dist/org/workspace-app-access.js +62 -14
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +3 -3
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.js +13 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/org-admin.js +3 -2
- package/dist/server/personal-provider-key-holders.d.ts +1 -1
- package/dist/server/personal-provider-key-holders.js +7 -2
- package/dist/server/personal-provider-key-policy.d.ts +2 -0
- package/dist/server/personal-provider-key-policy.js +21 -5
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +7 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
- package/dist/triggers/actions/manage-automation.d.ts +5 -5
- package/dist/triggers/routes.js +2 -9
- package/dist/usage/alerts-store.js +2 -9
- package/dist/usage/metrics-store.js +2 -9
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
- package/dist/workspace-connections/groups.js +2 -11
- package/package.json +3 -3
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +16 -13
package/dist/org/context.js
CHANGED
|
@@ -7,6 +7,7 @@ import { getDbExec, isTransientDatabaseError } from "../db/client.js";
|
|
|
7
7
|
import { isAnonymousWaitlistSessionEmail } from "../server/anonymous-identity.js";
|
|
8
8
|
import { crossSiteCookieAttrs, getSession } from "../server/auth.js";
|
|
9
9
|
import { shouldWriteFirstRunOnboardingEligibility } from "../server/first-run-onboarding-build-mode.js";
|
|
10
|
+
import { effectiveOrgMemberRole, readOrgMemberRole, } from "../server/personal-provider-key-policy.js";
|
|
10
11
|
import { getVerifiedServiceIdentityFromEvent, getRequestContext, hasExplicitPersonalOrgScope, } from "../server/request-context.js";
|
|
11
12
|
import { getSetting } from "../settings/store.js";
|
|
12
13
|
import { getUserSetting } from "../settings/user-settings.js";
|
|
@@ -46,6 +47,32 @@ function normalizeOrgRole(value) {
|
|
|
46
47
|
? value
|
|
47
48
|
: null;
|
|
48
49
|
}
|
|
50
|
+
function delegatedOrgMemberScope(email) {
|
|
51
|
+
const requestContext = getRequestContext();
|
|
52
|
+
if (requestContext?.a2aIdentityAssurance !== "org-member")
|
|
53
|
+
return undefined;
|
|
54
|
+
const requestEmail = requestContext.userEmail?.trim().toLowerCase();
|
|
55
|
+
const requestOrgId = requestContext.orgId?.trim();
|
|
56
|
+
if (!requestEmail ||
|
|
57
|
+
!requestOrgId ||
|
|
58
|
+
(email && email.trim().toLowerCase() !== requestEmail)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return requestOrgId;
|
|
62
|
+
}
|
|
63
|
+
function scopeMembershipsForDelegatedIdentity(email, memberships) {
|
|
64
|
+
const scopedOrgId = delegatedOrgMemberScope(email);
|
|
65
|
+
if (scopedOrgId === undefined || memberships === null)
|
|
66
|
+
return memberships;
|
|
67
|
+
if (!scopedOrgId)
|
|
68
|
+
return [];
|
|
69
|
+
return memberships.flatMap((membership) => {
|
|
70
|
+
if (membership.orgId !== scopedOrgId)
|
|
71
|
+
return [];
|
|
72
|
+
const role = effectiveOrgMemberRole(membership.role, scopedOrgId, email);
|
|
73
|
+
return role ? [{ ...membership, role }] : [];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
49
76
|
function emailDomainOf(email) {
|
|
50
77
|
return email.split("@")[1]?.toLowerCase() || null;
|
|
51
78
|
}
|
|
@@ -94,10 +121,13 @@ async function refreshFederatedMemberships(event, email, memberships, selectedOr
|
|
|
94
121
|
if (!result.active) {
|
|
95
122
|
return memberships.filter((item) => item.orgId !== selected.orgId);
|
|
96
123
|
}
|
|
97
|
-
|
|
124
|
+
const role = effectiveOrgMemberRole(result.role, selected.orgId, email);
|
|
125
|
+
if (!role)
|
|
126
|
+
return memberships.filter((item) => item.orgId !== selected.orgId);
|
|
127
|
+
if (role === selected.role) {
|
|
98
128
|
return memberships;
|
|
99
129
|
}
|
|
100
|
-
return memberships.map((item) => item.orgId === selected.orgId ? { ...item, role
|
|
130
|
+
return memberships.map((item) => item.orgId === selected.orgId ? { ...item, role } : item);
|
|
101
131
|
}
|
|
102
132
|
const MEMBERSHIPS_CACHE_KEY = "__anOrgMembershipsCache";
|
|
103
133
|
const ACTIVE_ORG_SETTING_CACHE_KEY = "__anActiveOrgSettingCache";
|
|
@@ -143,12 +173,19 @@ export function markActiveOrgSelectionChanged(event) {
|
|
|
143
173
|
});
|
|
144
174
|
}
|
|
145
175
|
function loadMembershipsForEvent(event, email) {
|
|
176
|
+
const scopedOrgId = delegatedOrgMemberScope(email);
|
|
177
|
+
if (scopedOrgId === null)
|
|
178
|
+
return Promise.resolve([]);
|
|
146
179
|
const ctx = event.context;
|
|
147
180
|
const cache = (ctx[MEMBERSHIPS_CACHE_KEY] ??
|
|
148
181
|
(ctx[MEMBERSHIPS_CACHE_KEY] = new Map()));
|
|
149
182
|
let promise = cache.get(email);
|
|
150
183
|
if (!promise) {
|
|
151
|
-
promise = loadMemberships(email)
|
|
184
|
+
promise = loadMemberships(email)
|
|
185
|
+
.then((memberships) => scopedOrgId === undefined
|
|
186
|
+
? memberships
|
|
187
|
+
: scopeMembershipsForDelegatedIdentity(email, memberships))
|
|
188
|
+
.catch((err) => {
|
|
152
189
|
cache.delete(email);
|
|
153
190
|
throw err;
|
|
154
191
|
});
|
|
@@ -166,17 +203,25 @@ async function resolveOrgContextUncached(event) {
|
|
|
166
203
|
const email = session?.email;
|
|
167
204
|
if (!email)
|
|
168
205
|
return EMPTY_CONTEXT;
|
|
206
|
+
const requestContext = getRequestContext();
|
|
207
|
+
const isDelegatedOrgMember = requestContext?.a2aIdentityAssurance === "org-member";
|
|
208
|
+
const delegatedOrgId = delegatedOrgMemberScope(email);
|
|
209
|
+
if (isDelegatedOrgMember && !delegatedOrgId) {
|
|
210
|
+
return { email, orgId: null, orgName: null, role: null };
|
|
211
|
+
}
|
|
169
212
|
if (isAnonymousWaitlistSessionEmail(email)) {
|
|
170
213
|
return { email, orgId: null, orgName: null, role: null };
|
|
171
214
|
}
|
|
172
|
-
if (hasExplicitPersonalOrgScope(event)) {
|
|
215
|
+
if (!isDelegatedOrgMember && hasExplicitPersonalOrgScope(event)) {
|
|
173
216
|
return { email, orgId: null, orgName: null, role: null };
|
|
174
217
|
}
|
|
175
|
-
const sessionOrgId =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
218
|
+
const sessionOrgId = delegatedOrgId ??
|
|
219
|
+
(typeof session.orgId === "string" && session.orgId.trim()
|
|
220
|
+
? session.orgId.trim()
|
|
221
|
+
: null);
|
|
222
|
+
const sessionOrgRole = isDelegatedOrgMember
|
|
223
|
+
? "member"
|
|
224
|
+
: normalizeOrgRole(session.orgRole);
|
|
180
225
|
const verifiedServiceIdentity = getVerifiedServiceIdentityFromEvent(event);
|
|
181
226
|
const serviceRole = implicitServiceOrgRole({
|
|
182
227
|
email,
|
|
@@ -209,7 +254,9 @@ async function resolveOrgContextUncached(event) {
|
|
|
209
254
|
}
|
|
210
255
|
}
|
|
211
256
|
const exec = getDbExec();
|
|
212
|
-
const activeOrgSettingPromise =
|
|
257
|
+
const activeOrgSettingPromise = isDelegatedOrgMember
|
|
258
|
+
? Promise.resolve(null)
|
|
259
|
+
: loadActiveOrgSettingForEvent(event, email);
|
|
213
260
|
activeOrgSettingPromise.catch(() => { });
|
|
214
261
|
let memberships;
|
|
215
262
|
try {
|
|
@@ -219,6 +266,10 @@ async function resolveOrgContextUncached(event) {
|
|
|
219
266
|
// A transient membership read must not downgrade an authenticated request
|
|
220
267
|
// to a private/solo scope when the session already carries its org.
|
|
221
268
|
if (sessionOrgId && isTransientDatabaseError(err)) {
|
|
269
|
+
if (isDelegatedOrgMember) {
|
|
270
|
+
markOrgScopeResolutionUnavailable(event);
|
|
271
|
+
return { email, orgId: null, orgName: null, role: null };
|
|
272
|
+
}
|
|
222
273
|
return {
|
|
223
274
|
email,
|
|
224
275
|
orgId: sessionOrgId,
|
|
@@ -231,6 +282,9 @@ async function resolveOrgContextUncached(event) {
|
|
|
231
282
|
if (memberships === null) {
|
|
232
283
|
// A null membership read is unavailable scope, not proof of a solo user.
|
|
233
284
|
markOrgScopeResolutionUnavailable(event);
|
|
285
|
+
if (isDelegatedOrgMember) {
|
|
286
|
+
return { email, orgId: null, orgName: null, role: null };
|
|
287
|
+
}
|
|
234
288
|
if (sessionOrgId) {
|
|
235
289
|
return {
|
|
236
290
|
email,
|
|
@@ -242,16 +296,18 @@ async function resolveOrgContextUncached(event) {
|
|
|
242
296
|
return { email, orgId: null, orgName: null, role: null };
|
|
243
297
|
}
|
|
244
298
|
const activeOrgSetting = await activeOrgSettingPromise;
|
|
245
|
-
const explicitPersonal = activeOrgSetting?.orgId === null;
|
|
246
|
-
const selectedMembership =
|
|
247
|
-
?
|
|
248
|
-
:
|
|
249
|
-
?
|
|
250
|
-
:
|
|
251
|
-
|
|
252
|
-
? memberships.find((membership) => membership.orgId === sessionOrgId)
|
|
299
|
+
const explicitPersonal = !isDelegatedOrgMember && activeOrgSetting?.orgId === null;
|
|
300
|
+
const selectedMembership = isDelegatedOrgMember
|
|
301
|
+
? memberships.find((membership) => membership.orgId === delegatedOrgId)
|
|
302
|
+
: explicitPersonal
|
|
303
|
+
? undefined
|
|
304
|
+
: ((activeOrgSetting?.orgId
|
|
305
|
+
? memberships.find((membership) => membership.orgId === activeOrgSetting.orgId)
|
|
253
306
|
: undefined) ??
|
|
254
|
-
|
|
307
|
+
(sessionOrgId
|
|
308
|
+
? memberships.find((membership) => membership.orgId === sessionOrgId)
|
|
309
|
+
: undefined) ??
|
|
310
|
+
memberships[0]);
|
|
255
311
|
const selectedOrgId = selectedMembership?.orgId ?? null;
|
|
256
312
|
const selectedWasFederated = Boolean(selectedMembership?.identityAuthority || selectedMembership?.identityId);
|
|
257
313
|
const refreshedMemberships = await refreshFederatedMemberships(event, email, memberships, selectedOrgId);
|
|
@@ -268,7 +324,8 @@ async function resolveOrgContextUncached(event) {
|
|
|
268
324
|
markFederationMembershipValidated(event, email, selectedOrgId);
|
|
269
325
|
}
|
|
270
326
|
const emailDomain = emailDomainOf(email);
|
|
271
|
-
const shouldTryDomainAutoJoin = !
|
|
327
|
+
const shouldTryDomainAutoJoin = !isDelegatedOrgMember &&
|
|
328
|
+
!explicitPersonal &&
|
|
272
329
|
session.emailVerified === true &&
|
|
273
330
|
emailDomain !== null &&
|
|
274
331
|
!isFreeEmailProvider(emailDomain) &&
|
|
@@ -358,7 +415,9 @@ async function resolveOrgContextUncached(event) {
|
|
|
358
415
|
}
|
|
359
416
|
return { email, orgId: null, orgName: null, role: null };
|
|
360
417
|
}
|
|
361
|
-
if (
|
|
418
|
+
if (!isDelegatedOrgMember &&
|
|
419
|
+
memberships.length === 0 &&
|
|
420
|
+
autoCreateDefaultOrgEnabled()) {
|
|
362
421
|
const created = await tryCreateDefaultOrg(exec, email, session, event);
|
|
363
422
|
if (created)
|
|
364
423
|
return created;
|
|
@@ -381,7 +440,8 @@ async function loadMemberships(email) {
|
|
|
381
440
|
return cachedMemberships(email, () => loadMembershipsUncached(email));
|
|
382
441
|
}
|
|
383
442
|
export async function listOrgMemberships(email) {
|
|
384
|
-
|
|
443
|
+
const memberships = await loadMemberships(email);
|
|
444
|
+
return scopeMembershipsForDelegatedIdentity(email, memberships);
|
|
385
445
|
}
|
|
386
446
|
export async function listOrgMembershipsForEvent(event, email, selectedOrgId) {
|
|
387
447
|
const memberships = await loadMembershipsForEvent(event, email);
|
|
@@ -423,6 +483,15 @@ async function loadMembershipsUncached(email) {
|
|
|
423
483
|
}) ?? null);
|
|
424
484
|
}
|
|
425
485
|
export async function resolveOrgScopeForEmail(email) {
|
|
486
|
+
const delegatedOrgId = delegatedOrgMemberScope(email);
|
|
487
|
+
if (delegatedOrgId !== undefined) {
|
|
488
|
+
if (!delegatedOrgId)
|
|
489
|
+
return { status: "unknown" };
|
|
490
|
+
const role = await readOrgMemberRole(delegatedOrgId, email);
|
|
491
|
+
return role
|
|
492
|
+
? { status: "organization", orgId: delegatedOrgId }
|
|
493
|
+
: { status: "unknown" };
|
|
494
|
+
}
|
|
426
495
|
if (getRequestContext()?.orgScope === "personal") {
|
|
427
496
|
return { status: "personal" };
|
|
428
497
|
}
|
|
@@ -457,6 +526,13 @@ export async function resolveActiveOrgIdForStorageCredential(email) {
|
|
|
457
526
|
const normalizedEmail = email.trim().toLowerCase();
|
|
458
527
|
if (!normalizedEmail)
|
|
459
528
|
return null;
|
|
529
|
+
const delegatedOrgId = delegatedOrgMemberScope(normalizedEmail);
|
|
530
|
+
if (delegatedOrgId !== undefined) {
|
|
531
|
+
if (!delegatedOrgId)
|
|
532
|
+
return null;
|
|
533
|
+
const role = await readOrgMemberRole(delegatedOrgId, normalizedEmail);
|
|
534
|
+
return role ? delegatedOrgId : null;
|
|
535
|
+
}
|
|
460
536
|
const { rows } = await getDbExec().execute({
|
|
461
537
|
sql: `SELECT m.org_id AS "orgId"
|
|
462
538
|
FROM org_members m
|
|
@@ -476,6 +552,22 @@ export async function resolveActiveOrgIdForStorageCredential(email) {
|
|
|
476
552
|
return rows.some((row) => row.orgId === activeOrgId) ? activeOrgId : null;
|
|
477
553
|
}
|
|
478
554
|
export async function resolveOrgIdForEmailViaEvent(event, email) {
|
|
555
|
+
const delegatedOrgId = delegatedOrgMemberScope(email);
|
|
556
|
+
if (delegatedOrgId !== undefined) {
|
|
557
|
+
if (!delegatedOrgId)
|
|
558
|
+
return null;
|
|
559
|
+
const memberships = await loadMembershipsForEvent(event, email);
|
|
560
|
+
if (!memberships || !memberships.some((m) => m.orgId === delegatedOrgId)) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
const refreshed = await refreshFederatedMemberships(event, email, memberships, delegatedOrgId);
|
|
564
|
+
if (refreshed !== memberships) {
|
|
565
|
+
updateMembershipsForEvent(event, email, refreshed);
|
|
566
|
+
}
|
|
567
|
+
return refreshed.some((m) => m.orgId === delegatedOrgId)
|
|
568
|
+
? delegatedOrgId
|
|
569
|
+
: null;
|
|
570
|
+
}
|
|
479
571
|
if (getRequestContext()?.orgScope === "personal")
|
|
480
572
|
return null;
|
|
481
573
|
const settingPromise = loadActiveOrgSettingForEvent(event, email);
|
|
@@ -842,7 +934,8 @@ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
|
|
|
842
934
|
if (!normalizedDomain)
|
|
843
935
|
return null;
|
|
844
936
|
const { rows } = await getDbExec().execute({
|
|
845
|
-
sql: `SELECT id, allowed_domain, a2a_secret
|
|
937
|
+
sql: `SELECT id, allowed_domain, a2a_secret,
|
|
938
|
+
a2a_member_delegation_enabled
|
|
846
939
|
FROM organizations WHERE LOWER(allowed_domain) = ? LIMIT 2`,
|
|
847
940
|
args: [normalizedDomain],
|
|
848
941
|
});
|
|
@@ -858,7 +951,12 @@ export async function resolveA2AOrganizationCredentialsByDomain(domain) {
|
|
|
858
951
|
const secret = String(row.a2a_secret ?? "").trim();
|
|
859
952
|
if (!orgId || orgDomain !== normalizedDomain || !secret)
|
|
860
953
|
return null;
|
|
861
|
-
return {
|
|
954
|
+
return {
|
|
955
|
+
orgId,
|
|
956
|
+
orgDomain,
|
|
957
|
+
secret,
|
|
958
|
+
a2aMemberDelegationEnabled: row.a2a_member_delegation_enabled === true,
|
|
959
|
+
};
|
|
862
960
|
}
|
|
863
961
|
export async function isSoleOrgDomain(domain) {
|
|
864
962
|
try {
|
package/dist/org/federation.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ export declare function validateFederatedOrganizationMembership(event: H3Event |
|
|
|
52
52
|
export declare function validateFederatedOrganizationMembershipForCurrentRequest(input: {
|
|
53
53
|
orgId: string;
|
|
54
54
|
email: string;
|
|
55
|
-
}): Promise<FederatedMembershipValidation>;
|
|
55
|
+
}, event?: H3Event): Promise<FederatedMembershipValidation>;
|
|
56
56
|
export declare function syncOrganizationToIdentityHub(event: H3Event, input: FederatedOrganizationSyncInput): Promise<boolean>;
|
|
57
57
|
export declare function provisionFederatedOrganization(identity: FederatedOrganizationIdentity, options?: {
|
|
58
58
|
/** The signing-in caller's request; see `setActiveOrgId`. */
|
package/dist/org/federation.js
CHANGED
|
@@ -408,8 +408,8 @@ export async function validateFederatedOrganizationMembership(event, input) {
|
|
|
408
408
|
}
|
|
409
409
|
return { active: true, role: body.memberRole };
|
|
410
410
|
}
|
|
411
|
-
export async function validateFederatedOrganizationMembershipForCurrentRequest(input) {
|
|
412
|
-
return validateFederatedOrganizationMembership(requestEventFromContext(), input);
|
|
411
|
+
export async function validateFederatedOrganizationMembershipForCurrentRequest(input, event) {
|
|
412
|
+
return validateFederatedOrganizationMembership(event ?? requestEventFromContext(), input);
|
|
413
413
|
}
|
|
414
414
|
export async function syncOrganizationToIdentityHub(event, input) {
|
|
415
415
|
const rollout = await federationRolloutState(input.email, input.id);
|
package/dist/org/handlers.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
|
|
|
62
62
|
workspaceAppDefaultVisibility: WorkspaceAppDefaultVisibility;
|
|
63
63
|
signInMethods: import("./sign-in-methods.js").OrgSignInMethods;
|
|
64
64
|
a2aSecretSet: boolean;
|
|
65
|
+
a2aMemberDelegationEnabled: boolean;
|
|
65
66
|
soloDeploymentAdmin: boolean;
|
|
66
67
|
}>>;
|
|
67
68
|
export declare const retryPendingFederatedRemovalHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
@@ -175,6 +176,9 @@ export declare const setRequiredAuthProviderHandler: import("h3").EventHandlerWi
|
|
|
175
176
|
revokedLegacySessions: number;
|
|
176
177
|
provider: RequiredAuthProvider;
|
|
177
178
|
}>>;
|
|
179
|
+
export declare const setA2AMemberDelegationHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
180
|
+
enabled: any;
|
|
181
|
+
}>>;
|
|
178
182
|
export declare const revealA2ASecretHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
179
183
|
a2aSecret: string;
|
|
180
184
|
}>>;
|
package/dist/org/handlers.js
CHANGED
|
@@ -35,6 +35,8 @@ import { getAppProductionUrl, resolveAppRuntimeUrl, } from "../server/app-url.js
|
|
|
35
35
|
import { isTrustedSelfHostedRuntime, resolveVercelDeploymentProtectionHeaders, } from "../server/credential-provider.js";
|
|
36
36
|
import { sendEmail, isEmailConfigured } from "../server/email.js";
|
|
37
37
|
import { readBody } from "../server/h3-helpers.js";
|
|
38
|
+
import { effectiveOrgMemberRole } from "../server/personal-provider-key-policy.js";
|
|
39
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
38
40
|
import { resolveDeploymentSignInMethods } from "../server/social-sign-in-providers.js";
|
|
39
41
|
import { getOrgSetting, putOrgSetting } from "../settings/org-settings.js";
|
|
40
42
|
import { isEmailDerivedName } from "../user-profile/shared.js";
|
|
@@ -140,37 +142,82 @@ async function getSessionForEvent(event) {
|
|
|
140
142
|
}
|
|
141
143
|
export const getMyOrgHandler = defineEventHandler(async (event) => {
|
|
142
144
|
const ctx = await getOrgContext(event);
|
|
145
|
+
const requestContext = getRequestContext();
|
|
146
|
+
const delegatedOrgMember = requestContext?.a2aIdentityAssurance === "org-member";
|
|
147
|
+
const delegatedEmail = requestContext?.userEmail?.trim().toLowerCase();
|
|
148
|
+
const delegatedOrgId = delegatedOrgMember
|
|
149
|
+
? requestContext.orgId?.trim()
|
|
150
|
+
: undefined;
|
|
151
|
+
const scopedDelegatedOrgId = delegatedEmail &&
|
|
152
|
+
delegatedEmail === ctx.email.trim().toLowerCase() &&
|
|
153
|
+
delegatedOrgId &&
|
|
154
|
+
ctx.orgId === delegatedOrgId
|
|
155
|
+
? delegatedOrgId
|
|
156
|
+
: null;
|
|
157
|
+
const visibleOrgId = delegatedOrgMember ? scopedDelegatedOrgId : ctx.orgId;
|
|
158
|
+
const visibleOrgName = visibleOrgId ? ctx.orgName : null;
|
|
159
|
+
const visibleRole = delegatedOrgMember
|
|
160
|
+
? visibleOrgId
|
|
161
|
+
? "member"
|
|
162
|
+
: null
|
|
163
|
+
: ctx.role;
|
|
143
164
|
const e = await exec();
|
|
144
|
-
const allOrgsRes =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const allOrgsRes = delegatedOrgMember
|
|
166
|
+
? scopedDelegatedOrgId
|
|
167
|
+
? await e.execute({
|
|
168
|
+
sql: `SELECT m.org_id AS "orgId", m.role AS role, o.name AS "orgName",
|
|
169
|
+
o.icon_json AS "iconJson", o.icon_revision AS "iconRevision"
|
|
170
|
+
FROM org_members m
|
|
171
|
+
INNER JOIN organizations o ON m.org_id = o.id
|
|
172
|
+
WHERE LOWER(m.email) = ? AND m.org_id = ?
|
|
173
|
+
AND m.federation_removal_pending_at IS NULL`,
|
|
174
|
+
args: [ctx.email.toLowerCase(), scopedDelegatedOrgId],
|
|
175
|
+
})
|
|
176
|
+
: { rows: [] }
|
|
177
|
+
: await e.execute({
|
|
178
|
+
sql: `SELECT m.org_id AS "orgId", m.role AS role, o.name AS "orgName",
|
|
179
|
+
o.icon_json AS "iconJson", o.icon_revision AS "iconRevision"
|
|
180
|
+
FROM org_members m
|
|
181
|
+
INNER JOIN organizations o ON m.org_id = o.id
|
|
182
|
+
WHERE LOWER(m.email) = ?
|
|
183
|
+
AND m.federation_removal_pending_at IS NULL`,
|
|
184
|
+
args: [ctx.email.toLowerCase()],
|
|
185
|
+
});
|
|
186
|
+
const orgs = allOrgsRes.rows.flatMap((r) => {
|
|
187
|
+
const orgId = String(r.orgId ?? r.org_id);
|
|
188
|
+
const role = delegatedOrgMember
|
|
189
|
+
? effectiveOrgMemberRole(r.role, orgId, ctx.email)
|
|
190
|
+
: String(r.role);
|
|
191
|
+
return role
|
|
192
|
+
? [
|
|
193
|
+
{
|
|
194
|
+
orgId,
|
|
195
|
+
role,
|
|
196
|
+
orgName: String(r.orgName ?? r.org_name),
|
|
197
|
+
icon: parseOrganizationIconJson(r.iconJson ?? r.icon_json),
|
|
198
|
+
iconRevision: Number(r.iconRevision ?? r.icon_revision ?? 0),
|
|
199
|
+
},
|
|
200
|
+
]
|
|
201
|
+
: [];
|
|
167
202
|
});
|
|
203
|
+
const pendingRemovalRes = delegatedOrgMember
|
|
204
|
+
? { rows: [] }
|
|
205
|
+
: await e.execute({
|
|
206
|
+
sql: `SELECT m.org_id AS "orgId", o.name AS "orgName"
|
|
207
|
+
FROM org_members m
|
|
208
|
+
INNER JOIN organizations o ON m.org_id = o.id
|
|
209
|
+
WHERE LOWER(m.email) = ?
|
|
210
|
+
AND m.federation_removal_pending_at IS NOT NULL`,
|
|
211
|
+
args: [ctx.email.toLowerCase()],
|
|
212
|
+
});
|
|
168
213
|
const pendingRemovals = pendingRemovalRes.rows.map((r) => ({
|
|
169
214
|
orgId: String(r.orgId ?? r.org_id),
|
|
170
215
|
orgName: String(r.orgName ?? r.org_name),
|
|
171
216
|
}));
|
|
172
217
|
let domainMatches = [];
|
|
173
|
-
const domain =
|
|
218
|
+
const domain = delegatedOrgMember
|
|
219
|
+
? undefined
|
|
220
|
+
: ctx.email.split("@")[1]?.toLowerCase();
|
|
174
221
|
if (domain) {
|
|
175
222
|
try {
|
|
176
223
|
const dmRes = await e.execute({
|
|
@@ -199,14 +246,16 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
|
|
|
199
246
|
let workspaceUrl = null;
|
|
200
247
|
let requiredAuthProvider = null;
|
|
201
248
|
let a2aSecretSet = false;
|
|
249
|
+
let a2aMemberDelegationEnabled = false;
|
|
202
250
|
let icon = null;
|
|
203
251
|
let iconRevision = 0;
|
|
204
|
-
if (
|
|
252
|
+
if (visibleOrgId) {
|
|
205
253
|
const adRes = await e.execute({
|
|
206
|
-
sql: `SELECT allowed_domain, a2a_secret,
|
|
254
|
+
sql: `SELECT allowed_domain, a2a_secret, a2a_member_delegation_enabled,
|
|
255
|
+
workspace_url, required_auth_provider,
|
|
207
256
|
icon_json, icon_revision
|
|
208
257
|
FROM organizations WHERE id = ? LIMIT 1`,
|
|
209
|
-
args: [
|
|
258
|
+
args: [visibleOrgId],
|
|
210
259
|
});
|
|
211
260
|
if (adRes.rows[0]) {
|
|
212
261
|
allowedDomain =
|
|
@@ -214,35 +263,49 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
|
|
|
214
263
|
workspaceUrl = String(adRes.rows[0].workspace_url ?? "") || null;
|
|
215
264
|
requiredAuthProvider = parseRequiredAuthProvider(adRes.rows[0].required_auth_provider ?? null);
|
|
216
265
|
a2aSecretSet = Boolean(String(adRes.rows[0].a2a_secret ?? "").trim());
|
|
266
|
+
a2aMemberDelegationEnabled =
|
|
267
|
+
adRes.rows[0].a2a_member_delegation_enabled === true;
|
|
217
268
|
icon = parseOrganizationIconJson(adRes.rows[0].icon_json);
|
|
218
269
|
iconRevision = Number(adRes.rows[0].icon_revision ?? 0);
|
|
219
270
|
}
|
|
220
271
|
}
|
|
221
|
-
const isOwnerOrAdmin =
|
|
272
|
+
const isOwnerOrAdmin = visibleRole === "owner" || visibleRole === "admin";
|
|
222
273
|
let workspaceAppDefaultVisibility = "org";
|
|
223
|
-
if (
|
|
224
|
-
const setting = await getOrgSetting(
|
|
274
|
+
if (visibleOrgId) {
|
|
275
|
+
const setting = await getOrgSetting(visibleOrgId, WORKSPACE_APP_DEFAULT_VISIBILITY_KEY);
|
|
225
276
|
workspaceAppDefaultVisibility = normalizeWorkspaceAppDefaultVisibility(setting?.visibility);
|
|
226
277
|
}
|
|
227
|
-
const invitesRes =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
278
|
+
const invitesRes = delegatedOrgMember
|
|
279
|
+
? scopedDelegatedOrgId
|
|
280
|
+
? await e.execute({
|
|
281
|
+
sql: `SELECT i.id AS id, i.org_id AS "orgId", o.name AS "orgName", i.invited_by AS "invitedBy"
|
|
282
|
+
FROM org_invitations i
|
|
283
|
+
INNER JOIN organizations o ON i.org_id = o.id
|
|
284
|
+
WHERE LOWER(i.email) = ? AND i.org_id = ? AND i.status = 'pending'`,
|
|
285
|
+
args: [ctx.email.toLowerCase(), scopedDelegatedOrgId],
|
|
286
|
+
})
|
|
287
|
+
: { rows: [] }
|
|
288
|
+
: await e.execute({
|
|
289
|
+
sql: `SELECT i.id AS id, i.org_id AS "orgId", o.name AS "orgName", i.invited_by AS "invitedBy"
|
|
290
|
+
FROM org_invitations i
|
|
291
|
+
INNER JOIN organizations o ON i.org_id = o.id
|
|
292
|
+
WHERE LOWER(i.email) = ? AND i.status = 'pending'`,
|
|
293
|
+
args: [ctx.email.toLowerCase()],
|
|
294
|
+
});
|
|
234
295
|
const pendingInvitations = invitesRes.rows.map((r) => ({
|
|
235
296
|
id: String(r.id),
|
|
236
297
|
orgId: String(r.orgId ?? r.org_id),
|
|
237
298
|
orgName: String(r.orgName ?? r.org_name),
|
|
238
299
|
invitedBy: String(r.invitedBy ?? r.invited_by),
|
|
239
300
|
}));
|
|
240
|
-
|
|
301
|
+
if (!delegatedOrgMember) {
|
|
302
|
+
scheduleFederatedOrgSync(event, { ...ctx, icon, iconRevision });
|
|
303
|
+
}
|
|
241
304
|
return {
|
|
242
|
-
email: ctx.email,
|
|
243
|
-
orgId:
|
|
244
|
-
orgName:
|
|
245
|
-
role:
|
|
305
|
+
email: delegatedOrgMember ? (delegatedEmail ?? "") : ctx.email,
|
|
306
|
+
orgId: visibleOrgId,
|
|
307
|
+
orgName: visibleOrgName,
|
|
308
|
+
role: visibleRole,
|
|
246
309
|
icon,
|
|
247
310
|
iconRevision,
|
|
248
311
|
emailConfigured: await isEmailConfigured(),
|
|
@@ -268,8 +331,13 @@ export const getMyOrgHandler = defineEventHandler(async (event) => {
|
|
|
268
331
|
// so the value would sit in JSON any script on the page can read, and it
|
|
269
332
|
// signs the JWTs peers accept as first-party callers. Reveal is an explicit
|
|
270
333
|
// owner GET on /_agent-native/org/a2a-secret.
|
|
271
|
-
a2aSecretSet: canManageOrgA2ASecret(
|
|
272
|
-
|
|
334
|
+
a2aSecretSet: canManageOrgA2ASecret(visibleRole) ? a2aSecretSet : undefined,
|
|
335
|
+
a2aMemberDelegationEnabled: visibleRole === "owner" ? a2aMemberDelegationEnabled : undefined,
|
|
336
|
+
soloDeploymentAdmin: delegatedOrgMember
|
|
337
|
+
? undefined
|
|
338
|
+
: visibleOrgId
|
|
339
|
+
? undefined
|
|
340
|
+
: isTrustedSelfHostedRuntime(),
|
|
273
341
|
};
|
|
274
342
|
});
|
|
275
343
|
export const retryPendingFederatedRemovalHandler = defineEventHandler(async (event) => {
|
|
@@ -1558,6 +1626,33 @@ export const setRequiredAuthProviderHandler = defineEventHandler(async (event) =
|
|
|
1558
1626
|
const result = await setRequiredAuthProvider(ctx.orgId, provider);
|
|
1559
1627
|
return { provider, ...result };
|
|
1560
1628
|
});
|
|
1629
|
+
export const setA2AMemberDelegationHandler = defineEventHandler(async (event) => {
|
|
1630
|
+
const ctx = await getOrgContext(event);
|
|
1631
|
+
if (!ctx.orgId) {
|
|
1632
|
+
throw createError({ statusCode: 400, message: "No active organization" });
|
|
1633
|
+
}
|
|
1634
|
+
if (ctx.role !== "owner") {
|
|
1635
|
+
throw createError({
|
|
1636
|
+
statusCode: 403,
|
|
1637
|
+
message: "Only the organization owner can manage member delegation",
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
const body = await readBody(event);
|
|
1641
|
+
if (typeof body?.enabled !== "boolean") {
|
|
1642
|
+
throw createError({
|
|
1643
|
+
statusCode: 400,
|
|
1644
|
+
message: "enabled must be a boolean",
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
const e = await exec();
|
|
1648
|
+
await e.execute({
|
|
1649
|
+
sql: `UPDATE organizations
|
|
1650
|
+
SET a2a_member_delegation_enabled = ?
|
|
1651
|
+
WHERE id = ?`,
|
|
1652
|
+
args: [body.enabled, ctx.orgId],
|
|
1653
|
+
});
|
|
1654
|
+
return { enabled: body.enabled };
|
|
1655
|
+
});
|
|
1561
1656
|
export const revealA2ASecretHandler = defineEventHandler(async (event) => {
|
|
1562
1657
|
const ctx = await getOrgContext(event);
|
|
1563
1658
|
if (!ctx.orgId) {
|
|
@@ -1886,6 +1981,7 @@ export const receiveA2ASecretHandler = defineEventHandler(async (event) => {
|
|
|
1886
1981
|
verified = null;
|
|
1887
1982
|
}
|
|
1888
1983
|
if (!verified ||
|
|
1984
|
+
verified.identityAssurance !== "organization" ||
|
|
1889
1985
|
verified.orgId !== localOrgId ||
|
|
1890
1986
|
verified.orgDomain?.trim().toLowerCase() !== orgDomain) {
|
|
1891
1987
|
throw createError({
|
package/dist/org/index.d.ts
CHANGED
|
@@ -159,13 +159,14 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
|
|
|
159
159
|
workspaceAppDefaultVisibility: import("./types.js").WorkspaceAppDefaultVisibility;
|
|
160
160
|
signInMethods: import("./sign-in-methods.js").OrgSignInMethods;
|
|
161
161
|
a2aSecretSet: boolean;
|
|
162
|
+
a2aMemberDelegationEnabled: boolean;
|
|
162
163
|
soloDeploymentAdmin: boolean;
|
|
163
164
|
}>>;
|
|
164
165
|
export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
165
166
|
success: boolean;
|
|
167
|
+
id?: undefined;
|
|
166
168
|
name?: undefined;
|
|
167
169
|
role?: undefined;
|
|
168
|
-
id?: undefined;
|
|
169
170
|
} | {
|
|
170
171
|
success?: undefined;
|
|
171
172
|
id: string;
|
package/dist/org/membership.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
1
2
|
export declare function isMissingOrganizationTableError(error: unknown): boolean;
|
|
2
3
|
export declare function isOrgMember(orgId: string, email: string, options?: {
|
|
3
4
|
requireOrganizationMetadata?: boolean;
|
|
5
|
+
requestEvent?: H3Event;
|
|
4
6
|
}): Promise<boolean>;
|
|
5
7
|
/**
|
|
6
|
-
* Checks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Checks the live roster for an opted-in org-member A2A assertion. This proves
|
|
9
|
+
* membership in the asserted org; authorization still caps the identity at
|
|
10
|
+
* member regardless of the stored owner/admin role.
|
|
9
11
|
*/
|
|
10
|
-
export declare function isOrgMemberForA2A(orgId: string, email: string): Promise<boolean>;
|
|
12
|
+
export declare function isOrgMemberForA2A(orgId: string, email: string, requestEvent?: H3Event): Promise<boolean>;
|
package/dist/org/membership.js
CHANGED
|
@@ -65,23 +65,25 @@ async function isOrgMemberWithPolicy(orgId, email, options) {
|
|
|
65
65
|
const validation = await validateFederatedOrganizationMembershipForCurrentRequest({
|
|
66
66
|
orgId,
|
|
67
67
|
email: normalized,
|
|
68
|
-
});
|
|
68
|
+
}, options.requestEvent);
|
|
69
69
|
return validation.active;
|
|
70
70
|
}
|
|
71
71
|
export function isOrgMember(orgId, email, options = {}) {
|
|
72
72
|
return isOrgMemberWithPolicy(orgId, email, {
|
|
73
73
|
allowMissingOrganizationTable: !options.requireOrganizationMetadata,
|
|
74
74
|
requireOrganizationRecord: !!options.requireOrganizationMetadata,
|
|
75
|
+
requestEvent: options.requestEvent,
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
78
|
-
* Checks
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* Checks the live roster for an opted-in org-member A2A assertion. This proves
|
|
80
|
+
* membership in the asserted org; authorization still caps the identity at
|
|
81
|
+
* member regardless of the stored owner/admin role.
|
|
81
82
|
*/
|
|
82
|
-
export function isOrgMemberForA2A(orgId, email) {
|
|
83
|
+
export function isOrgMemberForA2A(orgId, email, requestEvent) {
|
|
83
84
|
return isOrgMemberWithPolicy(orgId, email, {
|
|
84
85
|
allowMissingOrganizationTable: false,
|
|
85
86
|
requireOrganizationRecord: true,
|
|
87
|
+
requestEvent,
|
|
86
88
|
});
|
|
87
89
|
}
|
package/dist/org/migrations.js
CHANGED
|
@@ -284,4 +284,10 @@ export const ORG_MIGRATIONS = [
|
|
|
284
284
|
ON private_icon_assets (owner_email, org_id, created_at);
|
|
285
285
|
`,
|
|
286
286
|
},
|
|
287
|
+
{
|
|
288
|
+
version: 1035,
|
|
289
|
+
name: "organization-a2a-member-delegation",
|
|
290
|
+
sql: `ALTER TABLE organizations
|
|
291
|
+
ADD COLUMN IF NOT EXISTS a2a_member_delegation_enabled BOOLEAN NOT NULL DEFAULT FALSE`,
|
|
292
|
+
},
|
|
287
293
|
];
|
package/dist/org/plugin.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
|
|
|
24
24
|
* PUT /_agent-native/org/domain — set/clear allowed email domain (owner/admin)
|
|
25
25
|
* PUT /_agent-native/org/workspace-url — set/clear the org's workspace origin (owner/admin)
|
|
26
26
|
* PUT /_agent-native/org/auth-provider — require/clear Google sign-in (owner/admin)
|
|
27
|
+
* PUT /_agent-native/org/a2a-member-delegation — allow org-member A2A assertions (owner only)
|
|
27
28
|
* PUT /_agent-native/org/visual-identity — set/clear workspace icon (owner/admin)
|
|
28
29
|
* GET /_agent-native/org/a2a-secret — reveal A2A secret on demand (owner/admin)
|
|
29
30
|
* PUT /_agent-native/org/a2a-secret — regenerate or set A2A secret (owner/admin)
|