@cosmicdrift/kumiko-bundled-features 0.153.0 → 0.154.0

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-bundled-features",
3
- "version": "0.153.0",
3
+ "version": "0.154.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -115,11 +115,11 @@
115
115
  "./step-dispatcher": "./src/step-dispatcher/index.ts"
116
116
  },
117
117
  "dependencies": {
118
- "@cosmicdrift/kumiko-dispatcher-live": "0.153.0",
119
- "@cosmicdrift/kumiko-framework": "0.153.0",
120
- "@cosmicdrift/kumiko-headless": "0.153.0",
121
- "@cosmicdrift/kumiko-renderer": "0.153.0",
122
- "@cosmicdrift/kumiko-renderer-web": "0.153.0",
118
+ "@cosmicdrift/kumiko-dispatcher-live": "0.154.0",
119
+ "@cosmicdrift/kumiko-framework": "0.154.0",
120
+ "@cosmicdrift/kumiko-headless": "0.154.0",
121
+ "@cosmicdrift/kumiko-renderer": "0.154.0",
122
+ "@cosmicdrift/kumiko-renderer-web": "0.154.0",
123
123
  "@mollie/api-client": "^4.5.0",
124
124
  "imapflow": "^1.3.3",
125
125
  "mailparser": "^3.9.8",
@@ -4,6 +4,7 @@ import {
4
4
  createTestUser,
5
5
  setupTestStack,
6
6
  type TestStack,
7
+ testTenantId,
7
8
  unsafeCreateEntityTable,
8
9
  unsafePushTables,
9
10
  } from "@cosmicdrift/kumiko-framework/stack";
@@ -18,7 +19,7 @@ import { createTenantFeature } from "../../tenant";
18
19
  import { createUserFeature } from "../../user/feature";
19
20
  import { userEntity } from "../../user/schema/user";
20
21
  import { base32Decode } from "../base32";
21
- import { AuthMfaHandlers } from "../constants";
22
+ import { AuthMfaHandlers, AuthMfaQueries } from "../constants";
22
23
  import { createAuthMfaFeature } from "../feature";
23
24
  import { userMfaEntity } from "../schema/user-mfa";
24
25
  import { currentTotpCode } from "../totp";
@@ -216,3 +217,49 @@ describe("regenerate-recovery", () => {
216
217
  expectErrorIncludes(err, "mfa_not_enabled");
217
218
  });
218
219
  });
220
+
221
+ // pr-review kumiko-framework #1062/4 — the only prior coverage of
222
+ // user-mfa:status was the false → true transition (implicit in
223
+ // enableMfaFor's own setup); the more interesting true → false after
224
+ // disable, and tenant isolation, had no assertion anywhere.
225
+ describe("status query", () => {
226
+ test("true → false after disable", async () => {
227
+ const { user, secret } = await enableMfaFor("status-disable-1");
228
+
229
+ const before = await stack.http.queryOk<{ enabled: boolean }>(AuthMfaQueries.status, {}, user);
230
+ expect(before.enabled).toBe(true);
231
+
232
+ await stack.http.writeOk(AuthMfaHandlers.disable, { code: currentTotpCode(secret) }, user);
233
+
234
+ const after = await stack.http.queryOk<{ enabled: boolean }>(AuthMfaQueries.status, {}, user);
235
+ expect(after.enabled).toBe(false);
236
+ });
237
+
238
+ test("tenant isolation: same userId enrolled in tenant A stays invisible to the same id in tenant B", async () => {
239
+ // The entity's uniqueness is [userId, tenantId] — a second tenant CAN
240
+ // have a distinct (non-enrolled) row for the identical userId. The
241
+ // query filters only by userId (see status.query.ts's own comment) and
242
+ // relies on ctx.db's tenant scoping to keep them apart; assert that
243
+ // scoping actually holds instead of just trusting it.
244
+ const { user: tenantAUser } = await enableMfaFor("status-tenant-iso-1");
245
+ const tenantBUser = createTestUser({
246
+ id: tenantAUser.id,
247
+ tenantId: testTenantId(2),
248
+ roles: ["User"],
249
+ });
250
+
251
+ const tenantAStatus = await stack.http.queryOk<{ enabled: boolean }>(
252
+ AuthMfaQueries.status,
253
+ {},
254
+ tenantAUser,
255
+ );
256
+ expect(tenantAStatus.enabled).toBe(true);
257
+
258
+ const tenantBStatus = await stack.http.queryOk<{ enabled: boolean }>(
259
+ AuthMfaQueries.status,
260
+ {},
261
+ tenantBUser,
262
+ );
263
+ expect(tenantBStatus.enabled).toBe(false);
264
+ });
265
+ });