@softure-ai/core 0.1.7 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
4
4
  production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
5
  "Definition of done"). Versions before the first one below are described in their GitHub Releases (`core@x.y.z`).
6
6
 
7
+ ## 0.1.8
8
+
9
+ - Request origins (#311): `readRequestHost`, `readRequestOrigin`, `readForwardedProto`, `parseOrigin`,
10
+ `getTrustedOrigins` and `resolveAppOrigin(config, request, { trustedOrigins })`, one rule for every module that
11
+ builds an absolute URL for a request (README, "Request origins"). `X-Forwarded-Host` only ever picks a listed origin.
12
+ - The config takes `origins: { trustedOrigins, trustRequestHost }` (both optional; defaults `[]` and `false`), read by
13
+ auth, agent-ready, mcp-access and analytics. `SoftureConfig.origins` is always filled by `defineSoftureConfig`.
14
+
7
15
  ## 0.1.7
8
16
 
9
17
  - `toCalendarDay(instant, timeZone)` and `getCalendarDay(clock, timeZone)`: the calendar day (`YYYY-MM-DD`) of an
package/README.md CHANGED
@@ -28,6 +28,10 @@ interface SoftureConfigInput {
28
28
  locale: "en" | "pl";
29
29
  timezone: string; // IANA zone, e.g. "Europe/Warsaw"
30
30
  appOrigin: string; // http(s) origin without a path
31
+ origins?: { // read by resolveAppOrigin, see "Request origins" below
32
+ trustedOrigins?: string[]; // other origins the app is served under (at most 16)
33
+ trustRequestHost?: boolean; // trust Host for any host (default false)
34
+ };
31
35
  modules: SoftureModule[]; // a module is enabled by being listed
32
36
  }
33
37
  ```
@@ -116,6 +120,37 @@ export const notes = defineModule({
116
120
  - Version ranges in `dependsOn`: `x.y.z`, `^x.y.z`, `~x.y.z`, `*`, each optionally ending in `?`
117
121
  (optional dependency). Caret follows npm on `0.x`: `^0.1.0` is `>=0.1.0 <0.2.0`.
118
122
 
123
+ ### Request origins
124
+
125
+ Behind a proxy `request.url` names the server's listening address (`http://0.0.0.0:3000`), so a module that builds an
126
+ absolute URL for a request (auth's login redirect, agent-ready's documents, mcp-access's OAuth URLs, analytics' channel
127
+ redirect) reads the origin from headers, with one rule from core:
128
+
129
+ ```ts
130
+ import { getTrustedOrigins, readRequestHost, readRequestOrigin, resolveAppOrigin } from "@softure-ai/core";
131
+
132
+ readRequestHost(request); // first Host value, else the URL's host; lowercased
133
+ readRequestHost(request, { forwardedHost: true }); // first X-Forwarded-Host value first
134
+ readRequestOrigin(request); // X-Forwarded-Proto when http(s), else the URL's scheme; null for a bad host
135
+ getTrustedOrigins(config); // appOrigin, then origins.trustedOrigins
136
+ resolveAppOrigin(config, request); // the app origin to build URLs on for this request
137
+ ```
138
+
139
+ `resolveAppOrigin` answers, in order:
140
+
141
+ 1. the request's origin read with `X-Forwarded-Host` (a proxy that rewrites `Host` names the public host there), when
142
+ it is one of `getTrustedOrigins(config, extra)`;
143
+ 2. with `origins.trustRequestHost`, the request's origin read from `Host`, whatever the host: for one image served
144
+ under origins nobody lists (a test stack on another port), behind a proxy that passes `Host` through and refuses
145
+ hosts it does not serve;
146
+ 3. `appOrigin`.
147
+
148
+ `X-Forwarded-Host` only ever picks a listed origin: a client can send it through a proxy that keeps it. An app on two
149
+ hosts (product on `https://app.example.com`, site on `https://example.com`) lists the second once,
150
+ `origins: { trustedOrigins: ["https://example.com"] }`, and every module follows. A response built on the result is
151
+ cached per `Host`, `X-Forwarded-Host` and `X-Forwarded-Proto` (`Vary`). `parseOrigin(value)` is the strict origin check
152
+ the rule uses: exactly `http(s)://host[:port]`, a trailing `/` allowed.
153
+
119
154
  ## 4. Mounting
120
155
 
121
156
  `@softure-ai/core/next` holds the config registry for server actions and route handlers shipped
package/dist/config.d.ts CHANGED
@@ -24,6 +24,23 @@ export interface SoftureDatabaseConfig {
24
24
  */
25
25
  readonly handle?: () => MaybePromise<SoftureDatabaseHandle>;
26
26
  }
27
+ /** The app's origins besides `appOrigin` and how far the request's own host is trusted (core `resolveAppOrigin`). */
28
+ export interface SoftureOrigins {
29
+ /**
30
+ * Other origins the app is served under (an apex beside `app.`, a second domain): absolute URLs for a request sent
31
+ * to one of them stay on it. At most 16.
32
+ */
33
+ readonly trustedOrigins: readonly string[];
34
+ /**
35
+ * Trust the request's `Host` (and `X-Forwarded-Proto`) for any host, not only listed ones: for an image built once
36
+ * and served under origins nobody lists, behind a proxy that refuses hosts it does not serve. Default false.
37
+ */
38
+ readonly trustRequestHost: boolean;
39
+ }
40
+ export interface SoftureOriginsInput {
41
+ readonly trustedOrigins?: readonly string[];
42
+ readonly trustRequestHost?: boolean;
43
+ }
27
44
  export interface SoftureConfig {
28
45
  /**
29
46
  * `null` when the app has no database; required as soon as a module has a `dbSchema`. An empty `url`
@@ -35,6 +52,8 @@ export interface SoftureConfig {
35
52
  readonly timezone: string;
36
53
  /** Scheme, host and port of the app, without a path: `https://app.example.com`. */
37
54
  readonly appOrigin: string;
55
+ /** Every origin the app is served under besides `appOrigin`, read by `resolveAppOrigin`. */
56
+ readonly origins: SoftureOrigins;
38
57
  /** Enabled modules, in the order the app listed them. */
39
58
  readonly modules: readonly AnySoftureModule[];
40
59
  }
@@ -44,6 +63,7 @@ export interface SoftureConfigInput {
44
63
  readonly locale: Locale;
45
64
  readonly timezone: string;
46
65
  readonly appOrigin: string;
66
+ readonly origins?: SoftureOriginsInput;
47
67
  readonly modules: readonly AnySoftureModule[];
48
68
  }
49
69
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAGnD;;;GAGG;AAEH,MAAM,WAAW,0BAA0B;CAAG;AAE9C,iHAAiH;AACjH,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5H,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,YAAY,CAAC,qBAAqB,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AAED,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AAsBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAkB5E;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GACnC,MAAM,CAAC,gBAAgB,EAAE,EAAE,uBAAuB,CAAC,CAGrD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAGnD;;;GAGG;AAEH,MAAM,WAAW,0BAA0B;CAAG;AAE9C,iHAAiH;AACjH,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,OAAO,CAAC;AAE5H,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,YAAY,CAAC,qBAAqB,CAAC,CAAC;CAC7D;AAED,qHAAqH;AACrH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AAED,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC/C;AA8BD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,kBAAkB,GAAG,aAAa,CAmB5E;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEzF;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GACnC,MAAM,CAAC,gBAAgB,EAAE,EAAE,uBAAuB,CAAC,CAGrD"}
package/dist/config.js CHANGED
@@ -7,6 +7,7 @@ import { LOCALES } from "./i18n.js";
7
7
  import { err, ok } from "./result.js";
8
8
  import { parseVersionRange, satisfiesRange } from "./version-range.js";
9
9
  const CONFIG_SUBJECT = "softure.config (defineSoftureConfig)";
10
+ const ORIGIN_MESSAGE = "must be an http(s) origin without a path, e.g. https://app.example.com";
10
11
  const configSchema = z.object({
11
12
  // The URL is not checked for emptiness here: builds import the config without DATABASE_URL, so an empty URL is
12
13
  // refused when something connects (`createDatabase` in @softure-ai/db), not when the config is defined.
@@ -21,7 +22,13 @@ const configSchema = z.object({
21
22
  .default(null),
22
23
  locale: z.enum(LOCALES),
23
24
  timezone: z.string().refine(isTimeZone, "must be an IANA time zone, e.g. Europe/Warsaw"),
24
- appOrigin: z.string().refine(isOrigin, "must be an http(s) origin without a path, e.g. https://app.example.com"),
25
+ appOrigin: z.string().refine(isOrigin, ORIGIN_MESSAGE),
26
+ origins: z
27
+ .strictObject({
28
+ trustedOrigins: z.array(z.string().refine(isOrigin, ORIGIN_MESSAGE)).max(16, "takes at most 16 origins").default([]),
29
+ trustRequestHost: z.boolean().default(false),
30
+ })
31
+ .default({ trustedOrigins: [], trustRequestHost: false }),
25
32
  modules: z.array(z.custom(isSoftureModule, "must be a module returned by a module factory")),
26
33
  });
27
34
  /**
@@ -42,6 +49,7 @@ export function defineSoftureConfig(input) {
42
49
  return Object.freeze({
43
50
  ...config,
44
51
  database: config.database === null ? null : Object.freeze({ ...config.database }),
52
+ origins: Object.freeze({ ...config.origins, trustedOrigins: Object.freeze([...config.origins.trustedOrigins]) }),
45
53
  modules: Object.freeze([...config.modules]),
46
54
  });
47
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,11 +4,12 @@ export { formatMessage, getMessage, isLocale, LOCALES, mergeMessages, selectPlur
4
4
  export { coreMessages, type CoreMessages } from "./messages/index.js";
5
5
  export { err, ok, type Err, type ErrorCode, type Ok, type Result } from "./result.js";
6
6
  export { errorLogLabel, getPublicMessage, isPublicError, PublicError, safeError, type CoreErrorCode } from "./safe-error.js";
7
- export { defineSoftureConfig, getModule, sortModulesByDependencies, type SoftureConfig, type SoftureConfigInput, type SoftureDatabaseConfig, type SoftureDatabaseHandle, type SoftureDatabaseHandleTypes, } from "./config.js";
7
+ export { defineSoftureConfig, getModule, sortModulesByDependencies, type SoftureConfig, type SoftureConfigInput, type SoftureDatabaseConfig, type SoftureDatabaseHandle, type SoftureDatabaseHandleTypes, type SoftureOrigins, type SoftureOriginsInput, } from "./config.js";
8
8
  export { SoftureConfigError } from "./config-error.js";
9
9
  export { withDatabaseOptional } from "./database-requirement.js";
10
10
  export { moduleManifestSchema, type ModuleManifest } from "./manifest.js";
11
11
  export { defineModule, resolveMigrationsDir, toModuleJson, type AnySoftureModule, type HealthCheck, type ModuleContext, type ModuleFactory, type ModuleInput, type ModuleMigrations, type ModuleSpec, type PrivacyContributor, type SoftureModule, } from "./module.js";
12
+ export { getTrustedOrigins, parseOrigin, readForwardedProto, readRequestHost, readRequestOrigin, resolveAppOrigin, type OriginRequest, type ReadOriginOptions, type ResolveAppOriginOptions, } from "./origins.js";
12
13
  export { findSiteUrlProvider, getSiteUrls, type SiteUrlProvider, type SiteUrls } from "./site-urls.js";
13
14
  export { findSwitchReader, readSwitch, type SwitchReader, type SwitchReading } from "./switches.js";
14
15
  export { isVersion, parseVersionRange, satisfiesRange, type Version, type VersionRange } from "./version-range.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,GAChC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,WAAW,GACjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,EACzB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ export { SoftureConfigError } from "./config-error.js";
11
11
  export { withDatabaseOptional } from "./database-requirement.js";
12
12
  export { moduleManifestSchema } from "./manifest.js";
13
13
  export { defineModule, resolveMigrationsDir, toModuleJson, } from "./module.js";
14
+ export { getTrustedOrigins, parseOrigin, readForwardedProto, readRequestHost, readRequestOrigin, resolveAppOrigin, } from "./origins.js";
14
15
  export { findSiteUrlProvider, getSiteUrls } from "./site-urls.js";
15
16
  export { findSwitchReader, readSwitch } from "./switches.js";
16
17
  export { isVersion, parseVersionRange, satisfiesRange } from "./version-range.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,GAOb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAM1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,mGAAmG;AACnG,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,WAAW,EAA8B,MAAM,YAAY,CAAC;AACtF,OAAO,EACL,aAAa,EACb,UAAU,EACV,QAAQ,EACR,OAAO,EACP,aAAa,EACb,YAAY,GAOb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAqB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAkD,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAsB,MAAM,iBAAiB,CAAC;AAC7H,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,yBAAyB,GAQ1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAC1E,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,GAUb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GAIjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAuC,MAAM,gBAAgB,CAAC;AACvG,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAyC,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,cAAc,EAAmC,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { SoftureConfig } from "./config.js";
2
+ /** What the origin helpers read from a request: a Web `Request`, or Next's `headers()` with a URL. */
3
+ export interface OriginRequest {
4
+ readonly url: string;
5
+ readonly headers: {
6
+ get(name: string): string | null;
7
+ };
8
+ }
9
+ export interface ReadOriginOptions {
10
+ /** Read the first `X-Forwarded-Host` value before `Host`. Only for a result checked against a list of origins. */
11
+ readonly forwardedHost?: boolean;
12
+ }
13
+ export interface ResolveAppOriginOptions {
14
+ /** Origins trusted besides `appOrigin` and `config.origins.trustedOrigins`, e.g. a module's own option. */
15
+ readonly trustedOrigins?: readonly string[];
16
+ }
17
+ /**
18
+ * The normalized origin (`https://example.com`) when `value` is exactly `http(s)://host[:port]`, a trailing `/`
19
+ * allowed; null for anything else: a path, query, fragment, credentials, spaces or another scheme.
20
+ */
21
+ export declare function parseOrigin(value: string): string | null;
22
+ /**
23
+ * The host the request was sent to: the first `X-Forwarded-Host` value when `forwardedHost` is set, else the first
24
+ * `Host` value, else the URL's host; trimmed and lowercased.
25
+ */
26
+ export declare function readRequestHost(request: OriginRequest, options?: ReadOriginOptions): string;
27
+ /** The first `X-Forwarded-Proto` value when it is `http` or `https` (any case), else null. */
28
+ export declare function readForwardedProto(request: OriginRequest): "http" | "https" | null;
29
+ /**
30
+ * The origin the request was sent to: the first `X-Forwarded-Proto` value when it is http or https (else the URL's
31
+ * scheme) and `readRequestHost`. Null when the host is no host (a path, credentials or spaces in the header).
32
+ */
33
+ export declare function readRequestOrigin(request: OriginRequest, options?: ReadOriginOptions): string | null;
34
+ /**
35
+ * Every origin the app is served under: `appOrigin`, `config.origins.trustedOrigins`, then `extra`, without
36
+ * repeats. An `extra` entry that is not an http(s) origin is a setup bug and throws by name.
37
+ */
38
+ export declare function getTrustedOrigins(config: SoftureConfig, extra?: readonly string[]): readonly string[];
39
+ /**
40
+ * The app origin to build absolute URLs on for `request`:
41
+ * 1. the request's origin, read with `X-Forwarded-Host`, when it is one of `getTrustedOrigins`;
42
+ * 2. else, with `config.origins.trustRequestHost`, the request's origin read from `Host` (an image built once and
43
+ * served under other origins, behind a proxy that passes `Host` through and refuses hosts it does not serve);
44
+ * 3. else `config.appOrigin`.
45
+ */
46
+ export declare function resolveAppOrigin(config: SoftureConfig, request: OriginRequest, options?: ResolveAppOriginOptions): string;
47
+ //# sourceMappingURL=origins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../src/origins.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,sGAAsG;AACtG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,iBAAiB;IAChC,kHAAkH;IAClH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,2GAA2G;IAC3G,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASxD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAG/F;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAGlF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,GAAG,IAAI,CAGxG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,GAAE,SAAS,MAAM,EAAO,GAAG,SAAS,MAAM,EAAE,CAOzG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,GAAE,uBAA4B,GAAG,MAAM,CAK7H"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The normalized origin (`https://example.com`) when `value` is exactly `http(s)://host[:port]`, a trailing `/`
3
+ * allowed; null for anything else: a path, query, fragment, credentials, spaces or another scheme.
4
+ */
5
+ export function parseOrigin(value) {
6
+ const candidate = value.endsWith("/") ? value.slice(0, -1) : value;
7
+ // Checked on the raw text: `new URL` alone would accept and drop a path, a query or credentials.
8
+ if (!/^https?:\/\/[^/\\?#@\s]+$/i.test(candidate))
9
+ return null;
10
+ try {
11
+ return new URL(candidate).origin;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ /**
18
+ * The host the request was sent to: the first `X-Forwarded-Host` value when `forwardedHost` is set, else the first
19
+ * `Host` value, else the URL's host; trimmed and lowercased.
20
+ */
21
+ export function readRequestHost(request, options = {}) {
22
+ const forwarded = options.forwardedHost === true ? readFirstValue(request.headers.get("x-forwarded-host")) : null;
23
+ return forwarded ?? readFirstValue(request.headers.get("host")) ?? new URL(request.url).host;
24
+ }
25
+ /** The first `X-Forwarded-Proto` value when it is `http` or `https` (any case), else null. */
26
+ export function readForwardedProto(request) {
27
+ const proto = readFirstValue(request.headers.get("x-forwarded-proto"));
28
+ return proto === "http" || proto === "https" ? proto : null;
29
+ }
30
+ /**
31
+ * The origin the request was sent to: the first `X-Forwarded-Proto` value when it is http or https (else the URL's
32
+ * scheme) and `readRequestHost`. Null when the host is no host (a path, credentials or spaces in the header).
33
+ */
34
+ export function readRequestOrigin(request, options = {}) {
35
+ const scheme = readForwardedProto(request) ?? new URL(request.url).protocol.slice(0, -1);
36
+ return parseOrigin(`${scheme}://${readRequestHost(request, options)}`);
37
+ }
38
+ /**
39
+ * Every origin the app is served under: `appOrigin`, `config.origins.trustedOrigins`, then `extra`, without
40
+ * repeats. An `extra` entry that is not an http(s) origin is a setup bug and throws by name.
41
+ */
42
+ export function getTrustedOrigins(config, extra = []) {
43
+ const listed = extra.map((entry) => {
44
+ const origin = parseOrigin(entry);
45
+ if (origin === null)
46
+ throw new Error(`@softure-ai/core: trusted origin "${entry.slice(0, 100)}" must be an http(s) origin such as https://example.com`);
47
+ return origin;
48
+ });
49
+ return [...new Set([config.appOrigin, ...config.origins.trustedOrigins, ...listed])];
50
+ }
51
+ /**
52
+ * The app origin to build absolute URLs on for `request`:
53
+ * 1. the request's origin, read with `X-Forwarded-Host`, when it is one of `getTrustedOrigins`;
54
+ * 2. else, with `config.origins.trustRequestHost`, the request's origin read from `Host` (an image built once and
55
+ * served under other origins, behind a proxy that passes `Host` through and refuses hosts it does not serve);
56
+ * 3. else `config.appOrigin`.
57
+ */
58
+ export function resolveAppOrigin(config, request, options = {}) {
59
+ const forwarded = readRequestOrigin(request, { forwardedHost: true });
60
+ if (forwarded !== null && getTrustedOrigins(config, options.trustedOrigins).includes(forwarded))
61
+ return forwarded;
62
+ const requested = config.origins.trustRequestHost ? readRequestOrigin(request) : null;
63
+ return requested ?? config.appOrigin;
64
+ }
65
+ function readFirstValue(header) {
66
+ const value = header?.split(",")[0]?.trim().toLowerCase();
67
+ return value === undefined || value === "" ? null : value;
68
+ }
69
+ //# sourceMappingURL=origins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origins.js","sourceRoot":"","sources":["../src/origins.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "The SOFTURE AI module contract: app config, module definitions, Result, Clock and messages.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/config.ts CHANGED
@@ -33,6 +33,25 @@ export interface SoftureDatabaseConfig {
33
33
  readonly handle?: () => MaybePromise<SoftureDatabaseHandle>;
34
34
  }
35
35
 
36
+ /** The app's origins besides `appOrigin` and how far the request's own host is trusted (core `resolveAppOrigin`). */
37
+ export interface SoftureOrigins {
38
+ /**
39
+ * Other origins the app is served under (an apex beside `app.`, a second domain): absolute URLs for a request sent
40
+ * to one of them stay on it. At most 16.
41
+ */
42
+ readonly trustedOrigins: readonly string[];
43
+ /**
44
+ * Trust the request's `Host` (and `X-Forwarded-Proto`) for any host, not only listed ones: for an image built once
45
+ * and served under origins nobody lists, behind a proxy that refuses hosts it does not serve. Default false.
46
+ */
47
+ readonly trustRequestHost: boolean;
48
+ }
49
+
50
+ export interface SoftureOriginsInput {
51
+ readonly trustedOrigins?: readonly string[];
52
+ readonly trustRequestHost?: boolean;
53
+ }
54
+
36
55
  export interface SoftureConfig {
37
56
  /**
38
57
  * `null` when the app has no database; required as soon as a module has a `dbSchema`. An empty `url`
@@ -44,6 +63,8 @@ export interface SoftureConfig {
44
63
  readonly timezone: string;
45
64
  /** Scheme, host and port of the app, without a path: `https://app.example.com`. */
46
65
  readonly appOrigin: string;
66
+ /** Every origin the app is served under besides `appOrigin`, read by `resolveAppOrigin`. */
67
+ readonly origins: SoftureOrigins;
47
68
  /** Enabled modules, in the order the app listed them. */
48
69
  readonly modules: readonly AnySoftureModule[];
49
70
  }
@@ -54,11 +75,14 @@ export interface SoftureConfigInput {
54
75
  readonly locale: Locale;
55
76
  readonly timezone: string;
56
77
  readonly appOrigin: string;
78
+ readonly origins?: SoftureOriginsInput;
57
79
  readonly modules: readonly AnySoftureModule[];
58
80
  }
59
81
 
60
82
  const CONFIG_SUBJECT = "softure.config (defineSoftureConfig)";
61
83
 
84
+ const ORIGIN_MESSAGE = "must be an http(s) origin without a path, e.g. https://app.example.com";
85
+
62
86
  const configSchema = z.object({
63
87
  // The URL is not checked for emptiness here: builds import the config without DATABASE_URL, so an empty URL is
64
88
  // refused when something connects (`createDatabase` in @softure-ai/db), not when the config is defined.
@@ -73,7 +97,13 @@ const configSchema = z.object({
73
97
  .default(null),
74
98
  locale: z.enum(LOCALES),
75
99
  timezone: z.string().refine(isTimeZone, "must be an IANA time zone, e.g. Europe/Warsaw"),
76
- appOrigin: z.string().refine(isOrigin, "must be an http(s) origin without a path, e.g. https://app.example.com"),
100
+ appOrigin: z.string().refine(isOrigin, ORIGIN_MESSAGE),
101
+ origins: z
102
+ .strictObject({
103
+ trustedOrigins: z.array(z.string().refine(isOrigin, ORIGIN_MESSAGE)).max(16, "takes at most 16 origins").default([]),
104
+ trustRequestHost: z.boolean().default(false),
105
+ })
106
+ .default({ trustedOrigins: [], trustRequestHost: false }),
77
107
  modules: z.array(z.custom<AnySoftureModule>(isSoftureModule, "must be a module returned by a module factory")),
78
108
  });
79
109
 
@@ -97,6 +127,7 @@ export function defineSoftureConfig(input: SoftureConfigInput): SoftureConfig {
97
127
  return Object.freeze({
98
128
  ...config,
99
129
  database: config.database === null ? null : Object.freeze({ ...config.database }),
130
+ origins: Object.freeze({ ...config.origins, trustedOrigins: Object.freeze([...config.origins.trustedOrigins]) }),
100
131
  modules: Object.freeze([...config.modules]),
101
132
  });
102
133
  }
package/src/index.ts CHANGED
@@ -28,6 +28,8 @@ export {
28
28
  type SoftureDatabaseConfig,
29
29
  type SoftureDatabaseHandle,
30
30
  type SoftureDatabaseHandleTypes,
31
+ type SoftureOrigins,
32
+ type SoftureOriginsInput,
31
33
  } from "./config.js";
32
34
  export { SoftureConfigError } from "./config-error.js";
33
35
  export { withDatabaseOptional } from "./database-requirement.js";
@@ -46,6 +48,17 @@ export {
46
48
  type PrivacyContributor,
47
49
  type SoftureModule,
48
50
  } from "./module.js";
51
+ export {
52
+ getTrustedOrigins,
53
+ parseOrigin,
54
+ readForwardedProto,
55
+ readRequestHost,
56
+ readRequestOrigin,
57
+ resolveAppOrigin,
58
+ type OriginRequest,
59
+ type ReadOriginOptions,
60
+ type ResolveAppOriginOptions,
61
+ } from "./origins.js";
49
62
  export { findSiteUrlProvider, getSiteUrls, type SiteUrlProvider, type SiteUrls } from "./site-urls.js";
50
63
  export { findSwitchReader, readSwitch, type SwitchReader, type SwitchReading } from "./switches.js";
51
64
  export { isVersion, parseVersionRange, satisfiesRange, type Version, type VersionRange } from "./version-range.js";
package/src/origins.ts ADDED
@@ -0,0 +1,92 @@
1
+ // The public origin of a request, one rule for every module (issue #311). Behind a proxy `request.url` names the
2
+ // server's own listening address (`http://0.0.0.0:3000`), so the origin is read from headers: the scheme from the
3
+ // first `X-Forwarded-Proto` value when it is http or https, the host from `Host`. `X-Forwarded-Host` is read only
4
+ // where a list of the app's origins gates the result: a client can send it through a proxy that keeps it.
5
+ import type { SoftureConfig } from "./config.js";
6
+
7
+ /** What the origin helpers read from a request: a Web `Request`, or Next's `headers()` with a URL. */
8
+ export interface OriginRequest {
9
+ readonly url: string;
10
+ readonly headers: { get(name: string): string | null };
11
+ }
12
+
13
+ export interface ReadOriginOptions {
14
+ /** Read the first `X-Forwarded-Host` value before `Host`. Only for a result checked against a list of origins. */
15
+ readonly forwardedHost?: boolean;
16
+ }
17
+
18
+ export interface ResolveAppOriginOptions {
19
+ /** Origins trusted besides `appOrigin` and `config.origins.trustedOrigins`, e.g. a module's own option. */
20
+ readonly trustedOrigins?: readonly string[];
21
+ }
22
+
23
+ /**
24
+ * The normalized origin (`https://example.com`) when `value` is exactly `http(s)://host[:port]`, a trailing `/`
25
+ * allowed; null for anything else: a path, query, fragment, credentials, spaces or another scheme.
26
+ */
27
+ export function parseOrigin(value: string): string | null {
28
+ const candidate = value.endsWith("/") ? value.slice(0, -1) : value;
29
+ // Checked on the raw text: `new URL` alone would accept and drop a path, a query or credentials.
30
+ if (!/^https?:\/\/[^/\\?#@\s]+$/i.test(candidate)) return null;
31
+ try {
32
+ return new URL(candidate).origin;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * The host the request was sent to: the first `X-Forwarded-Host` value when `forwardedHost` is set, else the first
40
+ * `Host` value, else the URL's host; trimmed and lowercased.
41
+ */
42
+ export function readRequestHost(request: OriginRequest, options: ReadOriginOptions = {}): string {
43
+ const forwarded = options.forwardedHost === true ? readFirstValue(request.headers.get("x-forwarded-host")) : null;
44
+ return forwarded ?? readFirstValue(request.headers.get("host")) ?? new URL(request.url).host;
45
+ }
46
+
47
+ /** The first `X-Forwarded-Proto` value when it is `http` or `https` (any case), else null. */
48
+ export function readForwardedProto(request: OriginRequest): "http" | "https" | null {
49
+ const proto = readFirstValue(request.headers.get("x-forwarded-proto"));
50
+ return proto === "http" || proto === "https" ? proto : null;
51
+ }
52
+
53
+ /**
54
+ * The origin the request was sent to: the first `X-Forwarded-Proto` value when it is http or https (else the URL's
55
+ * scheme) and `readRequestHost`. Null when the host is no host (a path, credentials or spaces in the header).
56
+ */
57
+ export function readRequestOrigin(request: OriginRequest, options: ReadOriginOptions = {}): string | null {
58
+ const scheme = readForwardedProto(request) ?? new URL(request.url).protocol.slice(0, -1);
59
+ return parseOrigin(`${scheme}://${readRequestHost(request, options)}`);
60
+ }
61
+
62
+ /**
63
+ * Every origin the app is served under: `appOrigin`, `config.origins.trustedOrigins`, then `extra`, without
64
+ * repeats. An `extra` entry that is not an http(s) origin is a setup bug and throws by name.
65
+ */
66
+ export function getTrustedOrigins(config: SoftureConfig, extra: readonly string[] = []): readonly string[] {
67
+ const listed = extra.map((entry) => {
68
+ const origin = parseOrigin(entry);
69
+ if (origin === null) throw new Error(`@softure-ai/core: trusted origin "${entry.slice(0, 100)}" must be an http(s) origin such as https://example.com`);
70
+ return origin;
71
+ });
72
+ return [...new Set([config.appOrigin, ...config.origins.trustedOrigins, ...listed])];
73
+ }
74
+
75
+ /**
76
+ * The app origin to build absolute URLs on for `request`:
77
+ * 1. the request's origin, read with `X-Forwarded-Host`, when it is one of `getTrustedOrigins`;
78
+ * 2. else, with `config.origins.trustRequestHost`, the request's origin read from `Host` (an image built once and
79
+ * served under other origins, behind a proxy that passes `Host` through and refuses hosts it does not serve);
80
+ * 3. else `config.appOrigin`.
81
+ */
82
+ export function resolveAppOrigin(config: SoftureConfig, request: OriginRequest, options: ResolveAppOriginOptions = {}): string {
83
+ const forwarded = readRequestOrigin(request, { forwardedHost: true });
84
+ if (forwarded !== null && getTrustedOrigins(config, options.trustedOrigins).includes(forwarded)) return forwarded;
85
+ const requested = config.origins.trustRequestHost ? readRequestOrigin(request) : null;
86
+ return requested ?? config.appOrigin;
87
+ }
88
+
89
+ function readFirstValue(header: string | null): string | null {
90
+ const value = header?.split(",")[0]?.trim().toLowerCase();
91
+ return value === undefined || value === "" ? null : value;
92
+ }