@server/next 0.33.0 → 0.34.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.
Files changed (3) hide show
  1. package/index.d.ts +15 -2
  2. package/index.js +63 -21
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import * as http from 'http';
2
+
1
3
  type LimitOptions = {
2
4
  maxSize?: number | string;
3
5
  minSize?: number | string;
@@ -129,9 +131,20 @@ type Logger = {
129
131
  };
130
132
  type SecurityOptions = {
131
133
  trustProxy?: boolean;
134
+ frameguard?: boolean | string;
135
+ noSniff?: boolean;
136
+ referrerPolicy?: boolean | string;
137
+ hsts?: boolean | string;
138
+ xssProtection?: boolean;
139
+ csp?: boolean | string;
140
+ coop?: boolean | string;
141
+ corp?: boolean | string;
142
+ permissionsPolicy?: string;
132
143
  };
133
144
  type SecuritySettings = {
134
145
  trustProxy: boolean;
146
+ headers: Record<string, string>;
147
+ hsts: string | null;
135
148
  };
136
149
  type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
137
150
  type Options = {
@@ -150,7 +163,7 @@ type Options = {
150
163
  onError?: OnError;
151
164
  log?: LogLevel | boolean;
152
165
  favicon?: string | Bucket;
153
- security?: SecurityOptions;
166
+ security?: boolean | SecurityOptions;
154
167
  body?: BodyMode;
155
168
  };
156
169
  type Settings = {
@@ -332,7 +345,7 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
332
345
  port?: number;
333
346
  constructor(options?: Options);
334
347
  self(): this;
335
- node(): Promise<void>;
348
+ node(): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
336
349
  fetch(request: Request, env?: BunEnv): Promise<Response>;
337
350
  callback(request: Request, context: unknown): Promise<Response>;
338
351
  test(): {
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
  }
@@ -2484,25 +2525,26 @@ var Winter = async (app, request, env2) => {
2484
2525
  };
2485
2526
  var Node = async (app) => {
2486
2527
  const http = await import("http");
2487
- http.createServer(async (request, response) => {
2488
- const ctx = await createNode(request, app);
2489
- if ("error" in ctx) throw ctx.error;
2490
- const out = await handleRequest(app, ctx);
2491
- response.writeHead(out.status || 200, parseHeaders_default(out.headers));
2492
- if (out.body instanceof ReadableStream) {
2493
- await iterate(out.body, (chunk) => response.write(chunk));
2494
- } else {
2495
- response.write(out.body || "");
2528
+ const { attachWebsocket } = await import("./wsNode-GEJUCJQ7.js");
2529
+ const server2 = http.createServer(
2530
+ async (request, response) => {
2531
+ const ctx = await createNode(request, app);
2532
+ if ("error" in ctx) throw ctx.error;
2533
+ const out = await handleRequest(app, ctx);
2534
+ response.writeHead(out.status || 200, parseHeaders_default(out.headers));
2535
+ if (out.body instanceof ReadableStream) {
2536
+ await iterate(out.body, (chunk) => response.write(chunk));
2537
+ } else {
2538
+ response.write(out.body || "");
2539
+ }
2540
+ response.end();
2496
2541
  }
2497
- response.end();
2498
- }).listen(app.settings.port, () => {
2542
+ );
2543
+ attachWebsocket(server2, app);
2544
+ server2.listen(app.settings.port, () => {
2499
2545
  app.settings.log.start(`http://localhost:${app.settings.port}/`);
2500
- if (app.handlers.socket.length) {
2501
- console.warn(
2502
- "[server] WebSockets (.socket()) are only supported on Bun, not Node"
2503
- );
2504
- }
2505
2546
  });
2547
+ return server2;
2506
2548
  };
2507
2549
  var Netlify = async (app, request, context) => {
2508
2550
  request.context = context;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.33.0",
3
+ "version": "0.34.1",
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",