@cosmicdrift/kumiko-framework 0.87.1 → 0.87.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.87.
|
|
3
|
+
"version": "0.87.2",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -181,7 +181,7 @@
|
|
|
181
181
|
"zod": "^4.4.3"
|
|
182
182
|
},
|
|
183
183
|
"devDependencies": {
|
|
184
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.87.
|
|
184
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.87.2",
|
|
185
185
|
"bun-types": "^1.3.13",
|
|
186
186
|
"pino-pretty": "^13.1.3"
|
|
187
187
|
},
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { AccessDeniedError } from "../errors";
|
|
3
|
+
import { crossTenantOverrideDenied } from "./cross-tenant";
|
|
4
|
+
import type { SessionUser } from "./types";
|
|
5
|
+
|
|
6
|
+
const KEY = "feature.errors.tenantOverrideRequiresSystemAdmin";
|
|
7
|
+
|
|
8
|
+
function user(roles: string[]): SessionUser {
|
|
9
|
+
return { id: "u", tenantId: "t1" as SessionUser["tenantId"], roles };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe("crossTenantOverrideDenied", () => {
|
|
13
|
+
test("allows when no override is requested", () => {
|
|
14
|
+
expect(crossTenantOverrideDenied(user(["TenantAdmin"]), undefined, KEY)).toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("allows a SystemAdmin to target another tenant", () => {
|
|
18
|
+
expect(crossTenantOverrideDenied(user(["SystemAdmin"]), "other-tenant", KEY)).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("denies a TenantAdmin targeting another tenant", () => {
|
|
22
|
+
const denied = crossTenantOverrideDenied(user(["TenantAdmin"]), "other-tenant", KEY);
|
|
23
|
+
expect(denied).toBeInstanceOf(AccessDeniedError);
|
|
24
|
+
expect(denied?.code).toBe("access_denied");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("denies an Admin too — only SystemAdmin clears the override", () => {
|
|
28
|
+
expect(crossTenantOverrideDenied(user(["Admin", "TenantAdmin"]), "other", KEY)).toBeInstanceOf(
|
|
29
|
+
AccessDeniedError,
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AccessDeniedError } from "../errors";
|
|
2
|
+
import type { SessionUser } from "./types";
|
|
3
|
+
|
|
4
|
+
// A payload-supplied target tenant (tenantIdOverride) on a TenantAdmin-reachable
|
|
5
|
+
// handler is the cross-tenant escape hatch: hasAccess passes the handler for any
|
|
6
|
+
// TenantAdmin, so without a SystemAdmin gate a TenantAdmin could act on another
|
|
7
|
+
// tenant. Centralizes the check the override handlers used to inline. Returns
|
|
8
|
+
// the denial to `throw` (queries) or wrap in `writeFailure` (writes), or
|
|
9
|
+
// undefined when allowed. The i18nKey stays per-feature so existing
|
|
10
|
+
// translations keep resolving.
|
|
11
|
+
export function crossTenantOverrideDenied(
|
|
12
|
+
user: SessionUser,
|
|
13
|
+
tenantIdOverride: string | undefined,
|
|
14
|
+
i18nKey: string,
|
|
15
|
+
): AccessDeniedError | undefined {
|
|
16
|
+
if (tenantIdOverride === undefined) return undefined;
|
|
17
|
+
if (user.roles.includes("SystemAdmin")) return undefined;
|
|
18
|
+
return new AccessDeniedError({
|
|
19
|
+
i18nKey,
|
|
20
|
+
details: { reason: "tenant_override_requires_system_admin" },
|
|
21
|
+
});
|
|
22
|
+
}
|
package/src/engine/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ export {
|
|
|
38
38
|
} from "./constants";
|
|
39
39
|
export type { App, AppConfig } from "./create-app";
|
|
40
40
|
export { createApp } from "./create-app";
|
|
41
|
+
export { crossTenantOverrideDenied } from "./cross-tenant";
|
|
41
42
|
export { defineFeature } from "./define-feature";
|
|
42
43
|
export type {
|
|
43
44
|
QueryHandlerDefinition,
|