@cosmicdrift/kumiko-server-runtime 0.160.0 → 0.162.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-server-runtime",
3
- "version": "0.160.0",
3
+ "version": "0.162.0",
4
4
  "description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold/codegen tooling.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -72,8 +72,8 @@
72
72
  }
73
73
  },
74
74
  "dependencies": {
75
- "@cosmicdrift/kumiko-bundled-features": "0.160.0",
76
- "@cosmicdrift/kumiko-framework": "0.160.0",
75
+ "@cosmicdrift/kumiko-bundled-features": "0.162.0",
76
+ "@cosmicdrift/kumiko-framework": "0.162.0",
77
77
  "temporal-polyfill": "^0.3.2"
78
78
  },
79
79
  "publishConfig": {
@@ -823,7 +823,12 @@ describe("runProdApp — auth allowedOrigins forwarding", () => {
823
823
  test("cookieDomain without allowedOrigins fails closed — guard is wired through runProdApp", async () => {
824
824
  await expect(
825
825
  boot(undefined, {
826
- auth: { admin: ADMIN, cookieDomain: "example.eu", sessions: false },
826
+ features: [
827
+ authFoundationFeature,
828
+ createPersonalAccessTokensFeature({ scopes: {} }),
829
+ createSessionsFeature(),
830
+ ],
831
+ auth: { admin: ADMIN, cookieDomain: "example.eu" },
827
832
  allowPlaintextPii: "test: origin-guard focus, not crypto",
828
833
  }),
829
834
  ).rejects.toThrow(/allowedOrigins is empty/);
@@ -836,11 +841,15 @@ describe("runProdApp — auth allowedOrigins forwarding", () => {
836
841
  let bootError: unknown;
837
842
  try {
838
843
  const handle = await boot(undefined, {
844
+ features: [
845
+ authFoundationFeature,
846
+ createPersonalAccessTokensFeature({ scopes: {} }),
847
+ createSessionsFeature(),
848
+ ],
839
849
  auth: {
840
850
  admin: ADMIN,
841
851
  cookieDomain: "example.eu",
842
852
  allowedOrigins: ["https://app.example.eu"],
843
- sessions: false,
844
853
  },
845
854
  });
846
855
  expect(handle).toBeDefined();
@@ -861,7 +870,7 @@ describe("runProdApp — session boot gate (#1262/#1275)", () => {
861
870
  memberships: [],
862
871
  };
863
872
 
864
- test("auth mounted, sessions feature missing, no opt-out → aborts boot", async () => {
873
+ test("auth mounted, sessions feature missing → aborts boot", async () => {
865
874
  await expect(
866
875
  boot(undefined, {
867
876
  auth: {
@@ -871,7 +880,7 @@ describe("runProdApp — session boot gate (#1262/#1275)", () => {
871
880
  },
872
881
  allowPlaintextPii: "test: session-gate focus, not crypto",
873
882
  }),
874
- ).rejects.toThrow(/BOOT ABORTED.*sessions.*stateless/s);
883
+ ).rejects.toThrow(/BOOT ABORTED.*sessionStore/);
875
884
  });
876
885
 
877
886
  test("auth mounted, sessions feature mounted → boots cleanly (the happy path the gate guards)", async () => {
@@ -1,54 +1,22 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { assertSessionBootInvariants } from "../session-boot-gate";
3
3
 
4
- describe("assertSessionBootInvariants", () => {
5
- test("no auth mounted nothing to gate", () => {
4
+ describe("assertSessionBootInvariants (#1372)", () => {
5
+ test("no auth → no throw", () => {
6
6
  expect(() =>
7
- assertSessionBootInvariants({
8
- hasAuth: false,
9
- sessionsFeatureMounted: false,
10
- sessionsOption: undefined,
11
- }),
7
+ assertSessionBootInvariants({ hasAuth: false, sessionStoreProviderMounted: false }),
12
8
  ).not.toThrow();
13
9
  });
14
10
 
15
- test("auth mounted, sessions feature missing, no opt-out → aborts boot", () => {
11
+ test("auth + sessionStore no throw", () => {
16
12
  expect(() =>
17
- assertSessionBootInvariants({
18
- hasAuth: true,
19
- sessionsFeatureMounted: false,
20
- sessionsOption: undefined,
21
- }),
22
- ).toThrow(/BOOT ABORTED.*sessions.*stateless/s);
23
- });
24
-
25
- test("auth mounted, sessions feature missing, explicit sessions:false → boots", () => {
26
- expect(() =>
27
- assertSessionBootInvariants({
28
- hasAuth: true,
29
- sessionsFeatureMounted: false,
30
- sessionsOption: false,
31
- }),
32
- ).not.toThrow();
33
- });
34
-
35
- test("auth mounted, sessions feature wired → boots", () => {
36
- expect(() =>
37
- assertSessionBootInvariants({
38
- hasAuth: true,
39
- sessionsFeatureMounted: true,
40
- sessionsOption: undefined,
41
- }),
13
+ assertSessionBootInvariants({ hasAuth: true, sessionStoreProviderMounted: true }),
42
14
  ).not.toThrow();
43
15
  });
44
16
 
45
- test("auth mounted, sessions feature wired AND an expiresInMs override boots", () => {
17
+ test("auth without sessionStorethrows", () => {
46
18
  expect(() =>
47
- assertSessionBootInvariants({
48
- hasAuth: true,
49
- sessionsFeatureMounted: true,
50
- sessionsOption: { expiresInMs: 60_000 },
51
- }),
52
- ).not.toThrow();
19
+ assertSessionBootInvariants({ hasAuth: true, sessionStoreProviderMounted: false }),
20
+ ).toThrow(/BOOT ABORTED/);
53
21
  });
54
22
  });
@@ -1,51 +1,16 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import {
3
- createSessionsFeature,
4
- SESSIONS_FEATURE,
5
- } from "@cosmicdrift/kumiko-bundled-features/sessions";
6
- import { resolveProdSessionsConfig, shouldWireProdSessions } from "../session-wiring";
2
+ import { shouldWireProdSessions } from "../session-wiring";
7
3
 
8
- describe("shouldWireProdSessions — secure-by-default with opt-out (KF-1)", () => {
9
- it("wires sessions when the feature is mounted, even without an explicit config", () => {
10
- // The publicstatus bug: sessions feature mounted + auth set, but no auth.sessions —
11
- // previously left stateless (no revocation). Now it wires automatically.
12
- expect(shouldWireProdSessions(true, true, undefined)).toBe(true);
4
+ describe("shouldWireProdSessions — secure-by-default (#1372)", () => {
5
+ it("wires when auth + sessionStore provider mounted", () => {
6
+ expect(shouldWireProdSessions(true, true)).toBe(true);
13
7
  });
14
8
 
15
- it("wires sessions when a config object is given", () => {
16
- expect(shouldWireProdSessions(true, true, { expiresInMs: 1000 })).toBe(true);
9
+ it("does not wire without auth", () => {
10
+ expect(shouldWireProdSessions(false, true)).toBe(false);
17
11
  });
18
12
 
19
- it("does not wire when sessions: false (explicit opt-out)", () => {
20
- expect(shouldWireProdSessions(true, true, false)).toBe(false);
21
- });
22
-
23
- it("does not wire when the sessions feature is not mounted", () => {
24
- expect(shouldWireProdSessions(true, false, undefined)).toBe(false);
25
- });
26
-
27
- it("does not wire when the app has no auth at all", () => {
28
- expect(shouldWireProdSessions(false, true, undefined)).toBe(false);
29
- });
30
- });
31
-
32
- describe("SESSIONS_FEATURE constant matches the real feature name", () => {
33
- it("createSessionsFeature()'s name equals SESSIONS_FEATURE", () => {
34
- // shouldWireProdSessions's own arm only tests the pure boolean helper —
35
- // the actual run-prod-app.ts integration seam
36
- // (`features.some((f) => f.name === SESSIONS_FEATURE)`) drifts silently
37
- // if the feature is ever renamed without updating this constant.
38
- expect(createSessionsFeature().name).toBe(SESSIONS_FEATURE);
39
- });
40
- });
41
-
42
- describe("resolveProdSessionsConfig", () => {
43
- it("passes a config object through", () => {
44
- expect(resolveProdSessionsConfig({ expiresInMs: 5000 })).toEqual({ expiresInMs: 5000 });
45
- });
46
-
47
- it("collapses false / undefined to defaults", () => {
48
- expect(resolveProdSessionsConfig(undefined)).toEqual({});
49
- expect(resolveProdSessionsConfig(false)).toEqual({});
13
+ it("does not wire without sessionStore provider", () => {
14
+ expect(shouldWireProdSessions(true, false)).toBe(false);
50
15
  });
51
16
  });
@@ -1,4 +1,5 @@
1
1
  import { makeAuthPaths } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
2
+ import { resolveSessionStore } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
2
3
  import { bindMfaRevokeAllOtherSessionsFromFeature } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
3
4
  import { createSmtpTransportFromEnv } from "@cosmicdrift/kumiko-bundled-features/channel-email";
4
5
  import {
@@ -15,10 +16,7 @@ import {
15
16
  createSecretsContext,
16
17
  SECRETS_FEATURE_NAME,
17
18
  } from "@cosmicdrift/kumiko-bundled-features/secrets";
18
- import {
19
- bindAutoRevokeFromFeature,
20
- createSessionCallbacks,
21
- } from "@cosmicdrift/kumiko-bundled-features/sessions";
19
+ import { bindAutoRevokeFromFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
22
20
  import { createTextContentApi } from "@cosmicdrift/kumiko-bundled-features/text-content";
23
21
  import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
24
22
  import type { KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
@@ -38,7 +36,6 @@ import type {
38
36
  PasswordResetSetup,
39
37
  SignupSetup,
40
38
  } from "./run-prod-app";
41
- import type { ProdSessionsConfig } from "./session-wiring";
42
39
 
43
40
  // Boot-time context helpers for runProdApp: ctx-extra-context wiring
44
41
  // (textContent/delivery/secrets/config-resolver), auth-mail convenience
@@ -214,33 +211,27 @@ export function resolveAuthMail<T extends AuthMailNormalizable>(
214
211
  };
215
212
  }
216
213
 
217
- export function buildProdSessionAuth(
214
+ export async function buildProdSessionAuth(
218
215
  db: DbConnection,
219
- opts: ProdSessionsConfig,
216
+ registry: Registry,
220
217
  sessionsFeature: FeatureDefinition | undefined,
221
218
  mfaFeature: FeatureDefinition | undefined,
222
- ): {
223
- readonly sessionCreator: ReturnType<typeof createSessionCallbacks>["sessionCreator"];
224
- readonly sessionRevoker: ReturnType<typeof createSessionCallbacks>["sessionRevoker"];
225
- readonly sessionChecker: ReturnType<typeof createSessionCallbacks>["sessionChecker"];
226
- } {
227
- const cbs = createSessionCallbacks({
228
- db,
229
- ...(opts.expiresInMs !== undefined && { expiresInMs: opts.expiresInMs }),
230
- });
231
- // Secure-by-default: password-change/-reset mass-revokes the user's live
232
- // sessions without the app opting in via autoRevokeOnPasswordChange.
219
+ ): Promise<{
220
+ readonly sessionCreator: Awaited<ReturnType<typeof resolveSessionStore>>["creator"];
221
+ readonly sessionRevoker: Awaited<ReturnType<typeof resolveSessionStore>>["revoker"];
222
+ readonly sessionChecker: Awaited<ReturnType<typeof resolveSessionStore>>["checker"];
223
+ }> {
224
+ // Resolve the sessions feature sessionStore provider (#1372).
225
+ const store = await resolveSessionStore({ db, registry });
233
226
  if (sessionsFeature) {
234
- bindAutoRevokeFromFeature(sessionsFeature)?.(cbs.sessionMassRevoker);
227
+ bindAutoRevokeFromFeature(sessionsFeature)?.(store.massRevoker);
235
228
  }
236
- // MFA enable/disable/regenerate mass-revokes every OTHER live session
237
- // (stolen-session defense) — only wired when auth-mfa is mounted.
238
229
  if (mfaFeature) {
239
- bindMfaRevokeAllOtherSessionsFromFeature(mfaFeature)?.(cbs.sessionRevokeAllOthers);
230
+ bindMfaRevokeAllOtherSessionsFromFeature(mfaFeature)?.(store.revokeAllOthers);
240
231
  }
241
232
  return {
242
- sessionCreator: cbs.sessionCreator,
243
- sessionRevoker: cbs.sessionRevoker,
244
- sessionChecker: cbs.sessionChecker,
233
+ sessionCreator: store.creator,
234
+ sessionRevoker: store.revoker,
235
+ sessionChecker: store.checker,
245
236
  };
246
237
  }
@@ -46,7 +46,9 @@ import {
46
46
  seedAdmin,
47
47
  } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
48
48
  import {
49
+ EXT_SESSION_STORE,
49
50
  EXT_TOKEN_VERIFIER,
51
+ resolveAnonymousAccessFromRegistry,
50
52
  resolveTokenVerifier,
51
53
  } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
52
54
  import { AUTH_MFA_FEATURE, AuthMfaHandlers } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
@@ -140,11 +142,7 @@ import {
140
142
  import { buildStaticFallback } from "./run-prod-app-static-files";
141
143
  import { type SecurityHeadersOption, withSecurityHeaders } from "./security-headers";
142
144
  import { assertSessionBootInvariants } from "./session-boot-gate";
143
- import {
144
- type ProdSessionsOption,
145
- resolveProdSessionsConfig,
146
- shouldWireProdSessions,
147
- } from "./session-wiring";
145
+ import { shouldWireProdSessions } from "./session-wiring";
148
146
 
149
147
  export { buildBunServeOptions } from "./bun-serve-options";
150
148
  export {
@@ -287,14 +285,6 @@ export type RunProdAppAuthOptions = {
287
285
  readonly admin: SeedAdminOptions;
288
286
  /** Optional override of the login error → HTTP status map. */
289
287
  readonly loginErrorStatusMap?: Readonly<Record<string, number>>;
290
- /** Opt-in: revocable server-side sessions. Caller MUSS
291
- * `createSessionsFeature()` zu `features` adden — runProdApp wired
292
- * hier nur die Auth-Callbacks (creator/revoker/checker) gegen die
293
- * echte db-connection (sidless JWTs werden dann abgelehnt).
294
- *
295
- * Standardverhalten ohne diese Option: stateless JWTs ohne sid
296
- * (legacy-Verhalten, Karten­haus existing-Apps unangefasst). */
297
- readonly sessions?: ProdSessionsOption;
298
288
  /** Auth-Mail-Convenience: verdrahtet alle 4 Mail-Flows (passwordReset,
299
289
  * emailVerification, signup, invite) aus `auth.mail.baseUrl` + Standard-
300
290
  * Pfaden. Alle vier mailen via delivery (ctx.notify) — ersetzt das per-App
@@ -368,10 +358,8 @@ export type RunProdAppDeps = {
368
358
  };
369
359
 
370
360
  export type AnonymousAccessOption =
371
- | import("@cosmicdrift/kumiko-framework/api").ServerOptions["anonymousAccess"]
372
- | ((
373
- deps: RunProdAppDeps,
374
- ) => import("@cosmicdrift/kumiko-framework/api").ServerOptions["anonymousAccess"]);
361
+ | import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig
362
+ | ((deps: RunProdAppDeps) => import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig);
375
363
 
376
364
  export type ExtraContextOption =
377
365
  | Record<string, unknown>
@@ -726,12 +714,11 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
726
714
  mode: "prod",
727
715
  });
728
716
  const sessionsFeature = features.find((f) => f.name === SESSIONS_FEATURE);
717
+ const registry = createRegistry(features);
729
718
  assertSessionBootInvariants({
730
719
  hasAuth: Boolean(effectiveAuth),
731
- sessionsFeatureMounted: sessionsFeature !== undefined,
732
- sessionsOption: effectiveAuth?.sessions,
720
+ sessionStoreProviderMounted: registry.getExtensionUsages(EXT_SESSION_STORE).length > 0,
733
721
  });
734
- const registry = createRegistry(features);
735
722
 
736
723
  // C1 boot-mode exit: validators ran + registry built; no DB/Redis client
737
724
  // is constructed at all in this branch (the eager `new Redis(...)` below
@@ -861,10 +848,15 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
861
848
  { ...autoExtraContext, ...resolvedExtraContext },
862
849
  registry,
863
850
  );
864
- const resolvedAnonymousAccess =
851
+ const baseAnonymousAccess =
865
852
  typeof options.anonymousAccess === "function"
866
853
  ? options.anonymousAccess(deps)
867
854
  : options.anonymousAccess;
855
+ // #1374: tenantResolver / tenantExists come from auth-foundation providers.
856
+ const resolvedAnonymousAccess = await resolveAnonymousAccessFromRegistry(baseAnonymousAccess, {
857
+ db,
858
+ registry,
859
+ });
868
860
 
869
861
  // Sessions opt-in: db ist hier schon konkret (createDbConnection oben),
870
862
  // also direkt verdrahten — kein late-bound nötig wie bei runDevApp.
@@ -874,22 +866,13 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
874
866
  // AuthRoutesConfig-Surface — der geht via bindAutoRevokeFromFeature ans
875
867
  // sessions-Feature (Password-Change/-Reset revoked alle Sessions), nicht
876
868
  // über die auth-routes.
877
- // Secure-by-default: if the sessions feature is mounted, server-side revocation +
878
- // auto-revoke-on-password-change are wired automatically;
879
- // `auth.sessions` only overrides the config, and `auth.sessions: false` is the
880
- // explicit opt-out (back to stateless JWTs).
869
+ // Secure-by-default (#1372): sessionStore provider resolveSessionStore.
881
870
  const mfaFeature = features.find((f) => f.name === AUTH_MFA_FEATURE);
882
871
  const sessionAuthFragment = shouldWireProdSessions(
883
872
  Boolean(effectiveAuth),
884
- sessionsFeature !== undefined,
885
- effectiveAuth?.sessions,
873
+ registry.getExtensionUsages(EXT_SESSION_STORE).length > 0,
886
874
  )
887
- ? buildProdSessionAuth(
888
- db,
889
- resolveProdSessionsConfig(effectiveAuth?.sessions),
890
- sessionsFeature,
891
- mfaFeature,
892
- )
875
+ ? await buildProdSessionAuth(db, registry, sessionsFeature, mfaFeature)
893
876
  : undefined;
894
877
 
895
878
  // Token-verifier opt-in: any provider feature (personal-access-tokens, a
@@ -975,12 +958,20 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
975
958
  ...tenantLifecycleAuthFragment,
976
959
  ...(mfaFeature && {
977
960
  mfaVerifyHandler: AuthMfaHandlers.verify,
961
+ mfaPreauthEnableStartHandler: AuthMfaHandlers.enableStartPreauth,
962
+ mfaPreauthConfirmHandler: AuthMfaHandlers.enableConfirmPreauth,
978
963
  mfaVerifyRateLimit: createRedisLoginRateLimiter(
979
964
  redis,
980
965
  undefined,
981
966
  undefined,
982
967
  "mfa-verify",
983
968
  ),
969
+ mfaPreauthConfirmRateLimit: createRedisLoginRateLimiter(
970
+ redis,
971
+ undefined,
972
+ undefined,
973
+ "mfa-preauth-confirm",
974
+ ),
984
975
  }),
985
976
  ...(effectiveAuth.passwordReset && {
986
977
  passwordReset: {
@@ -1,29 +1,20 @@
1
- import type { ProdSessionsOption } from "./session-wiring";
2
-
3
1
  export type SessionBootGateOptions = {
4
2
  readonly hasAuth: boolean;
5
- readonly sessionsFeatureMounted: boolean;
6
- readonly sessionsOption: ProdSessionsOption | undefined;
3
+ readonly sessionStoreProviderMounted: boolean;
7
4
  };
8
5
 
9
- // Mirrors pii-boot-gate.ts: catch a forgotten wiring at boot instead of
10
- // letting it degrade silently into stateless JWTs (no server-side
11
- // revocation, valid for the full 24h token TTL). `auth.sessions: false` is
12
- // already the sanctioned opt-out (see session-wiring.ts) — reusing it here
13
- // instead of inventing a second acknowledgment param.
6
+ // Catch a forgotten sessions mount at boot instead of silently degrading
7
+ // into stateless JWTs (#1372). Mount createSessionsFeature() for revocable
8
+ // sessions; there is no auth.sessions opt-out anymore.
14
9
  export function assertSessionBootInvariants(opts: SessionBootGateOptions): void {
15
10
  // skip: no auth mounted — nothing to gate.
16
11
  if (!opts.hasAuth) return;
17
- // skip: explicit opt-out, operator acknowledged stateless JWTs.
18
- if (opts.sessionsOption === false) return;
19
- // skip: sessions feature is wired.
20
- if (opts.sessionsFeatureMounted) return;
12
+ // skip: sessionStore provider is wired (sessions feature).
13
+ if (opts.sessionStoreProviderMounted) return;
21
14
 
22
15
  throw new Error(
23
- "[runProdApp] BOOT ABORTED — auth is mounted but the `sessions` feature is not. " +
24
- "JWTs would be stateless (no server-side revocation, valid until the 24h expiry) " +
25
- "with no warning. Mount createSessionsFeature() " +
26
- "(@cosmicdrift/kumiko-bundled-features/sessions) for revocable sessions, or pass " +
27
- "{ auth: { sessions: false } } to acknowledge stateless JWTs are intentional.",
16
+ "[runProdApp] BOOT ABORTED — auth is mounted but no sessionStore provider is registered. " +
17
+ "JWTs would be stateless (no server-side revocation). Mount createSessionsFeature() " +
18
+ "(@cosmicdrift/kumiko-bundled-features/sessions) alongside auth-foundation.",
28
19
  );
29
20
  }
@@ -2,28 +2,16 @@
2
2
  * runProdApp session-wiring decision (extracted pure so it is testable without a
3
3
  * full prod boot).
4
4
  *
5
- * Secure-by-default: mounting the `sessions` feature turns server-side session
6
- * revocation ON automatically there is no separate opt-in. The
7
- * `auth.sessions` option only overrides the config, and `auth.sessions: false` is the
8
- * explicit opt-out (back to stateless JWTs).
5
+ * Secure-by-default (#1372): mounting a sessionStore provider (sessions feature)
6
+ * turns server-side session revocation ON automatically. There is no
7
+ * `auth.sessions` opt-in/opt-out mount sessions for revocable JWTs, omit it
8
+ * for intentional stateless JWTs (boot-gate warns / aborts unless acknowledged
9
+ * via a future flag if needed; today auth without sessions fails the gate).
9
10
  */
10
11
 
11
- export type ProdSessionsConfig = { readonly expiresInMs?: number };
12
-
13
- /** Config object to override defaults, or `false` to disable session wiring entirely. */
14
- export type ProdSessionsOption = ProdSessionsConfig | false;
15
-
16
12
  export function shouldWireProdSessions(
17
13
  hasAuth: boolean,
18
- sessionsFeatureMounted: boolean,
19
- sessionsOption: ProdSessionsOption | undefined,
14
+ sessionStoreProviderMounted: boolean,
20
15
  ): boolean {
21
- return hasAuth && sessionsFeatureMounted && sessionsOption !== false;
22
- }
23
-
24
- /** The config passed to buildProdSessionAuth — `false`/absent collapse to defaults. */
25
- export function resolveProdSessionsConfig(
26
- sessionsOption: ProdSessionsOption | undefined,
27
- ): ProdSessionsConfig {
28
- return sessionsOption || {};
16
+ return hasAuth && sessionStoreProviderMounted;
29
17
  }