tezx 2.0.11 → 3.0.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.
- package/README.md +122 -89
- package/bun/getConnInfo.d.ts +21 -0
- package/bun/getConnInfo.js +9 -0
- package/bun/index.d.ts +10 -4
- package/bun/index.js +8 -4
- package/bun/ws.d.ts +48 -0
- package/bun/ws.js +58 -0
- package/cjs/bun/getConnInfo.js +12 -0
- package/cjs/bun/index.js +35 -7
- package/cjs/bun/ws.js +63 -0
- package/cjs/core/config.js +2 -12
- package/cjs/core/context.js +131 -379
- package/cjs/core/error.js +49 -0
- package/cjs/core/request.js +79 -131
- package/cjs/core/router.js +54 -387
- package/cjs/core/server.js +83 -202
- package/cjs/deno/env.js +4 -4
- package/cjs/deno/getConnInfo.js +18 -0
- package/cjs/deno/index.js +11 -18
- package/cjs/deno/serveStatic.js +53 -0
- package/cjs/deno/ws.js +39 -0
- package/cjs/helper/index.js +46 -10
- package/cjs/index.js +5 -7
- package/cjs/jwt/node.js +94 -0
- package/cjs/jwt/web.js +178 -0
- package/cjs/middleware/basic-auth.js +42 -0
- package/cjs/middleware/bearer-auth.js +34 -0
- package/cjs/middleware/cache-control.js +44 -0
- package/cjs/middleware/cors.js +11 -21
- package/cjs/middleware/detect-bot.js +57 -0
- package/cjs/middleware/i18n.js +73 -60
- package/cjs/middleware/index.js +8 -46
- package/cjs/middleware/logger.js +9 -4
- package/cjs/middleware/pagination.js +3 -2
- package/cjs/middleware/powered-by.js +3 -2
- package/cjs/middleware/rate-limiter.js +38 -0
- package/cjs/middleware/request-id.js +4 -5
- package/cjs/middleware/sanitize-headers.js +22 -0
- package/cjs/middleware/secure-headers copy.js +143 -0
- package/cjs/middleware/secure-headers.js +157 -0
- package/cjs/middleware/{xssProtection.js → xss-protection.js} +5 -8
- package/cjs/node/env.js +7 -7
- package/cjs/node/getConnInfo.js +16 -0
- package/cjs/node/index.js +17 -18
- package/cjs/node/mount-node.js +59 -0
- package/cjs/node/serveStatic.js +56 -0
- package/cjs/node/toWebRequest.js +25 -0
- package/cjs/node/ws.js +82 -0
- package/cjs/registry/RadixRouter.js +148 -0
- package/cjs/registry/index.js +17 -0
- package/cjs/types/headers.js +2 -0
- package/cjs/types/index.js +13 -0
- package/cjs/utils/buffer.js +17 -0
- package/cjs/utils/colors.js +2 -0
- package/cjs/utils/cookie.js +59 -0
- package/cjs/utils/file.js +136 -0
- package/cjs/utils/formData.js +60 -10
- package/cjs/utils/generateID.js +37 -0
- package/cjs/utils/low-level.js +115 -0
- package/cjs/utils/{staticFile.js → mimeTypes.js} +0 -87
- package/cjs/utils/rateLimit.js +41 -0
- package/cjs/utils/response.js +65 -0
- package/cjs/{core/environment.js → utils/runtime.js} +2 -1
- package/cjs/utils/url.js +65 -30
- package/core/config.d.ts +2 -7
- package/core/config.js +2 -12
- package/core/context.d.ts +209 -164
- package/core/context.js +131 -346
- package/core/error.d.ts +96 -0
- package/core/error.js +44 -0
- package/core/request.d.ts +67 -107
- package/core/request.js +78 -130
- package/core/router.d.ts +138 -133
- package/core/router.js +53 -352
- package/core/server.d.ts +99 -38
- package/core/server.js +83 -202
- package/deno/env.js +3 -3
- package/deno/getConnInfo.d.ts +21 -0
- package/deno/getConnInfo.js +15 -0
- package/deno/index.d.ts +9 -4
- package/deno/index.js +7 -4
- package/deno/serveStatic.d.ts +28 -0
- package/deno/serveStatic.js +49 -0
- package/deno/ws.d.ts +42 -0
- package/deno/ws.js +36 -0
- package/helper/index.d.ts +29 -15
- package/helper/index.js +27 -7
- package/index.d.ts +10 -8
- package/index.js +4 -5
- package/jwt/node.d.ts +39 -0
- package/jwt/node.js +87 -0
- package/jwt/web.d.ts +14 -0
- package/jwt/web.js +174 -0
- package/middleware/basic-auth.d.ts +56 -0
- package/middleware/basic-auth.js +38 -0
- package/middleware/bearer-auth.d.ts +53 -0
- package/middleware/bearer-auth.js +30 -0
- package/middleware/cache-control.d.ts +30 -0
- package/middleware/cache-control.js +40 -0
- package/middleware/cors.d.ts +30 -3
- package/middleware/cors.js +12 -22
- package/middleware/detect-bot.d.ts +113 -0
- package/middleware/detect-bot.js +53 -0
- package/middleware/i18n.d.ts +166 -73
- package/middleware/i18n.js +73 -60
- package/middleware/index.d.ts +8 -32
- package/middleware/index.js +8 -44
- package/middleware/logger.d.ts +5 -2
- package/middleware/logger.js +9 -4
- package/middleware/pagination.d.ts +9 -6
- package/middleware/pagination.js +3 -2
- package/middleware/powered-by.d.ts +2 -1
- package/middleware/powered-by.js +3 -2
- package/middleware/{rateLimiter.d.ts → rate-limiter.d.ts} +15 -9
- package/middleware/rate-limiter.js +34 -0
- package/middleware/request-id.d.ts +2 -1
- package/middleware/request-id.js +5 -6
- package/middleware/{sanitizeHeader.d.ts → sanitize-headers.d.ts} +5 -19
- package/middleware/sanitize-headers.js +18 -0
- package/middleware/secure-headers copy.d.ts +15 -0
- package/middleware/secure-headers copy.js +136 -0
- package/middleware/secure-headers.d.ts +132 -0
- package/middleware/secure-headers.js +153 -0
- package/middleware/{xssProtection.d.ts → xss-protection.d.ts} +2 -1
- package/middleware/xss-protection.js +19 -0
- package/node/env.js +4 -4
- package/node/getConnInfo.d.ts +21 -0
- package/node/getConnInfo.js +13 -0
- package/node/index.d.ts +13 -4
- package/node/index.js +11 -4
- package/node/mount-node.d.ts +11 -0
- package/node/mount-node.js +56 -0
- package/node/serveStatic.d.ts +36 -0
- package/node/serveStatic.js +52 -0
- package/node/toWebRequest.js +22 -0
- package/node/ws.d.ts +56 -0
- package/node/ws.js +46 -0
- package/package.json +39 -30
- package/registry/RadixRouter.d.ts +40 -0
- package/registry/RadixRouter.js +144 -0
- package/registry/index.d.ts +2 -0
- package/registry/index.js +1 -0
- package/types/headers.d.ts +2 -0
- package/types/headers.js +1 -0
- package/types/index.d.ts +318 -18
- package/types/index.js +12 -1
- package/utils/buffer.d.ts +1 -0
- package/utils/buffer.js +14 -0
- package/utils/colors.d.ts +24 -0
- package/utils/colors.js +2 -0
- package/utils/cookie.d.ts +55 -0
- package/utils/cookie.js +53 -0
- package/utils/file.d.ts +38 -0
- package/utils/file.js +96 -0
- package/utils/formData.d.ts +41 -1
- package/utils/formData.js +58 -9
- package/utils/generateID.d.ts +42 -0
- package/utils/generateID.js +32 -0
- package/utils/httpStatusMap.d.ts +14 -0
- package/utils/low-level.d.ts +58 -0
- package/utils/low-level.js +108 -0
- package/utils/mimeTypes.d.ts +4 -0
- package/utils/{staticFile.js → mimeTypes.js} +0 -53
- package/utils/rateLimit.d.ts +18 -0
- package/utils/rateLimit.js +37 -0
- package/utils/response.d.ts +18 -0
- package/utils/response.js +58 -0
- package/{core/environment.d.ts → utils/runtime.d.ts} +1 -0
- package/{core/environment.js → utils/runtime.js} +1 -0
- package/utils/url.d.ts +42 -14
- package/utils/url.js +61 -27
- package/bun/adapter.d.ts +0 -127
- package/bun/adapter.js +0 -97
- package/cjs/bun/adapter.js +0 -100
- package/cjs/core/MiddlewareConfigure.js +0 -68
- package/cjs/core/common.js +0 -15
- package/cjs/deno/adpater.js +0 -67
- package/cjs/helper/common.js +0 -17
- package/cjs/middleware/basicAuth.js +0 -71
- package/cjs/middleware/cacheControl.js +0 -90
- package/cjs/middleware/detectBot.js +0 -104
- package/cjs/middleware/detectLocale.js +0 -43
- package/cjs/middleware/lazyLoadModules.js +0 -73
- package/cjs/middleware/rateLimiter.js +0 -24
- package/cjs/middleware/requestTimeout.js +0 -42
- package/cjs/middleware/sanitizeHeader.js +0 -51
- package/cjs/middleware/secureHeaders.js +0 -42
- package/cjs/node/adapter.js +0 -138
- package/cjs/utils/regexRouter.js +0 -58
- package/cjs/utils/state.js +0 -34
- package/cjs/utils/toWebRequest.js +0 -35
- package/cjs/ws/deno.js +0 -20
- package/cjs/ws/index.js +0 -53
- package/cjs/ws/node.js +0 -65
- package/core/MiddlewareConfigure.d.ts +0 -15
- package/core/MiddlewareConfigure.js +0 -63
- package/core/common.d.ts +0 -21
- package/core/common.js +0 -11
- package/deno/adpater.d.ts +0 -38
- package/deno/adpater.js +0 -64
- package/helper/common.d.ts +0 -5
- package/helper/common.js +0 -14
- package/middleware/basicAuth.d.ts +0 -81
- package/middleware/basicAuth.js +0 -67
- package/middleware/cacheControl.d.ts +0 -48
- package/middleware/cacheControl.js +0 -53
- package/middleware/detectBot.d.ts +0 -121
- package/middleware/detectBot.js +0 -98
- package/middleware/detectLocale.d.ts +0 -55
- package/middleware/detectLocale.js +0 -39
- package/middleware/lazyLoadModules.d.ts +0 -72
- package/middleware/lazyLoadModules.js +0 -69
- package/middleware/rateLimiter.js +0 -20
- package/middleware/requestTimeout.d.ts +0 -25
- package/middleware/requestTimeout.js +0 -38
- package/middleware/sanitizeHeader.js +0 -47
- package/middleware/secureHeaders.d.ts +0 -78
- package/middleware/secureHeaders.js +0 -38
- package/middleware/xssProtection.js +0 -22
- package/node/adapter.d.ts +0 -46
- package/node/adapter.js +0 -102
- package/utils/regexRouter.d.ts +0 -66
- package/utils/regexRouter.js +0 -53
- package/utils/state.d.ts +0 -50
- package/utils/state.js +0 -30
- package/utils/staticFile.d.ts +0 -10
- package/utils/toWebRequest.js +0 -32
- package/ws/deno.d.ts +0 -6
- package/ws/deno.js +0 -16
- package/ws/index.d.ts +0 -180
- package/ws/index.js +0 -50
- package/ws/node.d.ts +0 -7
- package/ws/node.js +0 -28
- /package/{utils → node}/toWebRequest.d.ts +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
const joinSrc = (v) => typeof v === "string" ? v : v.join(" ");
|
|
3
|
+
const buildCSPString = (cspObj) => {
|
|
4
|
+
const parts = [];
|
|
5
|
+
for (const key in cspObj) {
|
|
6
|
+
parts.push(`${key} ${joinSrc(cspObj[key])}`);
|
|
7
|
+
}
|
|
8
|
+
return parts.join("; ");
|
|
9
|
+
};
|
|
10
|
+
const defaultPresets = {
|
|
11
|
+
strict: {
|
|
12
|
+
preset: "strict",
|
|
13
|
+
hsts: true,
|
|
14
|
+
hstsMaxAge: 63072000,
|
|
15
|
+
frameGuard: "DENY",
|
|
16
|
+
noSniff: true,
|
|
17
|
+
xssProtection: true,
|
|
18
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
19
|
+
permissionsPolicy: "geolocation=(), microphone=(), camera=(), usb=()",
|
|
20
|
+
csp: {
|
|
21
|
+
"default-src": ["'self'"],
|
|
22
|
+
"script-src": ["'self'"],
|
|
23
|
+
"style-src": ["'self'", "'unsafe-inline'"],
|
|
24
|
+
"img-src": ["'self'", "data:", "blob:"],
|
|
25
|
+
"font-src": ["'self'"],
|
|
26
|
+
"connect-src": ["'self'"],
|
|
27
|
+
"object-src": ["'none'"],
|
|
28
|
+
"frame-ancestors": ["'none'"],
|
|
29
|
+
},
|
|
30
|
+
cspReportOnly: false,
|
|
31
|
+
},
|
|
32
|
+
balanced: {
|
|
33
|
+
preset: "balanced",
|
|
34
|
+
hsts: true,
|
|
35
|
+
hstsMaxAge: 31536000,
|
|
36
|
+
frameGuard: "SAMEORIGIN",
|
|
37
|
+
noSniff: true,
|
|
38
|
+
xssProtection: true,
|
|
39
|
+
referrerPolicy: "no-referrer-when-downgrade",
|
|
40
|
+
permissionsPolicy: "geolocation=(), microphone=()",
|
|
41
|
+
csp: {
|
|
42
|
+
"default-src": ["'self'"],
|
|
43
|
+
"script-src": ["'self'", "https://cdn.jsdelivr.net"],
|
|
44
|
+
"style-src": [
|
|
45
|
+
"'self'",
|
|
46
|
+
"'unsafe-inline'",
|
|
47
|
+
"https://fonts.googleapis.com",
|
|
48
|
+
],
|
|
49
|
+
"img-src": ["'self'", "data:", "https://images.example.com"],
|
|
50
|
+
"connect-src": ["'self'", "https://api.example.com"],
|
|
51
|
+
},
|
|
52
|
+
cspReportOnly: true,
|
|
53
|
+
},
|
|
54
|
+
dev: {
|
|
55
|
+
preset: "dev",
|
|
56
|
+
hsts: false,
|
|
57
|
+
frameGuard: "SAMEORIGIN",
|
|
58
|
+
noSniff: false,
|
|
59
|
+
xssProtection: false,
|
|
60
|
+
referrerPolicy: "no-referrer",
|
|
61
|
+
permissionsPolicy: "",
|
|
62
|
+
csp: {
|
|
63
|
+
"default-src": [
|
|
64
|
+
"'self'",
|
|
65
|
+
"'unsafe-inline'",
|
|
66
|
+
"'unsafe-eval'",
|
|
67
|
+
"http://localhost:3000",
|
|
68
|
+
],
|
|
69
|
+
"img-src": ["'self'", "data:", "blob:"],
|
|
70
|
+
},
|
|
71
|
+
cspReportOnly: true,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const setHeader = (ctx, name, value) => {
|
|
75
|
+
if (typeof ctx.setHeader === "function")
|
|
76
|
+
ctx.setHeader(name, value);
|
|
77
|
+
else if (ctx.response?.setHeader)
|
|
78
|
+
ctx.response.setHeader(name, value);
|
|
79
|
+
else if (ctx.headersOut)
|
|
80
|
+
ctx.headersOut[name] = value;
|
|
81
|
+
};
|
|
82
|
+
export const secureHeaders = (userOpts = {}) => {
|
|
83
|
+
const preset = userOpts.preset ?? "balanced";
|
|
84
|
+
const base = {
|
|
85
|
+
...(defaultPresets[preset] || defaultPresets.balanced),
|
|
86
|
+
...userOpts,
|
|
87
|
+
};
|
|
88
|
+
const hstsHeader = base.hsts
|
|
89
|
+
? `max-age=${base.hstsMaxAge || 31536000}; includeSubDomains; preload`
|
|
90
|
+
: "";
|
|
91
|
+
const frameHeader = base.frameGuard || "SAMEORIGIN";
|
|
92
|
+
const noSniffHeader = base.noSniff ? "nosniff" : "";
|
|
93
|
+
const xssHeader = base.xssProtection ? "1; mode=block" : "0";
|
|
94
|
+
const referrerHeader = base.referrerPolicy || "no-referrer";
|
|
95
|
+
const permissionsHeader = base.permissionsPolicy || "";
|
|
96
|
+
let cspStatic = null;
|
|
97
|
+
let cspNeedsNonce = !!base.cspUseNonce;
|
|
98
|
+
if (typeof base.csp === "string")
|
|
99
|
+
cspStatic = base.csp;
|
|
100
|
+
else if (base.csp && typeof base.csp === "object")
|
|
101
|
+
cspStatic = buildCSPString(base.csp);
|
|
102
|
+
if (base.ultraFastMode)
|
|
103
|
+
cspNeedsNonce = false;
|
|
104
|
+
const cspReportOnly = !!base.cspReportOnly;
|
|
105
|
+
return async (ctx, next) => {
|
|
106
|
+
try {
|
|
107
|
+
if (base.hsts)
|
|
108
|
+
setHeader(ctx, "Strict-Transport-Security", hstsHeader);
|
|
109
|
+
setHeader(ctx, "X-Frame-Options", frameHeader);
|
|
110
|
+
setHeader(ctx, "X-Content-Type-Options", noSniffHeader);
|
|
111
|
+
setHeader(ctx, "X-XSS-Protection", xssHeader);
|
|
112
|
+
setHeader(ctx, "Referrer-Policy", referrerHeader);
|
|
113
|
+
if (permissionsHeader)
|
|
114
|
+
setHeader(ctx, "Permissions-Policy", permissionsHeader);
|
|
115
|
+
if (cspNeedsNonce) {
|
|
116
|
+
const nonce = crypto.randomBytes(12).toString("base64");
|
|
117
|
+
let cspHeader = cspStatic || `default-src 'self'; script-src 'self' 'nonce-${nonce}'`;
|
|
118
|
+
if (cspReportOnly)
|
|
119
|
+
setHeader(ctx, "Content-Security-Policy-Report-Only", cspHeader);
|
|
120
|
+
else
|
|
121
|
+
setHeader(ctx, "Content-Security-Policy", cspHeader);
|
|
122
|
+
ctx.cspNonce = nonce;
|
|
123
|
+
}
|
|
124
|
+
else if (cspStatic) {
|
|
125
|
+
if (cspReportOnly)
|
|
126
|
+
setHeader(ctx, "Content-Security-Policy-Report-Only", cspStatic);
|
|
127
|
+
else
|
|
128
|
+
setHeader(ctx, "Content-Security-Policy", cspStatic);
|
|
129
|
+
}
|
|
130
|
+
return await next();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return await next();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Middleware } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for HTTP Strict Transport Security (HSTS) header.
|
|
4
|
+
*/
|
|
5
|
+
export interface HstsOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Max age in seconds for the `Strict-Transport-Security` header.
|
|
8
|
+
* Example: 31536000 (1 year)
|
|
9
|
+
*/
|
|
10
|
+
maxAge?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Apply HSTS to subdomains by adding `includeSubDomains` directive.
|
|
13
|
+
* Default: false
|
|
14
|
+
*/
|
|
15
|
+
includeSubDomains?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Add `preload` directive for browser preload lists.
|
|
18
|
+
* Default: false
|
|
19
|
+
*/
|
|
20
|
+
preload?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Only apply HSTS on HTTPS requests.
|
|
23
|
+
* If true, HTTP requests will not receive the HSTS header.
|
|
24
|
+
* Default: false
|
|
25
|
+
*/
|
|
26
|
+
hstsOnlyOnHttps?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Options for the secureHeaders middleware.
|
|
30
|
+
*/
|
|
31
|
+
export type SecureHeadersOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Built-in preset to use.
|
|
34
|
+
* - "strict": strongest defaults for production.
|
|
35
|
+
* - "balanced": reasonable defaults for most apps (report-only CSP by default).
|
|
36
|
+
* - "dev": permissive settings useful for local development.
|
|
37
|
+
*
|
|
38
|
+
* @default "balanced"
|
|
39
|
+
*/
|
|
40
|
+
preset?: "strict" | "balanced" | "dev";
|
|
41
|
+
/**
|
|
42
|
+
* HSTS (HTTP Strict Transport Security) options.
|
|
43
|
+
* If provided, the middleware will set the `Strict-Transport-Security` header.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* { maxAge: 31536000, includeSubDomains: true, preload: true }
|
|
47
|
+
*/
|
|
48
|
+
hsts?: HstsOptions;
|
|
49
|
+
/**
|
|
50
|
+
* Value for `X-Frame-Options` header.
|
|
51
|
+
* Common values: "DENY", "SAMEORIGIN".
|
|
52
|
+
*
|
|
53
|
+
* @default "SAMEORIGIN"
|
|
54
|
+
*/
|
|
55
|
+
frameGuard?: "DENY" | "SAMEORIGIN" | string;
|
|
56
|
+
/**
|
|
57
|
+
* If true, sets `X-Content-Type-Options: nosniff`.
|
|
58
|
+
*
|
|
59
|
+
* @default true (depends on preset)
|
|
60
|
+
*/
|
|
61
|
+
noSniff?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* If true, sets `X-XSS-Protection: 1; mode=block`.
|
|
64
|
+
* Note: modern browsers use CSP; this header is legacy but harmless.
|
|
65
|
+
*
|
|
66
|
+
* @default true (depends on preset)
|
|
67
|
+
*/
|
|
68
|
+
xssProtection?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Value for `Referrer-Policy` header.
|
|
71
|
+
* Examples: "no-referrer", "strict-origin-when-cross-origin".
|
|
72
|
+
*
|
|
73
|
+
* @default "no-referrer" (depends on preset)
|
|
74
|
+
*/
|
|
75
|
+
referrerPolicy?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Value for `Permissions-Policy` (formerly Feature-Policy).
|
|
78
|
+
* Example: 'geolocation=(), microphone=()'
|
|
79
|
+
*
|
|
80
|
+
* @default '' (empty string = not set)
|
|
81
|
+
*/
|
|
82
|
+
permissionsPolicy?: string;
|
|
83
|
+
/**
|
|
84
|
+
* Content Security Policy (CSP).
|
|
85
|
+
* - Pass a raw header string to use it unchanged.
|
|
86
|
+
* - Or pass an object mapping directives to sources (object will be prebuilt at init).
|
|
87
|
+
*
|
|
88
|
+
* Example object:
|
|
89
|
+
* {
|
|
90
|
+
* "default-src": ["'self'"],
|
|
91
|
+
* "script-src": ["'self'", "https://cdn.example.com"]
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
csp?: string | Record<string, string | string[]>;
|
|
95
|
+
/**
|
|
96
|
+
* If true, send `Content-Security-Policy-Report-Only` instead of enforcement header.
|
|
97
|
+
* Useful when first testing policies.
|
|
98
|
+
*
|
|
99
|
+
* @default false
|
|
100
|
+
*/
|
|
101
|
+
cspReportOnly?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* If true, middleware will generate a per-request nonce (string) and inject it
|
|
104
|
+
* into the `script-src` directive so inline scripts with that nonce are allowed.
|
|
105
|
+
* Note: nonce generation allocates a small string per-request unless `ultraFastMode` is enabled.
|
|
106
|
+
*
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
cspUseNonce?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Ultra-fast mode disables per-request allocations (e.g., nonce generation).
|
|
112
|
+
* Use this in high-QPS environments where inline scripts are not required.
|
|
113
|
+
*
|
|
114
|
+
* @default false
|
|
115
|
+
*/
|
|
116
|
+
ultraFastMode?: boolean;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* secureHeaders middleware
|
|
120
|
+
*
|
|
121
|
+
* Precomputes static headers (HSTS, static CSP, X-Frame-Options, etc.) at
|
|
122
|
+
* middleware creation time. Optionally supports per-request CSP nonces
|
|
123
|
+
* (disabled in ultraFastMode).
|
|
124
|
+
*
|
|
125
|
+
* @template T,Path
|
|
126
|
+
* @param {SecureHeadersOptions} [userOpts={}] - configuration overrides
|
|
127
|
+
* @returns {Middleware<T,Path>} TezX-compatible middleware
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* app.use(secureHeaders({ preset: 'strict', cspUseNonce: true }));
|
|
131
|
+
*/
|
|
132
|
+
export declare const secureHeaders: <T extends Record<string, any> = {}, Path extends string = any>(userOpts?: SecureHeadersOptions) => Middleware<T, Path>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { generateRandomBase64, GlobalConfig } from "../helper/index.js";
|
|
2
|
+
const joinSrc = (v) => typeof v === "string" ? v : v.join(" ");
|
|
3
|
+
const buildCSPString = (cspObj) => {
|
|
4
|
+
const parts = [];
|
|
5
|
+
for (const key in cspObj)
|
|
6
|
+
parts.push(`${key} ${joinSrc(cspObj[key])}`);
|
|
7
|
+
return parts.join("; ");
|
|
8
|
+
};
|
|
9
|
+
export const secureHeaders = (userOpts = {}) => {
|
|
10
|
+
const defaultPresets = {
|
|
11
|
+
strict: {
|
|
12
|
+
preset: "strict",
|
|
13
|
+
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true },
|
|
14
|
+
frameGuard: "DENY",
|
|
15
|
+
noSniff: true,
|
|
16
|
+
xssProtection: true,
|
|
17
|
+
referrerPolicy: "strict-origin-when-cross-origin",
|
|
18
|
+
permissionsPolicy: "geolocation=(), microphone=(), camera=(), usb=()",
|
|
19
|
+
csp: {
|
|
20
|
+
"default-src": ["'self'"],
|
|
21
|
+
"script-src": ["'self'"],
|
|
22
|
+
"style-src": ["'self'", "'unsafe-inline'"],
|
|
23
|
+
"img-src": ["'self'", "data:", "blob:"],
|
|
24
|
+
"font-src": ["'self'"],
|
|
25
|
+
"connect-src": ["'self'"],
|
|
26
|
+
"object-src": ["'none'"],
|
|
27
|
+
"frame-ancestors": ["'none'"],
|
|
28
|
+
},
|
|
29
|
+
cspReportOnly: false,
|
|
30
|
+
},
|
|
31
|
+
balanced: {
|
|
32
|
+
preset: "balanced",
|
|
33
|
+
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
|
|
34
|
+
frameGuard: "SAMEORIGIN",
|
|
35
|
+
noSniff: true,
|
|
36
|
+
xssProtection: true,
|
|
37
|
+
referrerPolicy: "no-referrer-when-downgrade",
|
|
38
|
+
permissionsPolicy: "geolocation=(), microphone=()",
|
|
39
|
+
csp: {
|
|
40
|
+
"default-src": ["'self'"],
|
|
41
|
+
"script-src": ["'self'", "https://cdn.jsdelivr.net"],
|
|
42
|
+
"style-src": [
|
|
43
|
+
"'self'",
|
|
44
|
+
"'unsafe-inline'",
|
|
45
|
+
"https://fonts.googleapis.com",
|
|
46
|
+
],
|
|
47
|
+
"img-src": ["'self'", "data:", "https://images.example.com"],
|
|
48
|
+
"connect-src": ["'self'", "https://api.example.com"],
|
|
49
|
+
},
|
|
50
|
+
cspReportOnly: true,
|
|
51
|
+
},
|
|
52
|
+
dev: {
|
|
53
|
+
preset: "dev",
|
|
54
|
+
hsts: undefined,
|
|
55
|
+
frameGuard: "SAMEORIGIN",
|
|
56
|
+
noSniff: false,
|
|
57
|
+
xssProtection: false,
|
|
58
|
+
referrerPolicy: "no-referrer",
|
|
59
|
+
permissionsPolicy: "",
|
|
60
|
+
csp: {
|
|
61
|
+
"default-src": [
|
|
62
|
+
"'self'",
|
|
63
|
+
"'unsafe-inline'",
|
|
64
|
+
"'unsafe-eval'",
|
|
65
|
+
"http://localhost:3000",
|
|
66
|
+
],
|
|
67
|
+
"img-src": ["'self'", "data:", "blob:"],
|
|
68
|
+
},
|
|
69
|
+
cspReportOnly: true,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const preset = userOpts.preset ?? "balanced";
|
|
73
|
+
const base = {
|
|
74
|
+
...(defaultPresets[preset] || defaultPresets.balanced),
|
|
75
|
+
...userOpts,
|
|
76
|
+
};
|
|
77
|
+
const frameHeader = base.frameGuard || "SAMEORIGIN";
|
|
78
|
+
const xssHeader = base.xssProtection ? "1; mode=block" : "0";
|
|
79
|
+
const noSniffHeader = base.noSniff ? "nosniff" : "";
|
|
80
|
+
const permissionsHeader = base.permissionsPolicy || "";
|
|
81
|
+
const referrerHeader = base.referrerPolicy || "no-referrer";
|
|
82
|
+
const hstsParts = [`max-age=${base.hsts?.maxAge || 31536000}`];
|
|
83
|
+
if (base.hsts?.includeSubDomains)
|
|
84
|
+
hstsParts.push("includeSubDomains");
|
|
85
|
+
if (base.hsts?.preload)
|
|
86
|
+
hstsParts.push("preload");
|
|
87
|
+
const hstsHeader = hstsParts.join("; ");
|
|
88
|
+
let cspStatic = null;
|
|
89
|
+
let cspNeedsNonce = !!base.cspUseNonce;
|
|
90
|
+
if (typeof base.csp === "string")
|
|
91
|
+
cspStatic = base.csp;
|
|
92
|
+
else if (base.csp)
|
|
93
|
+
cspStatic = buildCSPString(base.csp);
|
|
94
|
+
const cspReportOnly = !!base.cspReportOnly;
|
|
95
|
+
const ultraFast = !!base.ultraFastMode;
|
|
96
|
+
if (cspNeedsNonce && ultraFast) {
|
|
97
|
+
GlobalConfig.debugging.warn("secureHeaders: ultraFastMode disables CSP nonce support. Nonce will not be used.");
|
|
98
|
+
}
|
|
99
|
+
if (ultraFast)
|
|
100
|
+
cspNeedsNonce = false;
|
|
101
|
+
return async (ctx, next) => {
|
|
102
|
+
try {
|
|
103
|
+
if (base.hsts) {
|
|
104
|
+
const proto = (ctx.req?.header("x-forwarded-proto") || "").toString();
|
|
105
|
+
if (!base.hsts.hstsOnlyOnHttps || proto.includes("https")) {
|
|
106
|
+
ctx.headers.set("Strict-Transport-Security", hstsHeader);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
ctx.headers.set("X-Frame-Options", frameHeader);
|
|
110
|
+
ctx.headers.set("X-Content-Type-Options", noSniffHeader);
|
|
111
|
+
ctx.headers.set("X-XSS-Protection", xssHeader);
|
|
112
|
+
ctx.headers.set("Referrer-Policy", referrerHeader);
|
|
113
|
+
if (permissionsHeader)
|
|
114
|
+
ctx.headers.set("Permissions-Policy", permissionsHeader);
|
|
115
|
+
if (cspNeedsNonce) {
|
|
116
|
+
const nonce = generateRandomBase64();
|
|
117
|
+
let cspHeader = cspStatic;
|
|
118
|
+
if (!cspHeader) {
|
|
119
|
+
cspHeader = `default-src 'self'; script-src 'self' 'nonce-${nonce}'`;
|
|
120
|
+
}
|
|
121
|
+
if (typeof base.csp === "object") {
|
|
122
|
+
const idx = cspHeader.indexOf("script-src");
|
|
123
|
+
if (idx >= 0) {
|
|
124
|
+
const parts = [];
|
|
125
|
+
parts.push(cspHeader.slice(0, idx + 10));
|
|
126
|
+
parts.push(" 'nonce-" + nonce + "'");
|
|
127
|
+
parts.push(cspHeader.slice(idx + 10));
|
|
128
|
+
cspHeader = parts.join("");
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cspHeader += "; script-src 'self' 'nonce-" + nonce + "'";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
ctx.cspNonce = nonce;
|
|
135
|
+
if (cspReportOnly)
|
|
136
|
+
ctx.headers.set("Content-Security-Policy-Report-Only", cspHeader);
|
|
137
|
+
else
|
|
138
|
+
ctx.headers.set("Content-Security-Policy", cspHeader);
|
|
139
|
+
}
|
|
140
|
+
else if (cspStatic) {
|
|
141
|
+
if (cspReportOnly)
|
|
142
|
+
ctx.headers.set("Content-Security-Policy-Report-Only", cspStatic);
|
|
143
|
+
else
|
|
144
|
+
ctx.headers.set("Content-Security-Policy", cspStatic);
|
|
145
|
+
}
|
|
146
|
+
return await next();
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
console.error("secureHeaders middleware error", err);
|
|
150
|
+
return await next();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
};
|
|
@@ -40,4 +40,5 @@ export type XSSProtectionOptions = {
|
|
|
40
40
|
* fallbackCSP: "default-src 'self'"
|
|
41
41
|
* }));
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
declare const xssProtection: (options?: XSSProtectionOptions) => (ctx: Context, next: NextCallback) => Promise<void>;
|
|
44
|
+
export { xssProtection as default, xssProtection };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const xssProtection = (options = {}) => {
|
|
2
|
+
const { enabled = true, mode = "block", fallbackCSP = "default-src 'self'; script-src 'self';", } = options;
|
|
3
|
+
return async function xssProtection(ctx, next) {
|
|
4
|
+
const isEnabled = typeof enabled === "function" ? enabled(ctx) : enabled;
|
|
5
|
+
if (!isEnabled) {
|
|
6
|
+
return await next();
|
|
7
|
+
}
|
|
8
|
+
const xssHeaderValue = mode === "block" ? "1; mode=block" : "1";
|
|
9
|
+
ctx.setHeader("X-XSS-Protection", xssHeaderValue);
|
|
10
|
+
if (fallbackCSP) {
|
|
11
|
+
const existingCSP = ctx.req.header("content-security-policy");
|
|
12
|
+
if (!existingCSP) {
|
|
13
|
+
ctx.setHeader("Content-Security-Policy", fallbackCSP);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return await next();
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export { xssProtection as default, xssProtection };
|
package/node/env.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { TezXError } from "../core/error.js";
|
|
3
3
|
import { colorText } from "../utils/colors.js";
|
|
4
|
+
import { runtime } from "../utils/runtime.js";
|
|
4
5
|
function parseEnvFile(filePath, result) {
|
|
5
6
|
try {
|
|
6
|
-
let runtime = Environment.getEnvironment;
|
|
7
7
|
if (runtime !== "bun" && runtime !== "node") {
|
|
8
|
-
throw new
|
|
8
|
+
throw new TezXError(`Please use ${colorText(`import {loadEnv} from "tezx/${runtime}"`, "bgRed")} environment`);
|
|
9
9
|
}
|
|
10
10
|
let fileExists = existsSync(filePath);
|
|
11
11
|
if (!fileExists) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Middleware } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Middleware to extract and inject connection information into the request context.
|
|
4
|
+
*
|
|
5
|
+
* This middleware reads the socket's remote address information (like IP, port, and family)
|
|
6
|
+
* from the request object and attaches it to `ctx.req.remoteAddress`.
|
|
7
|
+
*
|
|
8
|
+
* @returns {Middleware<any>} The middleware function that sets `ctx.req.remoteAddress`.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { getConnInfo } from "tezx/node";
|
|
12
|
+
*
|
|
13
|
+
* app.use(getConnInfo());
|
|
14
|
+
*
|
|
15
|
+
* // Access later in route handler:
|
|
16
|
+
* router.get("/", (ctx) => {
|
|
17
|
+
* const ip = ctx.req.remoteAddress?.address;
|
|
18
|
+
* return new Response(`Your IP: ${ip}`);
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export declare function getConnInfo<T extends Record<string, any> = {}, Path extends string = any>(): Middleware<T, Path>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function getConnInfo() {
|
|
2
|
+
return (ctx, next) => {
|
|
3
|
+
let request = ctx.args?.[0];
|
|
4
|
+
if (request && request.socket) {
|
|
5
|
+
ctx.req.remoteAddress = {
|
|
6
|
+
family: request.socket.remoteFamily,
|
|
7
|
+
address: request.socket.remoteAddress,
|
|
8
|
+
port: request.socket.remotePort,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return next();
|
|
12
|
+
};
|
|
13
|
+
}
|
package/node/index.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { nodeAdapter } from "./adapter.js";
|
|
2
1
|
import { loadEnv } from "./env.js";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getConnInfo } from "./getConnInfo.js";
|
|
3
|
+
import { mountTezXOnNode } from "./mount-node.js";
|
|
4
|
+
import { serveStatic } from "./serveStatic.js";
|
|
5
|
+
import { toWebRequest } from "./toWebRequest.js";
|
|
6
|
+
import { upgradeWebSocket } from "./ws.js";
|
|
7
|
+
export type { nodeWebSocketOptions } from "./ws.js";
|
|
8
|
+
export type { ServeStatic, StaticServeOption, StaticFileArray, WebSocketCallback, WebSocketEvent, WebSocketOptions, } from "../types/index.js";
|
|
9
|
+
export { serveStatic, upgradeWebSocket, getConnInfo, toWebRequest, loadEnv, mountTezXOnNode, };
|
|
5
10
|
declare const _default: {
|
|
6
|
-
|
|
11
|
+
serveStatic: typeof serveStatic;
|
|
12
|
+
upgradeWebSocket: typeof upgradeWebSocket;
|
|
13
|
+
mountTezXOnNode: typeof mountTezXOnNode;
|
|
14
|
+
toWebRequest: typeof toWebRequest;
|
|
15
|
+
getConnInfo: typeof getConnInfo;
|
|
7
16
|
loadEnv: typeof loadEnv;
|
|
8
17
|
};
|
|
9
18
|
export default _default;
|
package/node/index.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { nodeAdapter } from "./adapter.js";
|
|
2
1
|
import { loadEnv } from "./env.js";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { getConnInfo } from "./getConnInfo.js";
|
|
3
|
+
import { mountTezXOnNode } from "./mount-node.js";
|
|
4
|
+
import { serveStatic } from "./serveStatic.js";
|
|
5
|
+
import { toWebRequest } from "./toWebRequest.js";
|
|
6
|
+
import { upgradeWebSocket } from "./ws.js";
|
|
7
|
+
export { serveStatic, upgradeWebSocket, getConnInfo, toWebRequest, loadEnv, mountTezXOnNode, };
|
|
5
8
|
export default {
|
|
6
|
-
|
|
9
|
+
serveStatic,
|
|
10
|
+
upgradeWebSocket,
|
|
11
|
+
mountTezXOnNode,
|
|
12
|
+
toWebRequest,
|
|
13
|
+
getConnInfo,
|
|
7
14
|
loadEnv,
|
|
8
15
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TezX } from "../core/server.js";
|
|
2
|
+
/**
|
|
3
|
+
* Mounts a TezX app onto a native Node.js HTTP server.
|
|
4
|
+
*
|
|
5
|
+
* This bridges a TezX application (typically used in a Fetch API environment) with a native
|
|
6
|
+
* Node.js HTTP server by adapting the request/response interface and handling stream piping.
|
|
7
|
+
*
|
|
8
|
+
* @param {TezX} app - The TezX app instance that handles incoming requests and returns a Response object.
|
|
9
|
+
* @param {import('http').Server} server - A native Node.js HTTP server instance to attach the TezX handler to.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mountTezXOnNode(app: TezX, server: any): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { GlobalConfig } from "../core/config.js";
|
|
3
|
+
import { toWebRequest } from "./toWebRequest.js";
|
|
4
|
+
function readableStreamToNodeStream(stream) {
|
|
5
|
+
const reader = stream.getReader();
|
|
6
|
+
return new Readable({
|
|
7
|
+
async read() {
|
|
8
|
+
try {
|
|
9
|
+
const { done, value } = await reader.read();
|
|
10
|
+
if (done) {
|
|
11
|
+
this.push(null);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
this.push(value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
this.destroy(err);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
destroy(err, callback) {
|
|
22
|
+
reader.cancel().then(() => callback(err), (cancelErr) => callback(cancelErr));
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function mountTezXOnNode(app, server) {
|
|
27
|
+
server.on("request", async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const request = toWebRequest(req, req.method);
|
|
30
|
+
const response = await app.serve(request, req, res, server);
|
|
31
|
+
if (res.writableEnded || res.headersSent)
|
|
32
|
+
return;
|
|
33
|
+
const { status = 200, statusText = "", headers = {}, body } = response;
|
|
34
|
+
const nodeHeaders = Object.fromEntries(headers instanceof Headers
|
|
35
|
+
? headers.entries()
|
|
36
|
+
: Object.entries(headers));
|
|
37
|
+
res.writeHead(status, statusText, nodeHeaders);
|
|
38
|
+
if (!body) {
|
|
39
|
+
return res.end();
|
|
40
|
+
}
|
|
41
|
+
if (body instanceof Readable) {
|
|
42
|
+
return body.pipe(res);
|
|
43
|
+
}
|
|
44
|
+
if (typeof body.getReader === "function") {
|
|
45
|
+
return (Readable.fromWeb?.(body)?.pipe?.(res) ??
|
|
46
|
+
readableStreamToNodeStream(body).pipe(res));
|
|
47
|
+
}
|
|
48
|
+
res.end(body);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
res.statusCode = 500;
|
|
52
|
+
GlobalConfig.debugging?.error?.(err?.message || "Unknown error");
|
|
53
|
+
res.end(err?.message || "Internal Server Error");
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ServeStatic, StaticFileArray, StaticServeOption } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registers static files for serving from a folder, optionally under a specific route.
|
|
4
|
+
*
|
|
5
|
+
* There are two usage patterns:
|
|
6
|
+
*
|
|
7
|
+
* 1. `serveStatic(route: string, folder: string, option?: StaticServeOption)`
|
|
8
|
+
* - Serves files in `folder` under the given `route`.
|
|
9
|
+
* - Example: `serveStatic("/assets", "./public/assets")`
|
|
10
|
+
*
|
|
11
|
+
* 2. `serveStatic(folder: string, option?: StaticServeOption)`
|
|
12
|
+
* - Serves files in `folder` under its own route (e.g., `./public` -> `/public`)
|
|
13
|
+
* - Example: `serveStatic("./public")`
|
|
14
|
+
*
|
|
15
|
+
* @param args - Either [route, folder, option] or [folder, option]
|
|
16
|
+
* @returns A list of static files and their associated route paths.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Universal ETag generator for Deno, Bun, and Node.js environments.
|
|
20
|
+
* Uses file size + modification time (mtimeMs) to create a unique hash-based ETag.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const etag = await getETag("/path/to/file.txt");
|
|
24
|
+
* ctx.setHeader("ETag", etag);
|
|
25
|
+
*/
|
|
26
|
+
export declare function serveStatic(route: string, folder: string, option?: StaticServeOption): ServeStatic;
|
|
27
|
+
export declare function serveStatic(folder: string, option?: StaticServeOption): ServeStatic;
|
|
28
|
+
/**
|
|
29
|
+
* Recursively collects files from a directory, applying filters.
|
|
30
|
+
*
|
|
31
|
+
* @param dir - Directory to search.
|
|
32
|
+
* @param basePath - Route base path for constructing public paths.
|
|
33
|
+
* @param option - Options including extensions filter.
|
|
34
|
+
* @returns Array of file objects with file system and public paths.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getFiles(dir: string, basePath?: string, option?: StaticServeOption): StaticFileArray;
|