@server/next 0.33.0 → 0.34.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.
Files changed (3) hide show
  1. package/index.d.ts +12 -1
  2. package/index.js +46 -5
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -129,9 +129,20 @@ type Logger = {
129
129
  };
130
130
  type SecurityOptions = {
131
131
  trustProxy?: boolean;
132
+ frameguard?: boolean | string;
133
+ noSniff?: boolean;
134
+ referrerPolicy?: boolean | string;
135
+ hsts?: boolean | string;
136
+ xssProtection?: boolean;
137
+ csp?: boolean | string;
138
+ coop?: boolean | string;
139
+ corp?: boolean | string;
140
+ permissionsPolicy?: string;
132
141
  };
133
142
  type SecuritySettings = {
134
143
  trustProxy: boolean;
144
+ headers: Record<string, string>;
145
+ hsts: string | null;
135
146
  };
136
147
  type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
137
148
  type Options = {
@@ -150,7 +161,7 @@ type Options = {
150
161
  onError?: OnError;
151
162
  log?: LogLevel | boolean;
152
163
  favicon?: string | Bucket;
153
- security?: SecurityOptions;
164
+ security?: boolean | SecurityOptions;
154
165
  body?: BodyMode;
155
166
  };
156
167
  type Settings = {
package/index.js CHANGED
@@ -1358,6 +1358,47 @@ function createLogger(level) {
1358
1358
  };
1359
1359
  }
1360
1360
 
1361
+ // src/helpers/security.ts
1362
+ function resolveSecurity(security) {
1363
+ const off = security === false;
1364
+ const o = security && typeof security === "object" ? security : {};
1365
+ const val = (v, def) => v === false ? null : v === true || v == null ? def : v;
1366
+ const map2 = off ? {} : {
1367
+ "x-frame-options": val(o.frameguard, "SAMEORIGIN"),
1368
+ "x-content-type-options": o.noSniff === false ? null : "nosniff",
1369
+ "referrer-policy": val(
1370
+ o.referrerPolicy,
1371
+ "strict-origin-when-cross-origin"
1372
+ ),
1373
+ "x-xss-protection": o.xssProtection === false ? null : "0",
1374
+ // Opt-in: default off
1375
+ "content-security-policy": val(o.csp, null),
1376
+ "cross-origin-opener-policy": val(o.coop, null),
1377
+ "cross-origin-resource-policy": val(o.corp, null),
1378
+ "permissions-policy": o.permissionsPolicy ?? null
1379
+ };
1380
+ const headers2 = {};
1381
+ for (const key in map2) {
1382
+ const value = map2[key];
1383
+ if (value) headers2[key] = value;
1384
+ }
1385
+ return {
1386
+ trustProxy: o.trustProxy ?? true,
1387
+ headers: headers2,
1388
+ hsts: off ? null : val(o.hsts, "max-age=15552000; includeSubDomains")
1389
+ };
1390
+ }
1391
+ function applySecurity(res, ctx) {
1392
+ const security = ctx.options.security;
1393
+ if (!security) return;
1394
+ for (const key in security.headers) {
1395
+ if (!res.headers.has(key)) res.headers.set(key, security.headers[key]);
1396
+ }
1397
+ if (security.hsts && ctx.platform.production && !res.headers.has("strict-transport-security")) {
1398
+ res.headers.set("strict-transport-security", security.hsts);
1399
+ }
1400
+ }
1401
+
1361
1402
  // src/helpers/config.ts
1362
1403
  function config(options = {}) {
1363
1404
  const env2 = globalThis.env;
@@ -1371,11 +1412,9 @@ function config(options = {}) {
1371
1412
  // How request bodies are read: parsed into ctx.body by default; `raw` keeps
1372
1413
  // the Buffer, `stream` hands the handler the unread web ReadableStream.
1373
1414
  body: options.body ?? "parse",
1374
- // Trust X-Forwarded-* headers for ctx.ip (on by default; set it to false
1375
- // when clients connect directly so a client can't spoof its IP).
1376
- security: {
1377
- trustProxy: options.security?.trustProxy ?? true
1378
- }
1415
+ // Secure-by-default response headers + trustProxy for ctx.ip. `false` turns
1416
+ // the added headers off; see resolveSecurity for the defaults.
1417
+ security: resolveSecurity(options.security)
1379
1418
  };
1380
1419
  options.cors = options.cors || env2.CORS || null;
1381
1420
  if (options.cors) {
@@ -1600,6 +1639,7 @@ async function parseResponse(out, ctx) {
1600
1639
  throw new Error(`Invalid response type ${out}`);
1601
1640
  }
1602
1641
  applyCors(out, ctx);
1642
+ applySecurity(out, ctx);
1603
1643
  if (ctx.time?.times?.length > 1) {
1604
1644
  out.headers.set("Server-Timing", ctx.time.headers());
1605
1645
  }
@@ -1756,6 +1796,7 @@ async function getResponse(app, ctx) {
1756
1796
  } catch (error) {
1757
1797
  const res = await ctx.options.onError(error, ctx);
1758
1798
  applyCors(res, ctx);
1799
+ applySecurity(res, ctx);
1759
1800
  return res;
1760
1801
  }
1761
1802
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
5
5
  "homepage": "https://server-js.com/",
6
6
  "repository": "github:franciscop/server-next",