@agent-native/dispatch 0.40.23 → 0.40.24-nightly-20261015093558
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/CHANGELOG.md +19 -0
- package/dist/actions/provider-api-register.d.ts +8 -8
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/app-roles.d.ts.map +1 -1
- package/dist/server/lib/app-roles.js +3 -3
- package/dist/server/lib/app-roles.js.map +1 -1
- package/package.json +4 -4
- package/src/server/index.ts +1 -1
- package/src/server/lib/app-roles.spec.ts +79 -41
- package/src/server/lib/app-roles.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @agent-native/dispatch
|
|
2
2
|
|
|
3
|
+
## 0.40.24-nightly-20261015093558
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2743662: Use the shared app permission policy for organization owner and admin access to Dispatch administration. Dispatch now requires `@agent-native/core` 0.206.0 or later, the first release with that policy.
|
|
8
|
+
- Updated dependencies [eb4cd30]
|
|
9
|
+
- Updated dependencies [ebf1cc7]
|
|
10
|
+
- Updated dependencies [e90b90a]
|
|
11
|
+
- Updated dependencies [5b201d1]
|
|
12
|
+
- Updated dependencies [db3a30e]
|
|
13
|
+
- Updated dependencies [b95626f]
|
|
14
|
+
- Updated dependencies [64bf791]
|
|
15
|
+
- Updated dependencies [e5a64ae]
|
|
16
|
+
- Updated dependencies [ef6a530]
|
|
17
|
+
- Updated dependencies [e6e1f56]
|
|
18
|
+
- Updated dependencies [4e49b3a]
|
|
19
|
+
- @agent-native/core@0.209.1-nightly-20261015093558
|
|
20
|
+
- @agent-native/toolkit@0.209.1-nightly-20261015093558
|
|
21
|
+
|
|
3
22
|
## 0.40.23
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
|
@@ -47,10 +47,10 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
|
|
|
47
47
|
notes?: string | undefined;
|
|
48
48
|
scope?: "org" | "user" | undefined;
|
|
49
49
|
}, {
|
|
50
|
+
message?: undefined;
|
|
50
51
|
id?: undefined;
|
|
51
52
|
provider?: undefined;
|
|
52
53
|
deleted?: undefined;
|
|
53
|
-
message?: undefined;
|
|
54
54
|
providers: {
|
|
55
55
|
id: string;
|
|
56
56
|
label: string;
|
|
@@ -65,30 +65,30 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
|
|
|
65
65
|
registered?: undefined;
|
|
66
66
|
label?: undefined;
|
|
67
67
|
} | {
|
|
68
|
+
message?: undefined;
|
|
68
69
|
id?: undefined;
|
|
69
|
-
count?: undefined;
|
|
70
70
|
deleted?: undefined;
|
|
71
|
-
message?: undefined;
|
|
72
71
|
providers?: undefined;
|
|
72
|
+
count?: undefined;
|
|
73
73
|
found: boolean;
|
|
74
74
|
provider: import("node_modules/@agent-native/core/dist/provider-api").CustomProviderConfig;
|
|
75
75
|
registered?: undefined;
|
|
76
76
|
label?: undefined;
|
|
77
77
|
} | {
|
|
78
|
+
message?: undefined;
|
|
78
79
|
provider?: undefined;
|
|
79
|
-
count?: undefined;
|
|
80
80
|
deleted?: undefined;
|
|
81
|
-
message?: undefined;
|
|
82
81
|
providers?: undefined;
|
|
82
|
+
count?: undefined;
|
|
83
83
|
found: boolean;
|
|
84
84
|
id: string;
|
|
85
85
|
registered?: undefined;
|
|
86
86
|
label?: undefined;
|
|
87
87
|
} | {
|
|
88
|
-
provider?: undefined;
|
|
89
|
-
count?: undefined;
|
|
90
88
|
message?: undefined;
|
|
89
|
+
provider?: undefined;
|
|
91
90
|
providers?: undefined;
|
|
91
|
+
count?: undefined;
|
|
92
92
|
found?: undefined;
|
|
93
93
|
deleted: boolean;
|
|
94
94
|
id: string;
|
|
@@ -96,9 +96,9 @@ declare const _default: import("node_modules/@agent-native/core/dist").ActionDef
|
|
|
96
96
|
label?: undefined;
|
|
97
97
|
} | {
|
|
98
98
|
provider?: undefined;
|
|
99
|
-
count?: undefined;
|
|
100
99
|
deleted?: undefined;
|
|
101
100
|
providers?: undefined;
|
|
101
|
+
count?: undefined;
|
|
102
102
|
found?: undefined;
|
|
103
103
|
registered: boolean;
|
|
104
104
|
id: string;
|
package/dist/server/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { defineAppRoles } from "@agent-native/core/org";
|
|
|
2
2
|
import { registerPackageActions, } from "@agent-native/core/server";
|
|
3
3
|
import { dispatchActions } from "../actions/index.js";
|
|
4
4
|
import { dispatchAccessDescriptor } from "../shared/app-roles.js";
|
|
5
|
-
defineAppRoles(dispatchAccessDescriptor);
|
|
5
|
+
defineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });
|
|
6
6
|
registerPackageActions(dispatchActions);
|
|
7
7
|
let activeConfig = {};
|
|
8
8
|
export function getDispatchConfig() {
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,sBAAsB,GAEvB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,cAAc,CAAC,wBAAwB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,sBAAsB,GAEvB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAElE,cAAc,CAAC,wBAAwB,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;AAEnE,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAExC,IAAI,YAAY,GAAmB,EAAE,CAAC;AAEtC,MAAM,UAAU,iBAAiB;IAC/B,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAM,GAAmB,EAAE;IACvD,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,KAAK,QAAQ,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EACL,OAAO,IAAI,gBAAgB,EAC3B,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["import { defineAppRoles } from \"@agent-native/core/org\";\nimport {\n registerPackageActions,\n type NitroPluginDef,\n} from \"@agent-native/core/server\";\n\nimport { dispatchActions } from \"../actions/index.js\";\nimport type { DispatchConfig } from \"../config.js\";\nimport { dispatchAccessDescriptor } from \"../shared/app-roles.js\";\n\ndefineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });\n\nregisterPackageActions(dispatchActions);\n\nlet activeConfig: DispatchConfig = {};\n\nexport function getDispatchConfig(): DispatchConfig {\n return activeConfig;\n}\n\nexport function setupDispatch(config: DispatchConfig = {}): NitroPluginDef {\n activeConfig = Object.freeze({ ...config });\n return (nitroApp) => {\n void nitroApp;\n };\n}\n\nexport { default as dispatchAuthPlugin } from \"./plugins/auth.js\";\nexport { default as dispatchIntegrationsPlugin } from \"./plugins/integrations.js\";\nexport { default as dispatchAgentChatPlugin } from \"./plugins/agent-chat.js\";\nexport {\n default as dispatchDbPlugin,\n runDispatchMigrations,\n} from \"./plugins/db.js\";\nexport { default as dispatchCoreRoutesPlugin } from \"./plugins/core-routes.js\";\n\nexport type { DispatchConfig } from \"../config.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-roles.d.ts","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"app-roles.d.ts","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAwBlE,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,OAAO,EACd,GAAG,CAAC,EAAE,gBAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CAoCf"}
|
|
@@ -4,7 +4,9 @@ import { ForbiddenError } from "@agent-native/core/sharing";
|
|
|
4
4
|
import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
|
|
5
5
|
let dispatchAccess;
|
|
6
6
|
function getDispatchAccess() {
|
|
7
|
-
return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor
|
|
7
|
+
return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {
|
|
8
|
+
allowOrgAdmins: true,
|
|
9
|
+
}));
|
|
8
10
|
}
|
|
9
11
|
export async function authorizeDispatchAdmin(_args, ctx) {
|
|
10
12
|
const email = ctx?.userEmail !== undefined ? ctx.userEmail : getRequestUserEmail();
|
|
@@ -31,8 +33,6 @@ export async function authorizeDispatchAdmin(_args, ctx) {
|
|
|
31
33
|
if (!membership.active) {
|
|
32
34
|
throw new ForbiddenError("Dispatch administration requires active organization membership.");
|
|
33
35
|
}
|
|
34
|
-
if (membership.role === "owner" || membership.role === "admin")
|
|
35
|
-
return;
|
|
36
36
|
await getDispatchAccess().assertPermission(["administer"], {
|
|
37
37
|
userEmail: email,
|
|
38
38
|
orgId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-roles.js","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,+BAA+B,EAC/B,2BAA2B,EAE3B,wDAAwD,GACzD,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,IAAI,cAA2D,CAAC;AAEhE,SAAS,iBAAiB;IACxB,OAAO,CAAC,cAAc,KAAK,cAAc,CAAC,wBAAwB,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app-roles.js","sourceRoot":"","sources":["../../../src/server/lib/app-roles.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,+BAA+B,EAC/B,2BAA2B,EAE3B,wDAAwD,GACzD,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,IAAI,cAA2D,CAAC;AAEhE,SAAS,iBAAiB;IACxB,OAAO,CAAC,cAAc,KAAK,cAAc,CAAC,wBAAwB,EAAE;QAClE,cAAc,EAAE,IAAI;KACrB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAc,EACd,GAAsB;IAEtB,MAAM,KAAK,GACT,GAAG,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACvE,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,MAAM,IAAI,cAAc,CACtB,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,OAAO;IAC3B,IAAI,UAAU,CAAC;IACf,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,wDAAwD,CACzE;YACE,KAAK;YACL,KAAK;SACN,CACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,+BAA+B,CAAC,KAAK,CAAC;YACtC,2BAA2B,EAAE,EAC7B,CAAC;YACD,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,cAAc,CACtB,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,MAAM,iBAAiB,EAAE,CAAC,gBAAgB,CAAC,CAAC,YAAY,CAAC,EAAE;QACzD,SAAS,EAAE,KAAK;QAChB,KAAK;KACN,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ActionRunContext } from \"@agent-native/core/action\";\nimport {\n defineAppRoles,\n isMissingOrganizationTableError,\n isStandaloneDispatchRuntime,\n type AppRoles,\n validateFederatedOrganizationMembershipForCurrentRequest,\n} from \"@agent-native/core/org\";\nimport {\n getRequestOrgId,\n getRequestUserEmail,\n} from \"@agent-native/core/server\";\nimport { ForbiddenError } from \"@agent-native/core/sharing\";\n\nimport { dispatchAccessDescriptor } from \"../../shared/app-roles.js\";\n\nlet dispatchAccess: AppRoles<\"admin\", \"administer\"> | undefined;\n\nfunction getDispatchAccess(): AppRoles<\"admin\", \"administer\"> {\n return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {\n allowOrgAdmins: true,\n }));\n}\n\nexport async function authorizeDispatchAdmin(\n _args: unknown,\n ctx?: ActionRunContext,\n): Promise<void> {\n const email =\n ctx?.userEmail !== undefined ? ctx.userEmail : getRequestUserEmail();\n const orgId = ctx?.orgId !== undefined ? ctx.orgId : getRequestOrgId();\n if (!email?.trim()) {\n throw new ForbiddenError(\n \"Dispatch administration requires an authenticated user.\",\n );\n }\n if (!orgId?.trim()) return;\n let membership;\n try {\n membership = await validateFederatedOrganizationMembershipForCurrentRequest(\n {\n orgId,\n email,\n },\n );\n } catch (error) {\n if (\n isMissingOrganizationTableError(error) &&\n isStandaloneDispatchRuntime()\n ) {\n return;\n }\n throw error;\n }\n if (!membership.active) {\n throw new ForbiddenError(\n \"Dispatch administration requires active organization membership.\",\n );\n }\n await getDispatchAccess().assertPermission([\"administer\"], {\n userEmail: email,\n orgId,\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/dispatch",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.24-nightly-20261015093558",
|
|
4
4
|
"description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"tailwind-merge": "^3.5.0",
|
|
90
90
|
"vaul": "^1.1.2",
|
|
91
91
|
"zod": "^4.3.6",
|
|
92
|
-
"@agent-native/toolkit": "0.209.
|
|
92
|
+
"@agent-native/toolkit": "0.209.1-nightly-20261015093558"
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@react-router/dev": "^8.1.0",
|
|
@@ -103,10 +103,10 @@
|
|
|
103
103
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
104
104
|
"vite": "8.1.0",
|
|
105
105
|
"vitest": "^4.1.5",
|
|
106
|
-
"@agent-native/core": "0.209.
|
|
106
|
+
"@agent-native/core": "0.209.1-nightly-20261015093558"
|
|
107
107
|
},
|
|
108
108
|
"peerDependencies": {
|
|
109
|
-
"@agent-native/core": "
|
|
109
|
+
"@agent-native/core": "0.209.1-nightly-20261015093558",
|
|
110
110
|
"react": ">=19.2.7",
|
|
111
111
|
"react-dom": ">=19.2.7",
|
|
112
112
|
"react-router": ">=8"
|
package/src/server/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { dispatchActions } from "../actions/index.js";
|
|
|
8
8
|
import type { DispatchConfig } from "../config.js";
|
|
9
9
|
import { dispatchAccessDescriptor } from "../shared/app-roles.js";
|
|
10
10
|
|
|
11
|
-
defineAppRoles(dispatchAccessDescriptor);
|
|
11
|
+
defineAppRoles(dispatchAccessDescriptor, { allowOrgAdmins: true });
|
|
12
12
|
|
|
13
13
|
registerPackageActions(dispatchActions);
|
|
14
14
|
|
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
const mocks = vi.hoisted(() => ({
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
roles: [] as string[],
|
|
5
|
+
orgRole: "member",
|
|
6
|
+
member: true,
|
|
7
|
+
federated: false,
|
|
8
|
+
overrides: [] as { permission: string; roles_json: string }[],
|
|
9
|
+
execute: vi.fn(),
|
|
6
10
|
getRequestOrgId: vi.fn(),
|
|
7
11
|
getRequestUserEmail: vi.fn(),
|
|
8
12
|
isStandaloneDispatchRuntime: vi.fn(),
|
|
9
13
|
validateFederatedOrganizationMembershipForCurrentRequest: vi.fn(),
|
|
10
14
|
}));
|
|
11
15
|
|
|
12
|
-
vi.mock("
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
vi.mock("../../../../core/src/db/client.js", () => ({
|
|
17
|
+
getDbExec: () => ({ execute: mocks.execute }),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock("../../../../core/src/org/federation.js", () => ({
|
|
20
|
+
validateFederatedOrganizationMembershipForCurrentRequest:
|
|
21
|
+
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
22
|
+
}));
|
|
23
|
+
vi.mock("@agent-native/core/org", async () => ({
|
|
24
|
+
...(await import("../../../../core/src/org/app-roles.js")),
|
|
17
25
|
isMissingOrganizationTableError: (error: unknown) =>
|
|
18
26
|
/(?:organizations|org_members).*does not exist/i.test(String(error)),
|
|
19
27
|
isStandaloneDispatchRuntime: mocks.isStandaloneDispatchRuntime,
|
|
20
28
|
validateFederatedOrganizationMembershipForCurrentRequest:
|
|
21
29
|
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
22
30
|
}));
|
|
23
|
-
|
|
24
31
|
vi.mock("@agent-native/core/server", () => ({
|
|
25
32
|
getRequestOrgId: mocks.getRequestOrgId,
|
|
26
33
|
getRequestUserEmail: mocks.getRequestUserEmail,
|
|
@@ -39,57 +46,87 @@ const context = {
|
|
|
39
46
|
describe("authorizeDispatchAdmin", () => {
|
|
40
47
|
beforeEach(() => {
|
|
41
48
|
vi.clearAllMocks();
|
|
49
|
+
mocks.roles = [];
|
|
50
|
+
mocks.orgRole = "member";
|
|
51
|
+
mocks.member = true;
|
|
52
|
+
mocks.federated = false;
|
|
53
|
+
mocks.overrides = [];
|
|
54
|
+
mocks.execute.mockImplementation(async ({ sql }: { sql: string }) => ({
|
|
55
|
+
rows: sql.includes("app_permission_overrides")
|
|
56
|
+
? mocks.overrides
|
|
57
|
+
: mocks.member
|
|
58
|
+
? [
|
|
59
|
+
{
|
|
60
|
+
roles: mocks.roles,
|
|
61
|
+
orgRole: mocks.orgRole,
|
|
62
|
+
identityAuthority: mocks.federated
|
|
63
|
+
? "https://hub.example.test"
|
|
64
|
+
: null,
|
|
65
|
+
identityId: mocks.federated ? "org_hub" : null,
|
|
66
|
+
},
|
|
67
|
+
]
|
|
68
|
+
: [],
|
|
69
|
+
}));
|
|
42
70
|
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
|
|
43
71
|
{ active: true, role: "member" },
|
|
44
72
|
);
|
|
45
|
-
mocks.assertAny.mockResolvedValue("admin");
|
|
46
|
-
mocks.assertPermission.mockResolvedValue(undefined);
|
|
47
73
|
mocks.getRequestOrgId.mockReturnValue(undefined);
|
|
48
74
|
mocks.getRequestUserEmail.mockReturnValue(undefined);
|
|
49
75
|
mocks.isStandaloneDispatchRuntime.mockReturnValue(false);
|
|
50
76
|
});
|
|
51
77
|
|
|
52
78
|
it("denies an organization member without the Dispatch admin role", async () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
mocks.assertPermission.mockRejectedValue(
|
|
57
|
-
new ForbiddenError("Requires dispatch role admin"),
|
|
79
|
+
await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
|
|
80
|
+
"Requires dispatch permission administer",
|
|
58
81
|
);
|
|
82
|
+
});
|
|
59
83
|
|
|
84
|
+
it.each(["owner", "admin"])(
|
|
85
|
+
"allows an organization %s without app roles through the shared policy",
|
|
86
|
+
async (role) => {
|
|
87
|
+
mocks.orgRole = role;
|
|
88
|
+
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
|
|
89
|
+
{ active: true, role },
|
|
90
|
+
);
|
|
91
|
+
await expect(
|
|
92
|
+
authorizeDispatchAdmin({}, context),
|
|
93
|
+
).resolves.toBeUndefined();
|
|
94
|
+
expect(mocks.execute).toHaveBeenCalledWith(
|
|
95
|
+
expect.objectContaining({
|
|
96
|
+
args: ["dispatch", "org-1", "member@example.test"],
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
it("uses the identity hub role over a stale local organization admin role", async () => {
|
|
103
|
+
mocks.federated = true;
|
|
104
|
+
mocks.orgRole = "admin";
|
|
60
105
|
await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
|
|
61
|
-
"Requires dispatch
|
|
106
|
+
"Requires dispatch permission administer",
|
|
62
107
|
);
|
|
63
|
-
expect(
|
|
64
|
-
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
65
|
-
).toHaveBeenCalledWith({
|
|
66
|
-
orgId: "org-1",
|
|
67
|
-
email: "member@example.test",
|
|
68
|
-
});
|
|
69
|
-
expect(mocks.assertPermission).toHaveBeenCalledWith(["administer"], {
|
|
70
|
-
orgId: "org-1",
|
|
71
|
-
userEmail: "member@example.test",
|
|
72
|
-
});
|
|
73
108
|
});
|
|
74
109
|
|
|
75
|
-
it("allows an organization admin
|
|
110
|
+
it("allows an organization admin granted by the identity hub", async () => {
|
|
111
|
+
mocks.federated = true;
|
|
76
112
|
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
|
|
77
113
|
{ active: true, role: "admin" },
|
|
78
114
|
);
|
|
79
|
-
|
|
80
115
|
await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
|
|
81
|
-
expect(
|
|
116
|
+
expect(
|
|
117
|
+
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
118
|
+
).toHaveBeenCalledTimes(2);
|
|
82
119
|
});
|
|
83
120
|
|
|
84
121
|
it("denies a stale linked organization admin", async () => {
|
|
122
|
+
mocks.orgRole = "admin";
|
|
85
123
|
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockResolvedValue(
|
|
86
124
|
{ active: false, role: null },
|
|
87
125
|
);
|
|
88
|
-
|
|
89
126
|
await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
|
|
90
127
|
"active organization membership",
|
|
91
128
|
);
|
|
92
|
-
expect(mocks.
|
|
129
|
+
expect(mocks.execute).not.toHaveBeenCalled();
|
|
93
130
|
});
|
|
94
131
|
|
|
95
132
|
it("allows standalone administration when the org schema is absent", async () => {
|
|
@@ -97,27 +134,28 @@ describe("authorizeDispatchAdmin", () => {
|
|
|
97
134
|
mocks.validateFederatedOrganizationMembershipForCurrentRequest.mockRejectedValue(
|
|
98
135
|
new Error('relation "org_members" does not exist'),
|
|
99
136
|
);
|
|
100
|
-
|
|
101
137
|
await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
|
|
102
|
-
expect(mocks.
|
|
138
|
+
expect(mocks.execute).not.toHaveBeenCalled();
|
|
103
139
|
});
|
|
104
140
|
|
|
105
141
|
it("allows a member with the Dispatch admin role", async () => {
|
|
142
|
+
mocks.roles = ["admin"];
|
|
106
143
|
await expect(authorizeDispatchAdmin({}, context)).resolves.toBeUndefined();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("honors a permission override for an app admin", async () => {
|
|
147
|
+
mocks.roles = ["admin"];
|
|
148
|
+
mocks.overrides = [{ permission: "administer", roles_json: "[]" }];
|
|
149
|
+
await expect(authorizeDispatchAdmin({}, context)).rejects.toThrow(
|
|
150
|
+
"Requires dispatch permission administer",
|
|
151
|
+
);
|
|
111
152
|
});
|
|
112
153
|
|
|
113
154
|
it("allows authenticated personal-mode administration", async () => {
|
|
114
155
|
await expect(
|
|
115
156
|
authorizeDispatchAdmin({}, { ...context, orgId: null }),
|
|
116
157
|
).resolves.toBeUndefined();
|
|
117
|
-
expect(
|
|
118
|
-
mocks.validateFederatedOrganizationMembershipForCurrentRequest,
|
|
119
|
-
).not.toHaveBeenCalled();
|
|
120
|
-
expect(mocks.assertAny).not.toHaveBeenCalled();
|
|
158
|
+
expect(mocks.execute).not.toHaveBeenCalled();
|
|
121
159
|
});
|
|
122
160
|
|
|
123
161
|
it("denies an unauthenticated caller", async () => {
|
|
@@ -17,7 +17,9 @@ import { dispatchAccessDescriptor } from "../../shared/app-roles.js";
|
|
|
17
17
|
let dispatchAccess: AppRoles<"admin", "administer"> | undefined;
|
|
18
18
|
|
|
19
19
|
function getDispatchAccess(): AppRoles<"admin", "administer"> {
|
|
20
|
-
return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor
|
|
20
|
+
return (dispatchAccess ??= defineAppRoles(dispatchAccessDescriptor, {
|
|
21
|
+
allowOrgAdmins: true,
|
|
22
|
+
}));
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export async function authorizeDispatchAdmin(
|
|
@@ -55,7 +57,6 @@ export async function authorizeDispatchAdmin(
|
|
|
55
57
|
"Dispatch administration requires active organization membership.",
|
|
56
58
|
);
|
|
57
59
|
}
|
|
58
|
-
if (membership.role === "owner" || membership.role === "admin") return;
|
|
59
60
|
await getDispatchAccess().assertPermission(["administer"], {
|
|
60
61
|
userEmail: email,
|
|
61
62
|
orgId,
|