@cosmicdrift/kumiko-server-runtime 0.358.0 → 0.360.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.
@@ -0,0 +1,12 @@
1
+ export declare const APP_BASE_URL_ENV = "APP_BASE_URL";
2
+ type AuthOriginOptions = {
3
+ readonly allowedOrigins?: readonly string[];
4
+ readonly unsafeSkipOriginCheck?: boolean;
5
+ readonly cookieDomain?: string;
6
+ readonly publicOrigin?: string;
7
+ readonly mail?: {
8
+ readonly baseUrl: string;
9
+ };
10
+ };
11
+ export declare function resolveAuthPublicOrigin(auth: AuthOriginOptions, envSource: Record<string, string | undefined>, warn: (message: string) => void): string | undefined;
12
+ export {};
@@ -0,0 +1,23 @@
1
+ import { publicOriginOf } from "@cosmicdrift/kumiko-framework/api";
2
+ export const APP_BASE_URL_ENV = "APP_BASE_URL";
3
+ // The mail link base is what users already open from auth mails, so it wins
4
+ // over the infra-provided APP_BASE_URL. Missing or unparsable in production
5
+ // is a loud warning, not a boot failure: the double-submit CSRF token still
6
+ // protects, the Origin guard is defense in depth.
7
+ export function resolveAuthPublicOrigin(auth, envSource, warn) {
8
+ if ((auth.allowedOrigins?.length ?? 0) > 0 || auth.unsafeSkipOriginCheck === true) {
9
+ return undefined;
10
+ }
11
+ const envBaseUrl = envSource[APP_BASE_URL_ENV] === "" ? undefined : envSource[APP_BASE_URL_ENV];
12
+ const baseUrl = auth.publicOrigin ?? auth.mail?.baseUrl ?? envBaseUrl;
13
+ const origin = baseUrl === undefined ? undefined : publicOriginOf(baseUrl);
14
+ if (origin === undefined &&
15
+ auth.cookieDomain === undefined &&
16
+ envSource["NODE_ENV"] === "production") {
17
+ warn("[runProdApp] WARNING: no valid public app URL is known (auth.publicOrigin, " +
18
+ `auth.mail.baseUrl or ${APP_BASE_URL_ENV}), so the server-side Origin guard is OFF. ` +
19
+ "Cookie-authenticated requests are protected by the CSRF token only. Set " +
20
+ `auth.mail.baseUrl or ${APP_BASE_URL_ENV} to the app's https URL.`);
21
+ }
22
+ return origin;
23
+ }
@@ -132,6 +132,10 @@ export type RunProdAppAuthOptions = {
132
132
  * AuthRoutesConfig.cookieDomain). Set to the registrable parent
133
133
  * domain when login and app live on different subdomains. */
134
134
  readonly cookieDomain?: string;
135
+ /** Secure flag for the session cookies (see AuthRoutesConfig.secureCookies).
136
+ * Defaults to NODE_ENV === "production"; set it to true when TLS ends at a
137
+ * proxy and NODE_ENV is not "production". */
138
+ readonly secureCookies?: boolean;
135
139
  /** Former cookie domains cleared on login/logout (see
136
140
  * AuthRoutesConfig.retiredCookieDomains). */
137
141
  readonly retiredCookieDomains?: readonly string[];
@@ -144,6 +148,10 @@ export type RunProdAppAuthOptions = {
144
148
  * — accept the wide-cookie CSRF risk explicitly instead of setting
145
149
  * `allowedOrigins`. */
146
150
  readonly unsafeSkipOriginCheck?: boolean;
151
+ /** Canonical public origin for the default Origin guard (see
152
+ * AuthRoutesConfig.publicOrigin). Defaults to the origin of
153
+ * `mail.baseUrl`, then of the APP_BASE_URL env. */
154
+ readonly publicOrigin?: string;
147
155
  /** Number of trusted reverse-proxy hops between the client and this
148
156
  * process for client-IP derivation (see AuthRoutesConfig.trustedProxyHops,
149
157
  * kumiko-framework#1539) — closes the X-Forwarded-For spoofing hole on
@@ -71,6 +71,7 @@ import { buildBunServeOptions, resolveDerivedMaxRequestBodySize } from "./bun-se
71
71
  import { buildComposeAuthOptions, composeFeatures } from "./compose-features.js";
72
72
  import { makeDispatchSystemWrite } from "./extra-routes-deps.js";
73
73
  import { assertPiiBootInvariants } from "./pii-boot-gate.js";
74
+ import { resolveAuthPublicOrigin } from "./resolve-auth-public-origin.js";
74
75
  import { addConfigAccessorFactory, buildBootExtraContext, buildProdSessionAuth, loggerFromExtraContext, resolveAuthMail, wireProdPatAutoRevoke, } from "./run-prod-app-boot-context.js";
75
76
  import { buildStaticFallback } from "./run-prod-app-static-files.js";
76
77
  import { withSecurityHeaders } from "./security-headers.js";
@@ -279,6 +280,12 @@ export async function runProdApp(options) {
279
280
  const effectiveAuth = options.auth
280
281
  ? resolveAuthMail(options.auth, jwtSecret, envSource)
281
282
  : undefined;
283
+ const authPublicOrigin = effectiveAuth
284
+ ? resolveAuthPublicOrigin(effectiveAuth, envSource, (message) => {
285
+ // biome-ignore lint/suspicious/noConsole: boot-time warn, no logger configured this early
286
+ console.warn(message);
287
+ })
288
+ : undefined;
282
289
  // 3. Feature registry. Auth-mode auto-mixes config/user/tenant/auth-email-
283
290
  // password via composeFeatures — same source-of-truth as runDevApp
284
291
  // AND the per-app drizzle-Schema-Generator, so Migration und Runtime
@@ -532,6 +539,9 @@ export async function runProdApp(options) {
532
539
  ...(effectiveAuth.cookieDomain !== undefined && {
533
540
  cookieDomain: effectiveAuth.cookieDomain,
534
541
  }),
542
+ ...(effectiveAuth.secureCookies !== undefined && {
543
+ secureCookies: effectiveAuth.secureCookies,
544
+ }),
535
545
  ...(effectiveAuth.retiredCookieDomains !== undefined && {
536
546
  retiredCookieDomains: effectiveAuth.retiredCookieDomains,
537
547
  }),
@@ -541,6 +551,7 @@ export async function runProdApp(options) {
541
551
  ...(effectiveAuth.unsafeSkipOriginCheck !== undefined && {
542
552
  unsafeSkipOriginCheck: effectiveAuth.unsafeSkipOriginCheck,
543
553
  }),
554
+ ...(authPublicOrigin !== undefined && { publicOrigin: authPublicOrigin }),
544
555
  ...(trustedProxyHops !== undefined && { trustedProxyHops }),
545
556
  ...(effectiveAuth.postAuthLanding !== undefined && {
546
557
  postAuthLanding: effectiveAuth.postAuthLanding,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-server-runtime",
3
- "version": "0.358.0",
3
+ "version": "0.360.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>",
@@ -84,11 +84,11 @@
84
84
  }
85
85
  },
86
86
  "dependencies": {
87
- "@cosmicdrift/kumiko-bundled-features": "0.358.0",
88
- "@cosmicdrift/kumiko-framework": "0.358.0",
89
- "@cosmicdrift/kumiko-headless": "0.358.0",
90
- "@cosmicdrift/kumiko-renderer-web": "0.358.0",
91
- "@cosmicdrift/kumiko-types": "0.358.0",
87
+ "@cosmicdrift/kumiko-bundled-features": "0.360.0",
88
+ "@cosmicdrift/kumiko-framework": "0.360.0",
89
+ "@cosmicdrift/kumiko-headless": "0.360.0",
90
+ "@cosmicdrift/kumiko-renderer-web": "0.360.0",
91
+ "@cosmicdrift/kumiko-types": "0.360.0",
92
92
  "bun-types": "^1.3.13",
93
93
  "ioredis": "^5.10.1",
94
94
  "temporal-polyfill": "^0.3.2"