tezx 3.0.14-beta → 3.0.14-beta2
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/bun/env.d.ts +5 -0
- package/bun/env.js +44 -0
- package/bun/index.d.ts +1 -1
- package/bun/index.js +1 -1
- package/bun/ws.js +4 -3
- package/cjs/bun/env.js +47 -0
- package/cjs/bun/index.js +1 -1
- package/cjs/bun/ws.js +4 -3
- package/cjs/core/context.js +4 -3
- package/cjs/core/error.js +8 -0
- package/cjs/core/request.js +3 -2
- package/cjs/core/router.js +11 -17
- package/cjs/core/server.js +14 -9
- package/cjs/deno/env.js +2 -1
- package/cjs/deno/serveStatic.js +2 -1
- package/cjs/deno/ws.js +4 -3
- package/cjs/helper/index.js +12 -10
- package/cjs/index.js +1 -1
- package/cjs/jwt/node.js +94 -0
- package/cjs/jwt/web.js +178 -0
- package/cjs/middleware/basic-auth.js +9 -14
- package/cjs/middleware/bearer-auth.js +5 -5
- package/cjs/middleware/cache-control.js +44 -0
- package/cjs/middleware/cors.js +1 -1
- package/cjs/middleware/detect-bot.js +57 -0
- package/cjs/middleware/i18n.js +92 -0
- package/cjs/middleware/index.js +5 -0
- package/cjs/middleware/logger.js +3 -2
- package/cjs/middleware/rate-limiter.js +1 -1
- package/cjs/middleware/sanitize-headers.js +1 -1
- package/cjs/middleware/secure-headers copy.js +143 -0
- package/cjs/middleware/secure-headers.js +157 -0
- package/cjs/node/env.js +4 -3
- package/cjs/node/serveStatic.js +2 -1
- package/cjs/node/ws.js +3 -2
- package/cjs/registry/RadixRouter.js +2 -33
- package/cjs/utils/buffer.js +17 -0
- package/cjs/utils/file.js +28 -6
- package/cjs/utils/generateID.js +10 -0
- package/cjs/utils/response.js +3 -1
- package/core/context.d.ts +3 -3
- package/core/context.js +4 -3
- package/core/error.d.ts +1 -0
- package/core/error.js +7 -0
- package/core/request.js +3 -2
- package/core/router.d.ts +3 -8
- package/core/router.js +11 -17
- package/core/server.d.ts +10 -23
- package/core/server.js +15 -10
- package/deno/env.js +2 -1
- package/deno/index.d.ts +1 -1
- package/deno/serveStatic.js +2 -1
- package/deno/ws.d.ts +1 -1
- package/deno/ws.js +4 -3
- package/helper/index.d.ts +7 -6
- package/helper/index.js +7 -6
- package/index.d.ts +5 -2
- package/index.js +1 -1
- 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 +2 -1
- package/middleware/basic-auth.js +9 -14
- package/middleware/bearer-auth.d.ts +2 -1
- package/middleware/bearer-auth.js +5 -5
- package/middleware/cache-control.d.ts +30 -0
- package/middleware/cache-control.js +40 -0
- package/middleware/cors.js +1 -1
- package/middleware/detect-bot.d.ts +113 -0
- package/middleware/detect-bot.js +53 -0
- package/middleware/i18n.d.ts +194 -0
- package/middleware/i18n.js +88 -0
- package/middleware/index.d.ts +5 -0
- package/middleware/index.js +5 -0
- package/middleware/logger.d.ts +1 -1
- package/middleware/logger.js +3 -2
- package/middleware/rate-limiter.d.ts +3 -2
- package/middleware/rate-limiter.js +1 -1
- package/middleware/sanitize-headers.js +1 -1
- 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/node/env.js +4 -3
- package/node/serveStatic.d.ts +8 -0
- package/node/serveStatic.js +2 -1
- package/node/ws.d.ts +1 -1
- package/node/ws.js +3 -2
- package/package.json +12 -1
- package/registry/RadixRouter.js +2 -33
- package/types/index.d.ts +1 -1
- package/utils/buffer.d.ts +1 -0
- package/utils/buffer.js +14 -0
- package/utils/file.d.ts +6 -2
- package/utils/file.js +27 -6
- package/utils/generateID.d.ts +9 -0
- package/utils/generateID.js +9 -0
- package/utils/response.js +3 -1
- package/cjs/utils/regexRouter.js +0 -57
- package/utils/regexRouter.d.ts +0 -66
- package/utils/regexRouter.js +0 -52
|
@@ -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
|
+
};
|
package/node/env.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
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
|
-
if (runtime !== "
|
|
7
|
-
throw new
|
|
7
|
+
if (runtime !== "node") {
|
|
8
|
+
throw new TezXError(`Please use ${colorText(`import {loadEnv} from "tezx/${runtime}"`, "bgRed")} environment`);
|
|
8
9
|
}
|
|
9
10
|
let fileExists = existsSync(filePath);
|
|
10
11
|
if (!fileExists) {
|
package/node/serveStatic.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ import { ServeStatic, StaticFileArray, StaticServeOption } from "../types/index.
|
|
|
15
15
|
* @param args - Either [route, folder, option] or [folder, option]
|
|
16
16
|
* @returns A list of static files and their associated route paths.
|
|
17
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
|
+
*/
|
|
18
26
|
export declare function serveStatic(route: string, folder: string, option?: StaticServeOption): ServeStatic;
|
|
19
27
|
export declare function serveStatic(folder: string, option?: StaticServeOption): ServeStatic;
|
|
20
28
|
/**
|
package/node/serveStatic.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { extensionExtract, sanitizePathSplitBasePath, } from "../utils/low-level.js";
|
|
4
|
+
import { TezXError } from "../core/error.js";
|
|
4
5
|
export function serveStatic(...args) {
|
|
5
6
|
let route = "";
|
|
6
7
|
let dir;
|
|
@@ -21,7 +22,7 @@ export function serveStatic(...args) {
|
|
|
21
22
|
[dir] = args;
|
|
22
23
|
break;
|
|
23
24
|
default:
|
|
24
|
-
throw new
|
|
25
|
+
throw new TezXError(`\x1b[1;31m404 Not Found\x1b[0m \x1b[1;32mInvalid arguments\x1b[0m`);
|
|
25
26
|
}
|
|
26
27
|
return {
|
|
27
28
|
files: getFiles(dir, route, options),
|
package/node/ws.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type nodeWebSocketOptions = {
|
|
|
22
22
|
*
|
|
23
23
|
* @param {WebSocketCallback} callback - Function that returns WebSocket event handlers.
|
|
24
24
|
* @param {WebSocketOptions} [options={}] - Options for WebSocket upgrade behavior.
|
|
25
|
-
* @param {(error:
|
|
25
|
+
* @param {(error: TezXError, ctx: T) => any} [options.onUpgradeError] - Custom error handler for upgrade failures.
|
|
26
26
|
* @param {number} [options.maxPayload=1048576] - Maximum allowed payload size (in bytes).
|
|
27
27
|
* @param {boolean} [options.perMessageDeflate=false] - Whether to enable per-message deflate.
|
|
28
28
|
*
|
package/node/ws.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TezXError } from "../core/error.js";
|
|
1
2
|
export function upgradeWebSocket(callback, options = {}) {
|
|
2
3
|
const { onUpgradeError = (error, ctx) => {
|
|
3
4
|
ctx.setStatus = 401;
|
|
@@ -13,13 +14,13 @@ export function upgradeWebSocket(callback, options = {}) {
|
|
|
13
14
|
return next();
|
|
14
15
|
}
|
|
15
16
|
ctx.setStatus = 401;
|
|
16
|
-
return onUpgradeError(new
|
|
17
|
+
return onUpgradeError(new TezXError("401 Bad Request: Invalid WebSocket headers", 401), ctx);
|
|
17
18
|
}
|
|
18
19
|
ctx.wsProtocol = ctx.url?.startsWith("https") ? "wss" : "ws";
|
|
19
20
|
const server = ctx.args?.[2];
|
|
20
21
|
if (!server?.on) {
|
|
21
22
|
ctx.setStatus = 500;
|
|
22
|
-
return onUpgradeError(new
|
|
23
|
+
return onUpgradeError(new TezXError("Node server instance missing for WebSocket", 426), ctx);
|
|
23
24
|
}
|
|
24
25
|
const { WebSocketServer } = await import("ws");
|
|
25
26
|
const wss = new WebSocketServer({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tezx",
|
|
3
|
-
"version": "3.0.14-
|
|
3
|
+
"version": "3.0.14-beta2",
|
|
4
4
|
"description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "cjs/index.js",
|
|
@@ -42,6 +42,16 @@
|
|
|
42
42
|
"require": "./cjs/helper/index.js",
|
|
43
43
|
"types": "./helper/index.d.ts"
|
|
44
44
|
},
|
|
45
|
+
"./jwt/node": {
|
|
46
|
+
"import": "./jwt/node.js",
|
|
47
|
+
"require": "./cjs/jwt/node.js",
|
|
48
|
+
"types": "./jwt/node.d.ts"
|
|
49
|
+
},
|
|
50
|
+
"./jwt/web": {
|
|
51
|
+
"import": "./jwt/web.js",
|
|
52
|
+
"require": "./cjs/jwt/web.js",
|
|
53
|
+
"types": "./jwt/web.d.ts"
|
|
54
|
+
},
|
|
45
55
|
"./registry": {
|
|
46
56
|
"import": "./registry/index.js",
|
|
47
57
|
"require": "./cjs/registry/index.js",
|
|
@@ -64,6 +74,7 @@
|
|
|
64
74
|
"core/",
|
|
65
75
|
"deno/",
|
|
66
76
|
"helper/",
|
|
77
|
+
"jwt/",
|
|
67
78
|
"middleware/",
|
|
68
79
|
"node/",
|
|
69
80
|
"registry/",
|
package/registry/RadixRouter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TezXError } from "../core/error.js";
|
|
1
2
|
import { sanitizePathSplit } from "../helper/index.js";
|
|
2
3
|
export class RadixRouter {
|
|
3
4
|
root = { children: {} };
|
|
@@ -19,7 +20,7 @@ export class RadixRouter {
|
|
|
19
20
|
}
|
|
20
21
|
else if (node.children[":"]?.paramName !== paramName ||
|
|
21
22
|
node.children[":"]?.isOptional !== isOptional) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new TezXError(`Conflicting param definition for ${paramName}`);
|
|
23
24
|
}
|
|
24
25
|
node = node.children[":"];
|
|
25
26
|
}
|
|
@@ -141,35 +142,3 @@ export class RadixRouter {
|
|
|
141
142
|
return result;
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
|
-
const routes = [
|
|
145
|
-
"/",
|
|
146
|
-
"/users",
|
|
147
|
-
"/users/:id",
|
|
148
|
-
"/users/:id/profile",
|
|
149
|
-
"/posts/:postId?",
|
|
150
|
-
"/files/*",
|
|
151
|
-
"/admin/settings",
|
|
152
|
-
"/search/:term?",
|
|
153
|
-
"/categories/:categoryId/products/:productId",
|
|
154
|
-
"/about",
|
|
155
|
-
];
|
|
156
|
-
const testPaths = [
|
|
157
|
-
"/",
|
|
158
|
-
"/users",
|
|
159
|
-
"/users/123",
|
|
160
|
-
"/users/123/profile",
|
|
161
|
-
"/posts",
|
|
162
|
-
"/posts/456",
|
|
163
|
-
"/files/path/to/file.txt",
|
|
164
|
-
"/admin/settings",
|
|
165
|
-
"/search",
|
|
166
|
-
"/search/nodejs",
|
|
167
|
-
"/categories/12/products/999",
|
|
168
|
-
"/notfound",
|
|
169
|
-
];
|
|
170
|
-
const router = new RadixRouter();
|
|
171
|
-
let x = function xx() {
|
|
172
|
-
return {
|
|
173
|
-
body: "3453455",
|
|
174
|
-
};
|
|
175
|
-
};
|
package/types/index.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type WebSocketOptions = {
|
|
|
61
61
|
/**
|
|
62
62
|
* Called when an error occurs during the upgrade process.
|
|
63
63
|
*/
|
|
64
|
-
onUpgradeError?: (err:
|
|
64
|
+
onUpgradeError?: (err: TezXError, ctx: Context) => HttpBaseResponse;
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
67
|
* Represents a string key used for HTTP headers.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function base64Decode(base64: string): string;
|
package/utils/buffer.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function base64Decode(base64) {
|
|
2
|
+
const pad = base64.length % 4;
|
|
3
|
+
if (pad)
|
|
4
|
+
base64 += "=".repeat(4 - pad);
|
|
5
|
+
if (typeof Buffer !== "undefined") {
|
|
6
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
7
|
+
}
|
|
8
|
+
else if (typeof atob === "function") {
|
|
9
|
+
return new TextDecoder().decode(Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)));
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
throw new Error("Base64 decode not supported in this environment");
|
|
13
|
+
}
|
|
14
|
+
}
|
package/utils/file.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export declare function readStream(path: string): Promise<ReadableStream>;
|
|
|
29
29
|
* Supports Node.js, Bun, and Deno runtimes.
|
|
30
30
|
*
|
|
31
31
|
* @param {string} path - Path to the file.
|
|
32
|
-
* @returns {Promise<number>} - Resolves to the file size in bytes.
|
|
32
|
+
* @returns {Promise<{size: number, mtime: Date}>} - Resolves to the file size in bytes.
|
|
33
33
|
*/
|
|
34
|
-
export declare function fileSize(path: string): Promise<
|
|
34
|
+
export declare function fileSize(path: string): Promise<{
|
|
35
|
+
mtime: Date;
|
|
36
|
+
size: number;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function etagDigest(algo: string | undefined, data: any): Promise<string>;
|