@cosmicdrift/kumiko-dev-server 0.51.0 → 0.53.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-dev-server",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -706,3 +706,44 @@ describe("runProdApp: lokaler Event-Dispatcher (MSP-Anwendung im Single-Containe
706
706
  expect(handle.entrypoint.eventDispatcher).toBeUndefined();
707
707
  });
708
708
  });
709
+
710
+ // Origin-guard config (framework #340) flows from runProdApp's auth options
711
+ // through to buildServer. Before the forwarding fix, RunProdAppAuthOptions had
712
+ // no `allowedOrigins`, so a cookieDomain app could not satisfy the fail-closed
713
+ // guard — it could only CrashLoop.
714
+ describe("runProdApp — auth allowedOrigins forwarding", () => {
715
+ const ADMIN = {
716
+ email: "origin-guard@example.eu",
717
+ password: "test-pw-strong-1234",
718
+ displayName: "Admin",
719
+ memberships: [],
720
+ };
721
+
722
+ test("cookieDomain without allowedOrigins fails closed — guard is wired through runProdApp", async () => {
723
+ await expect(
724
+ boot(undefined, { auth: { admin: ADMIN, cookieDomain: "example.eu" } }),
725
+ ).rejects.toThrow(/allowedOrigins is empty/);
726
+ });
727
+
728
+ test("cookieDomain + allowedOrigins clears the guard — allowlist reaches buildServer", async () => {
729
+ // Without the forwarding fix this would ALSO throw /allowedOrigins is empty/.
730
+ // It may still fail later on the minimal harness (no auth tables migrated),
731
+ // but never on the origin guard — that is the forwarding proof.
732
+ let bootError: unknown;
733
+ try {
734
+ const handle = await boot(undefined, {
735
+ auth: {
736
+ admin: ADMIN,
737
+ cookieDomain: "example.eu",
738
+ allowedOrigins: ["https://app.example.eu"],
739
+ },
740
+ });
741
+ expect(handle).toBeDefined();
742
+ } catch (error) {
743
+ bootError = error;
744
+ }
745
+ if (bootError !== undefined) {
746
+ expect(String(bootError)).not.toMatch(/allowedOrigins is empty/);
747
+ }
748
+ });
749
+ });
@@ -99,6 +99,12 @@ export type RunDevAppAuthOptions = {
99
99
  /** Domain attribute for both auth cookies (see
100
100
  * AuthRoutesConfig.cookieDomain). Symmetric zu RunProdAppAuthOptions. */
101
101
  readonly cookieDomain?: string;
102
+ /** Server-side Origin allowlist for the CSRF guard (see
103
+ * AuthRoutesConfig.allowedOrigins). Symmetric zu RunProdAppAuthOptions —
104
+ * required once `cookieDomain` is set. */
105
+ readonly allowedOrigins?: readonly string[];
106
+ /** Opt out of the Origin guard. Symmetric zu RunProdAppAuthOptions. */
107
+ readonly unsafeSkipOriginCheck?: boolean;
102
108
  };
103
109
 
104
110
  /** Hook for app-specific seeding (demo data, fixtures). Runs after the
@@ -300,6 +306,12 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
300
306
  ...(options.auth.cookieDomain !== undefined && {
301
307
  cookieDomain: options.auth.cookieDomain,
302
308
  }),
309
+ ...(options.auth.allowedOrigins !== undefined && {
310
+ allowedOrigins: options.auth.allowedOrigins,
311
+ }),
312
+ ...(options.auth.unsafeSkipOriginCheck !== undefined && {
313
+ unsafeSkipOriginCheck: options.auth.unsafeSkipOriginCheck,
314
+ }),
303
315
  ...sessionAuthFragment,
304
316
  ...(options.auth.passwordReset && {
305
317
  passwordReset: {
@@ -280,6 +280,15 @@ export type RunProdAppAuthOptions = {
280
280
  * AuthRoutesConfig.cookieDomain). Set to the registrable parent
281
281
  * domain when login and app live on different subdomains. */
282
282
  readonly cookieDomain?: string;
283
+ /** Server-side Origin allowlist for the CSRF guard (see
284
+ * AuthRoutesConfig.allowedOrigins). REQUIRED once `cookieDomain` is set —
285
+ * buildServer fails closed otherwise. Apex + admin host, never tenant
286
+ * subdomains. */
287
+ readonly allowedOrigins?: readonly string[];
288
+ /** Opt out of the Origin guard (see AuthRoutesConfig.unsafeSkipOriginCheck)
289
+ * — accept the wide-cookie CSRF risk explicitly instead of setting
290
+ * `allowedOrigins`. */
291
+ readonly unsafeSkipOriginCheck?: boolean;
283
292
  };
284
293
 
285
294
  /** Hook for app-specific seeding — runs after the admin (when auth is
@@ -725,6 +734,12 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
725
734
  ...(options.auth.cookieDomain !== undefined && {
726
735
  cookieDomain: options.auth.cookieDomain,
727
736
  }),
737
+ ...(options.auth.allowedOrigins !== undefined && {
738
+ allowedOrigins: options.auth.allowedOrigins,
739
+ }),
740
+ ...(options.auth.unsafeSkipOriginCheck !== undefined && {
741
+ unsafeSkipOriginCheck: options.auth.unsafeSkipOriginCheck,
742
+ }),
728
743
  ...sessionAuthFragment,
729
744
  ...(options.auth.passwordReset && {
730
745
  passwordReset: {