@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 +1 -1
- package/docs/auth.md +10 -2
- package/docs/getting-started.md +1 -1
- package/package.json +1 -1
- package/src/server/index.ts +1 -0
- package/src/server/runtime-config.ts +26 -1
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
|
|
78
|
-
policy-controlled for
|
|
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.
|
package/docs/getting-started.md
CHANGED
|
@@ -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
|
|
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
package/src/server/index.ts
CHANGED
|
@@ -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
|
|
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 },
|