@pablozaiden/webapp 0.6.0 → 0.6.1

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/README.md CHANGED
@@ -22,7 +22,7 @@ Each application package must declare `react` and `react-dom`; they are peer dep
22
22
 
23
23
  | Export | Use |
24
24
  | --- | --- |
25
- | `@pablozaiden/webapp/server` | `createWebAppServer`, route helpers, responses, SQLite store |
25
+ | `@pablozaiden/webapp/server` | `createWebAppServer`, route helpers, responses, request-origin helpers, SQLite store |
26
26
  | `@pablozaiden/webapp/web` | `WebAppRoot`, `renderWebApp`, sidebar types, UI controls, realtime hooks |
27
27
  | `@pablozaiden/webapp/contracts` | Shared auth/config/device/API-key types |
28
28
  | `@pablozaiden/webapp/cli` | One-binary command helpers, device/environment auth credentials and generic API CLI caller |
package/docs/auth.md CHANGED
@@ -74,8 +74,9 @@ The same normalized request context drives expected same-origin origins,
74
74
  WebSocket origin checks, passkey expected origins and RP IDs, the `Secure`
75
75
  cookie attribute, cookie paths, setup links, and device/discovery URLs.
76
76
  `{PREFIX}_PUBLIC_BASE_URL` is authoritative for origin, hostname, and secure
77
- state even when forwarded headers are present; trusted prefixes remain
78
- policy-controlled for path-bearing cookies and links.
77
+ state even when forwarded headers are present. It must be an origin-only
78
+ absolute `http` or `https` URL; trusted prefixes remain policy-controlled for
79
+ path-bearing cookies and links.
79
80
 
80
81
  Trust mode is a deployment boundary setting, not a way to trust arbitrary
81
82
  client input. The reverse proxy must strip client-supplied forwarded headers
@@ -84,3 +85,10 @@ single-value headers. Do not expose the application directly to untrusted
84
85
  clients when trust mode is enabled. This release does not implement a
85
86
  proxy-address allowlist, so the network boundary and proxy sanitization are
86
87
  required.
88
+
89
+ Applications that generate their own public URLs should use
90
+ `getRequestOriginInfo` or `getRequestBaseUrl` from
91
+ `@pablozaiden/webapp/server` with the server's runtime config instead of
92
+ parsing `{PREFIX}_PUBLIC_BASE_URL` independently. The framework validates the
93
+ configured public base URL during startup and applies the same trusted-proxy
94
+ policy used by its authentication and origin checks.
@@ -186,7 +186,7 @@ The app configures an uppercase `envPrefix`; the framework reads only variables
186
186
  | `{PREFIX}_TRUST_PROXY` | `false` | Explicitly trust the documented `X-Forwarded-*` headers; keep disabled for direct deployments |
187
187
  | `{PREFIX}_TRUST_PROXY_HEADERS` | `proto,host,prefix` when enabled | Comma-separated subset of supported forwarded values: `proto`, `host`, `prefix` |
188
188
  | `{PREFIX}_TRUST_PROXY_CHAIN` | `first` | Select the left-most (`first`) or right-most (`last`) non-empty value from comma-separated forwarded headers |
189
- | `{PREFIX}_PUBLIC_BASE_URL` | request origin | Authoritative external origin for auth/device URLs; it overrides forwarded protocol and host values |
189
+ | `{PREFIX}_PUBLIC_BASE_URL` | request origin | Authoritative absolute `http` or `https` origin (without a path, query, or fragment) for auth/device URLs; it overrides forwarded protocol and host values |
190
190
  | `{PREFIX}_AUTH_ISSUER` | `urn:{prefix}:webapp` | JWT issuer override |
191
191
 
192
192
  The configured data directory contains the framework-owned
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablozaiden/webapp",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Opinionated Bun + React webapp framework for single-server apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -3,6 +3,7 @@ export * from "./routes";
3
3
  export * from "./route-catalog";
4
4
  export * from "./responses";
5
5
  export * from "./create-web-app-server";
6
+ export { getRequestBaseUrl, getRequestOriginInfo, type RequestOriginInfo } from "./auth/request-origin";
6
7
  export * from "./auth/store";
7
8
  export * from "./auth/sqlite-store";
8
9
  export * from "./realtime/bus";
@@ -84,6 +84,30 @@ function parseBoolean(raw: string | undefined, fallback: boolean, name: string):
84
84
  throw new Error(`${name} must be true or false; received "${raw}"`);
85
85
  }
86
86
 
87
+ function parsePublicBaseUrl(raw: string | undefined, name: string): string | undefined {
88
+ if (!raw) {
89
+ return undefined;
90
+ }
91
+ let parsed: URL;
92
+ try {
93
+ parsed = new URL(raw);
94
+ } catch {
95
+ throw new Error(`${name} must be a valid absolute http(s) origin; received "${raw}"`);
96
+ }
97
+ if (
98
+ (parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
99
+ parsed.username ||
100
+ parsed.password ||
101
+ parsed.origin === "null" ||
102
+ parsed.pathname !== "/" ||
103
+ parsed.search ||
104
+ parsed.hash
105
+ ) {
106
+ throw new Error(`${name} must be a valid absolute http(s) origin; received "${raw}"`);
107
+ }
108
+ return parsed.origin;
109
+ }
110
+
87
111
  function parseTrustProxyHeaders(raw: string | undefined, enabled: boolean, name: string): TrustProxyHeader[] {
88
112
  if (raw === undefined) {
89
113
  return enabled ? [...TRUST_PROXY_HEADERS] : [];
@@ -122,6 +146,7 @@ export function readRuntimeConfig(input: {
122
146
  const envPrefix = assertEnvPrefix(input.envPrefix);
123
147
  const logLevelRaw = readEnv(envPrefix, "LOG_LEVEL");
124
148
  const logLevel = parseLogLevel(logLevelRaw, input.defaultLogLevel ?? "info", envName(envPrefix, "LOG_LEVEL"));
149
+ const publicBaseUrl = parsePublicBaseUrl(readEnv(envPrefix, "PUBLIC_BASE_URL"), envName(envPrefix, "PUBLIC_BASE_URL"));
125
150
  const trustProxyEnabled = parseBoolean(readEnv(envPrefix, "TRUST_PROXY"), false, envName(envPrefix, "TRUST_PROXY"));
126
151
  const trustProxy = {
127
152
  enabled: trustProxyEnabled,
@@ -138,7 +163,7 @@ export function readRuntimeConfig(input: {
138
163
  logLevelFromEnv: Boolean(logLevelRaw),
139
164
  passkeyDisabled: isTruthyEnv(readEnv(envPrefix, "DISABLE_PASSKEY")),
140
165
  sameOriginDisabled: isTruthyEnv(readEnv(envPrefix, "DISABLE_SAME_ORIGIN_CHECK")),
141
- publicBaseUrl: readEnv(envPrefix, "PUBLIC_BASE_URL"),
166
+ publicBaseUrl,
142
167
  authIssuer: readEnv(envPrefix, "AUTH_ISSUER"),
143
168
  trustProxy,
144
169
  development: process.env["NODE_ENV"] === "production" ? false : { hmr: true, console: true },