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.
Files changed (102) hide show
  1. package/bun/env.d.ts +5 -0
  2. package/bun/env.js +44 -0
  3. package/bun/index.d.ts +1 -1
  4. package/bun/index.js +1 -1
  5. package/bun/ws.js +4 -3
  6. package/cjs/bun/env.js +47 -0
  7. package/cjs/bun/index.js +1 -1
  8. package/cjs/bun/ws.js +4 -3
  9. package/cjs/core/context.js +4 -3
  10. package/cjs/core/error.js +8 -0
  11. package/cjs/core/request.js +3 -2
  12. package/cjs/core/router.js +11 -17
  13. package/cjs/core/server.js +14 -9
  14. package/cjs/deno/env.js +2 -1
  15. package/cjs/deno/serveStatic.js +2 -1
  16. package/cjs/deno/ws.js +4 -3
  17. package/cjs/helper/index.js +12 -10
  18. package/cjs/index.js +1 -1
  19. package/cjs/jwt/node.js +94 -0
  20. package/cjs/jwt/web.js +178 -0
  21. package/cjs/middleware/basic-auth.js +9 -14
  22. package/cjs/middleware/bearer-auth.js +5 -5
  23. package/cjs/middleware/cache-control.js +44 -0
  24. package/cjs/middleware/cors.js +1 -1
  25. package/cjs/middleware/detect-bot.js +57 -0
  26. package/cjs/middleware/i18n.js +92 -0
  27. package/cjs/middleware/index.js +5 -0
  28. package/cjs/middleware/logger.js +3 -2
  29. package/cjs/middleware/rate-limiter.js +1 -1
  30. package/cjs/middleware/sanitize-headers.js +1 -1
  31. package/cjs/middleware/secure-headers copy.js +143 -0
  32. package/cjs/middleware/secure-headers.js +157 -0
  33. package/cjs/node/env.js +4 -3
  34. package/cjs/node/serveStatic.js +2 -1
  35. package/cjs/node/ws.js +3 -2
  36. package/cjs/registry/RadixRouter.js +2 -33
  37. package/cjs/utils/buffer.js +17 -0
  38. package/cjs/utils/file.js +28 -6
  39. package/cjs/utils/generateID.js +10 -0
  40. package/cjs/utils/response.js +3 -1
  41. package/core/context.d.ts +3 -3
  42. package/core/context.js +4 -3
  43. package/core/error.d.ts +1 -0
  44. package/core/error.js +7 -0
  45. package/core/request.js +3 -2
  46. package/core/router.d.ts +3 -8
  47. package/core/router.js +11 -17
  48. package/core/server.d.ts +10 -23
  49. package/core/server.js +15 -10
  50. package/deno/env.js +2 -1
  51. package/deno/index.d.ts +1 -1
  52. package/deno/serveStatic.js +2 -1
  53. package/deno/ws.d.ts +1 -1
  54. package/deno/ws.js +4 -3
  55. package/helper/index.d.ts +7 -6
  56. package/helper/index.js +7 -6
  57. package/index.d.ts +5 -2
  58. package/index.js +1 -1
  59. package/jwt/node.d.ts +39 -0
  60. package/jwt/node.js +87 -0
  61. package/jwt/web.d.ts +14 -0
  62. package/jwt/web.js +174 -0
  63. package/middleware/basic-auth.d.ts +2 -1
  64. package/middleware/basic-auth.js +9 -14
  65. package/middleware/bearer-auth.d.ts +2 -1
  66. package/middleware/bearer-auth.js +5 -5
  67. package/middleware/cache-control.d.ts +30 -0
  68. package/middleware/cache-control.js +40 -0
  69. package/middleware/cors.js +1 -1
  70. package/middleware/detect-bot.d.ts +113 -0
  71. package/middleware/detect-bot.js +53 -0
  72. package/middleware/i18n.d.ts +194 -0
  73. package/middleware/i18n.js +88 -0
  74. package/middleware/index.d.ts +5 -0
  75. package/middleware/index.js +5 -0
  76. package/middleware/logger.d.ts +1 -1
  77. package/middleware/logger.js +3 -2
  78. package/middleware/rate-limiter.d.ts +3 -2
  79. package/middleware/rate-limiter.js +1 -1
  80. package/middleware/sanitize-headers.js +1 -1
  81. package/middleware/secure-headers copy.d.ts +15 -0
  82. package/middleware/secure-headers copy.js +136 -0
  83. package/middleware/secure-headers.d.ts +132 -0
  84. package/middleware/secure-headers.js +153 -0
  85. package/node/env.js +4 -3
  86. package/node/serveStatic.d.ts +8 -0
  87. package/node/serveStatic.js +2 -1
  88. package/node/ws.d.ts +1 -1
  89. package/node/ws.js +3 -2
  90. package/package.json +12 -1
  91. package/registry/RadixRouter.js +2 -33
  92. package/types/index.d.ts +1 -1
  93. package/utils/buffer.d.ts +1 -0
  94. package/utils/buffer.js +14 -0
  95. package/utils/file.d.ts +6 -2
  96. package/utils/file.js +27 -6
  97. package/utils/generateID.d.ts +9 -0
  98. package/utils/generateID.js +9 -0
  99. package/utils/response.js +3 -1
  100. package/cjs/utils/regexRouter.js +0 -57
  101. package/utils/regexRouter.d.ts +0 -66
  102. package/utils/regexRouter.js +0 -52
package/cjs/jwt/web.js ADDED
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sign = sign;
4
+ exports.verify = verify;
5
+ function parseExpiry(exp) {
6
+ const match = exp.match(/^(\d+)([smhd])$/);
7
+ if (!match)
8
+ return parseInt(exp, 10);
9
+ const [, val, unit] = match;
10
+ const num = parseInt(val, 10);
11
+ switch (unit) {
12
+ case "s":
13
+ return num;
14
+ case "m":
15
+ return num * 60;
16
+ case "h":
17
+ return num * 3600;
18
+ case "d":
19
+ return num * 86400;
20
+ default:
21
+ return num;
22
+ }
23
+ }
24
+ const encoder = new TextEncoder();
25
+ const decoder = new TextDecoder();
26
+ function uint8ToBase64(u8) {
27
+ let binary = "";
28
+ const len = u8.length;
29
+ for (let i = 0; i < len; i++)
30
+ binary += String.fromCharCode(u8[i]);
31
+ return typeof btoa !== "undefined" ? btoa(binary) : fallbackBtoa(binary);
32
+ }
33
+ function base64ToUint8(base64) {
34
+ const bin = typeof atob !== "undefined" ? atob(base64) : fallbackAtob(base64);
35
+ const len = bin.length;
36
+ const u8 = new Uint8Array(len);
37
+ for (let i = 0; i < len; i++)
38
+ u8[i] = bin.charCodeAt(i);
39
+ return u8;
40
+ }
41
+ function base64urlFromUint8(u8) {
42
+ return uint8ToBase64(u8)
43
+ .replace(/=/g, "")
44
+ .replace(/\+/g, "-")
45
+ .replace(/\//g, "_");
46
+ }
47
+ function base64urlEncodeString(str) {
48
+ return base64urlFromUint8(encoder.encode(str));
49
+ }
50
+ function base64urlDecodeToString(input) {
51
+ let b64 = input.replace(/-/g, "+").replace(/_/g, "/");
52
+ const pad = b64.length % 4;
53
+ if (pad)
54
+ b64 += "=".repeat(4 - pad);
55
+ const u8 = base64ToUint8(b64);
56
+ return decoder.decode(u8);
57
+ }
58
+ function constantTimeEqual(a, b) {
59
+ if (a.length !== b.length)
60
+ return false;
61
+ let res = 0;
62
+ for (let i = 0; i < a.length; i++) {
63
+ res |= a.charCodeAt(i) ^ b.charCodeAt(i);
64
+ }
65
+ return res === 0;
66
+ }
67
+ function mapAlg(alg) {
68
+ if (alg === "HS256")
69
+ return { name: "HMAC", hash: "SHA-256" };
70
+ return { name: "HMAC", hash: "SHA-512" };
71
+ }
72
+ async function importKey(secret, alg) {
73
+ const keyData = encoder.encode(secret);
74
+ return crypto.subtle.importKey("raw", keyData, mapAlg(alg), false, [
75
+ "sign",
76
+ "verify",
77
+ ]);
78
+ }
79
+ async function hmacSign(key, data) {
80
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
81
+ return new Uint8Array(sig);
82
+ }
83
+ function fallbackBtoa(binaryStr) {
84
+ let base64 = "";
85
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
86
+ let i = 0;
87
+ const len = binaryStr.length;
88
+ while (i < len) {
89
+ const c1 = binaryStr.charCodeAt(i++) & 0xff;
90
+ if (i === len) {
91
+ base64 += chars.charAt(c1 >> 2);
92
+ base64 += chars.charAt((c1 & 0x3) << 4);
93
+ base64 += "==";
94
+ break;
95
+ }
96
+ const c2 = binaryStr.charCodeAt(i++);
97
+ if (i === len) {
98
+ base64 += chars.charAt(c1 >> 2);
99
+ base64 += chars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
100
+ base64 += chars.charAt((c2 & 0xf) << 2);
101
+ base64 += "=";
102
+ break;
103
+ }
104
+ const c3 = binaryStr.charCodeAt(i++);
105
+ base64 += chars.charAt(c1 >> 2);
106
+ base64 += chars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
107
+ base64 += chars.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
108
+ base64 += chars.charAt(c3 & 0x3f);
109
+ }
110
+ return base64;
111
+ }
112
+ function fallbackAtob(b64) {
113
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
114
+ let output = "";
115
+ let i = 0;
116
+ b64 = b64.replace(/[^A-Za-z0-9\+\/]/g, "");
117
+ while (i < b64.length) {
118
+ const enc1 = chars.indexOf(b64.charAt(i++));
119
+ const enc2 = chars.indexOf(b64.charAt(i++));
120
+ const enc3 = chars.indexOf(b64.charAt(i++));
121
+ const enc4 = chars.indexOf(b64.charAt(i++));
122
+ const chr1 = (enc1 << 2) | (enc2 >> 4);
123
+ const chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
124
+ const chr3 = ((enc3 & 3) << 6) | enc4;
125
+ output += String.fromCharCode(chr1);
126
+ if (enc3 !== 64 && enc3 !== -1)
127
+ output += String.fromCharCode(chr2);
128
+ if (enc4 !== 64 && enc4 !== -1)
129
+ output += String.fromCharCode(chr3);
130
+ }
131
+ return output;
132
+ }
133
+ async function sign(payload, opts) {
134
+ const alg = (opts?.algorithm || "HS256");
135
+ const secret = opts?.secret || globalThis.JWT_SECRET || "tezx_secret";
136
+ const header = { alg, typ: "JWT" };
137
+ const now = Math.floor(Date.now() / 1000);
138
+ const exp = typeof opts?.expiresIn === "string"
139
+ ? now + parseExpiry(opts.expiresIn)
140
+ : typeof opts?.expiresIn === "number"
141
+ ? now + opts.expiresIn
142
+ : now + 86400;
143
+ const fullPayload = { ...payload, iat: now, exp };
144
+ const encodedHeader = base64urlEncodeString(JSON.stringify(header));
145
+ const encodedPayload = base64urlEncodeString(JSON.stringify(fullPayload));
146
+ const data = `${encodedHeader}.${encodedPayload}`;
147
+ const key = await importKey(secret, alg);
148
+ const sigU8 = await hmacSign(key, data);
149
+ const signature = base64urlFromUint8(sigU8);
150
+ return `${data}.${signature}`;
151
+ }
152
+ async function verify(token, secret) {
153
+ try {
154
+ const parts = token.split(".");
155
+ if (parts.length !== 3)
156
+ return null;
157
+ const [eh, ep, sig] = parts;
158
+ const data = `${eh}.${ep}`;
159
+ const headerJson = base64urlDecodeToString(eh);
160
+ const header = JSON.parse(headerJson);
161
+ const alg = header?.alg === "HS512" ? "HS512" : "HS256";
162
+ const s = secret || globalThis.JWT_SECRET || "tezx_secret";
163
+ const key = await importKey(s, alg);
164
+ const expectedSigU8 = await hmacSign(key, data);
165
+ const expectedSig = base64urlFromUint8(expectedSigU8);
166
+ if (!constantTimeEqual(expectedSig, sig))
167
+ return null;
168
+ const payloadJson = base64urlDecodeToString(ep);
169
+ const payload = JSON.parse(payloadJson);
170
+ if (payload.exp && Date.now() / 1000 > payload.exp)
171
+ return null;
172
+ return payload;
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ }
178
+ exports.default = { sign, verify };
@@ -1,45 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.basicAuth = void 0;
4
- const node_buffer_1 = require("node:buffer");
4
+ const error_js_1 = require("../core/error.js");
5
+ const buffer_js_1 = require("../utils/buffer.js");
5
6
  const basicAuth = (options) => {
6
7
  const { validate, realm = "Restricted Area", onUnauthorized = (ctx, error) => {
7
8
  ctx.setStatus = 401;
8
- ctx.setHeader("WWW-Authenticate", `Basic realm="${realm}"`);
9
+ ctx.headers.set("WWW-Authenticate", `Basic realm="${realm}"`);
9
10
  return ctx.json({ error: error?.message || "Unauthorized" });
10
11
  }, } = options;
11
12
  return async (ctx, next) => {
12
13
  const auth = ctx.req.header("authorization");
13
14
  if (!auth || !auth.startsWith("Basic ")) {
14
- return onUnauthorized(ctx, new Error("Basic authentication required"));
15
+ return onUnauthorized(ctx, new error_js_1.TezXError("Basic authentication required"));
15
16
  }
16
17
  const base64 = auth.slice(6).trim();
17
18
  if (!base64) {
18
- return onUnauthorized(ctx, new Error("Empty credentials"));
19
+ return onUnauthorized(ctx, new error_js_1.TezXError("Empty credentials"));
19
20
  }
20
21
  let username, password;
21
22
  try {
22
- const decoded = node_buffer_1.Buffer.from(base64, "base64").toString("utf-8");
23
+ const decoded = (0, buffer_js_1.base64Decode)(base64);
23
24
  const idx = decoded.indexOf(":");
24
25
  if (idx === -1)
25
- throw new Error("Missing colon in credentials");
26
+ throw new error_js_1.TezXError("Missing colon in credentials", 400);
26
27
  username = decoded.slice(0, idx);
27
28
  password = decoded.slice(idx + 1);
28
- }
29
- catch (err) {
30
- return onUnauthorized(ctx, new Error("Invalid Basic auth format"));
31
- }
32
- try {
33
29
  const valid = await validate(username, password, ctx);
34
30
  if (!valid) {
35
- return onUnauthorized(ctx, new Error("Invalid username or password"));
31
+ return onUnauthorized(ctx, new error_js_1.TezXError("Invalid username or password", 403));
36
32
  }
37
33
  ctx.user = { username };
38
34
  await next();
39
35
  }
40
36
  catch (err) {
41
- const error = err instanceof Error ? err : new Error(String(err));
42
- return onUnauthorized(ctx, error);
37
+ return onUnauthorized(ctx, (0, error_js_1.TezXErrorParse)(err));
43
38
  }
44
39
  };
45
40
  };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bearerAuth = void 0;
4
+ const error_js_1 = require("../core/error.js");
4
5
  const bearerAuth = (options) => {
5
6
  const { validate, realm = "API", onUnauthorized = (ctx, error) => {
6
7
  ctx.setStatus = 401;
@@ -10,23 +11,22 @@ const bearerAuth = (options) => {
10
11
  return async (ctx, next) => {
11
12
  const auth = ctx.req.header("authorization");
12
13
  if (!auth || !auth.startsWith("Bearer ")) {
13
- return onUnauthorized(ctx, new Error("Bearer token required"));
14
+ return onUnauthorized(ctx, new error_js_1.TezXError("Bearer token required"));
14
15
  }
15
16
  const token = auth.slice(7).trim();
16
17
  if (!token) {
17
- return onUnauthorized(ctx, new Error("Empty token"));
18
+ return onUnauthorized(ctx, new error_js_1.TezXError("Empty token"));
18
19
  }
19
20
  try {
20
21
  const valid = await validate(token, ctx);
21
22
  if (!valid) {
22
- return onUnauthorized(ctx, new Error("Invalid or expired token"));
23
+ return onUnauthorized(ctx, new error_js_1.TezXError("Invalid or expired token"));
23
24
  }
24
25
  ctx.token = token;
25
26
  await next();
26
27
  }
27
28
  catch (err) {
28
- const error = err instanceof Error ? err : new Error(String(err));
29
- return onUnauthorized(ctx, error);
29
+ return onUnauthorized(ctx, (0, error_js_1.TezXErrorParse)(err));
30
30
  }
31
31
  };
32
32
  };
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cacheControl = void 0;
4
+ const error_js_1 = require("../core/error.js");
5
+ const cacheControl = (opts) => {
6
+ const { defaultSettings, rules = [], onError = (err, ctx) => {
7
+ ctx.setStatus = 500;
8
+ ctx.body = { error: err.message ?? "Cache middleware failed" };
9
+ }, } = opts;
10
+ const len = rules.length | 0;
11
+ return async function cacheControlMiddleware(ctx, next) {
12
+ const method = ctx.method;
13
+ if (method !== "GET" && method !== "HEAD") {
14
+ return await next();
15
+ }
16
+ try {
17
+ await next();
18
+ let matched;
19
+ for (let i = 0; i < len; i++) {
20
+ const rule = rules[i];
21
+ if (rule.condition(ctx)) {
22
+ matched = rule;
23
+ break;
24
+ }
25
+ }
26
+ const settings = matched ?? defaultSettings;
27
+ const maxAge = settings.maxAge;
28
+ const scope = settings.scope;
29
+ const vary = settings.vary;
30
+ const cacheValue = `${scope}, max-age=${maxAge}`;
31
+ const expiresValue = new Date(Date.now() + maxAge * 1000).toUTCString();
32
+ const headers = ctx.headers;
33
+ headers.set("Cache-Control", cacheValue);
34
+ headers.set("Expires", expiresValue);
35
+ if (vary && vary.length > 0)
36
+ headers.set("Vary", vary.join(", "));
37
+ }
38
+ catch (err) {
39
+ return onError((0, error_js_1.TezXErrorParse)(err), ctx);
40
+ }
41
+ };
42
+ };
43
+ exports.cacheControl = cacheControl;
44
+ exports.default = exports.cacheControl;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.cors = cors;
4
4
  exports.default = cors;
5
5
  function cors(option = {}) {
6
- const { credentials, maxAge, origin, } = option;
6
+ const { credentials, maxAge, origin } = option;
7
7
  let methods = (option.methods || ["GET", "POST", "PUT", "DELETE"]).join(", ");
8
8
  let allowedHeaders = (option.allowedHeaders || ["Content-Type", "Authorization"]).join(", ");
9
9
  let exposedHeaders = option?.exposedHeaders?.join(", ");
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectBot = void 0;
4
+ const config_js_1 = require("../core/config.js");
5
+ const rateLimit_js_1 = require("../utils/rateLimit.js");
6
+ const runtime_js_1 = require("../utils/runtime.js");
7
+ const detectBot = (opts = {}) => {
8
+ const botUAs = opts.botUserAgents || ["bot", "spider", "crawl", "slurp"];
9
+ let checkBot;
10
+ if (runtime_js_1.runtime === "bun") {
11
+ const botRegex = new RegExp(botUAs.join("|"), "i");
12
+ checkBot = (ua) => botRegex.test(ua);
13
+ }
14
+ else {
15
+ checkBot = (ua) => {
16
+ for (const b of botUAs)
17
+ if (ua.includes(b))
18
+ return true;
19
+ return false;
20
+ };
21
+ }
22
+ const maxReq = opts.maxRequests || 30;
23
+ const winMs = opts.windowMs || 60000;
24
+ const enableRL = !!opts.enableRateLimiting;
25
+ const onBot = opts.onBotDetected ??
26
+ ((ctx, reason) => ctx.status(403).json({ error: `Bot detected: ${reason}` }));
27
+ let store = opts.storage;
28
+ if (enableRL && !store)
29
+ store = (0, rateLimit_js_1.createRateLimitDefaultStorage)();
30
+ return async (ctx, next) => {
31
+ const ua = ctx.headers.get("user-agent") || "";
32
+ if (checkBot(ua)) {
33
+ return onBot?.(ctx, "User-Agent");
34
+ }
35
+ if (await opts?.isBlacklisted?.(ctx)) {
36
+ return onBot?.(ctx, "Blacklisted IP");
37
+ }
38
+ if (enableRL) {
39
+ const addr = ctx.req.remoteAddress;
40
+ if (!addr) {
41
+ config_js_1.GlobalConfig.debugging.warn("[TezX detectBot] Missing remoteAddress. Use `getConnInfo(ctx)` to enable rate limiting.");
42
+ }
43
+ else {
44
+ const key = `${addr.address}:${addr.port || 0}`;
45
+ const { check, entry } = (0, rateLimit_js_1.isRateLimit)(key, store, maxReq, winMs);
46
+ if (check && addr.address) {
47
+ return onBot?.(ctx, `Rate limit exceeded. Retry after ${Math.ceil((entry.resetTime - Date.now()) / 1000)} seconds.`);
48
+ }
49
+ }
50
+ }
51
+ if (await opts.customBotDetector?.(ctx)) {
52
+ return onBot?.(ctx, "Custom Detector");
53
+ }
54
+ return await next();
55
+ };
56
+ };
57
+ exports.detectBot = detectBot;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.i18n = exports.default = void 0;
4
+ const index_js_1 = require("../index.js");
5
+ const i18n = (options) => {
6
+ const { loadTranslations, defaultCacheDuration = 3600000, detectLanguage, defaultLanguage = "en", translationFunctionKey = "t", formatMessage = (msg, vars = {}) => {
7
+ if (vars && msg.indexOf("{{") !== -1) {
8
+ let out = "";
9
+ let i = 0;
10
+ while (i < msg.length) {
11
+ const startVar = msg.indexOf("{{", i);
12
+ if (startVar === -1) {
13
+ out += msg.slice(i);
14
+ break;
15
+ }
16
+ const endVar = msg.indexOf("}}", startVar + 2);
17
+ if (endVar === -1) {
18
+ out += msg.slice(i);
19
+ break;
20
+ }
21
+ out += msg.slice(i, startVar);
22
+ const keyVar = msg.slice(startVar + 2, endVar).trim();
23
+ out += vars[keyVar] !== undefined ? String(vars[keyVar]) : "";
24
+ i = endVar + 2;
25
+ }
26
+ return out;
27
+ }
28
+ return msg;
29
+ }, isCacheValid = (cached) => cached.expiresAt > Date.now(), cacheTranslations = false, cacheStorage: externalCache = null, } = options;
30
+ let localCache = null;
31
+ if (cacheTranslations && !externalCache)
32
+ localCache = new Map();
33
+ return async function i18n(ctx, next) {
34
+ try {
35
+ const lang = detectLanguage(ctx) ?? defaultLanguage;
36
+ let translations = undefined;
37
+ if (cacheTranslations) {
38
+ if (externalCache) {
39
+ const cached = await externalCache.get(lang);
40
+ if (cached && isCacheValid(cached, lang))
41
+ translations = cached.translations;
42
+ }
43
+ else if (localCache?.get(lang)) {
44
+ const cached = localCache.get(lang);
45
+ if (isCacheValid(cached, lang))
46
+ translations = cached.translations;
47
+ else
48
+ localCache.delete(lang);
49
+ }
50
+ }
51
+ if (!translations) {
52
+ translations = await loadTranslations(lang);
53
+ const expiresAt = Date.now() + defaultCacheDuration;
54
+ if (cacheTranslations) {
55
+ if (externalCache) {
56
+ await externalCache.set(lang, { translations, expiresAt });
57
+ }
58
+ else {
59
+ localCache?.set(lang, { translations, expiresAt });
60
+ }
61
+ }
62
+ }
63
+ ctx[translationFunctionKey] = function translate(key, vars) {
64
+ let acc = translations;
65
+ let start = 0;
66
+ for (let i = 0; i <= key.length; i++) {
67
+ const c = key.charCodeAt(i);
68
+ if (c === 46 || i === key.length) {
69
+ const segment = key.slice(start, i);
70
+ if (acc && typeof acc === "object") {
71
+ acc = acc[segment];
72
+ }
73
+ else {
74
+ acc = undefined;
75
+ break;
76
+ }
77
+ start = i + 1;
78
+ }
79
+ }
80
+ let msg = typeof acc === "string" ? acc : key;
81
+ return formatMessage(msg, vars);
82
+ };
83
+ ctx.language = lang;
84
+ return await next();
85
+ }
86
+ catch (error) {
87
+ throw new index_js_1.TezXError(error?.message ?? "i18n failed", 500, error?.stack);
88
+ }
89
+ };
90
+ };
91
+ exports.default = i18n;
92
+ exports.i18n = i18n;
@@ -16,10 +16,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./basic-auth.js"), exports);
18
18
  __exportStar(require("./bearer-auth.js"), exports);
19
+ __exportStar(require("./cache-control.js"), exports);
19
20
  __exportStar(require("./cors.js"), exports);
21
+ __exportStar(require("./detect-bot.js"), exports);
22
+ __exportStar(require("./i18n.js"), exports);
20
23
  __exportStar(require("./logger.js"), exports);
21
24
  __exportStar(require("./pagination.js"), exports);
22
25
  __exportStar(require("./powered-by.js"), exports);
26
+ __exportStar(require("./rate-limiter.js"), exports);
23
27
  __exportStar(require("./request-id.js"), exports);
24
28
  __exportStar(require("./sanitize-headers.js"), exports);
29
+ __exportStar(require("./secure-headers.js"), exports);
25
30
  __exportStar(require("./xss-protection.js"), exports);
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logger = logger;
4
3
  exports.default = logger;
4
+ exports.logger = logger;
5
+ const error_js_1 = require("../core/error.js");
5
6
  const colors_js_1 = require("../utils/colors.js");
6
7
  function logger(options = { enabled: true }) {
7
8
  return async function logger(ctx, next) {
@@ -19,7 +20,7 @@ function logger(options = { enabled: true }) {
19
20
  }
20
21
  catch (err) {
21
22
  console.error(`${(0, colors_js_1.colorText)("Error:", "red")}`, err.stack);
22
- throw new Error(err.stack);
23
+ throw new error_js_1.TezXError(err.stack);
23
24
  }
24
25
  };
25
26
  }
@@ -26,7 +26,7 @@ const rateLimiter = (options) => {
26
26
  if (check) {
27
27
  const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
28
28
  ctx.headers.set("Retry-After", retryAfter.toString());
29
- return onError(ctx, retryAfter, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
29
+ return onError(ctx, retryAfter, new error_js_1.TezXError(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
30
30
  }
31
31
  ctx.headers.set("X-RateLimit-Limit", maxRequests.toString());
32
32
  ctx.headers.set("X-RateLimit-Remaining", (maxRequests - entry.count).toString());
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sanitizeHeaders = exports.default = void 0;
4
4
  const sanitizeHeaders = (options = {}) => {
5
- const { whitelist = [], blacklist = [], } = options;
5
+ const { whitelist = [], blacklist = [] } = options;
6
6
  const normalizedWhitelist = whitelist.map((h) => h.toLowerCase());
7
7
  const normalizedBlacklist = blacklist.map((h) => h.toLowerCase());
8
8
  let lWhite = normalizedWhitelist.length;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.secureHeaders = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const joinSrc = (v) => typeof v === "string" ? v : v.join(" ");
9
+ const buildCSPString = (cspObj) => {
10
+ const parts = [];
11
+ for (const key in cspObj) {
12
+ parts.push(`${key} ${joinSrc(cspObj[key])}`);
13
+ }
14
+ return parts.join("; ");
15
+ };
16
+ const defaultPresets = {
17
+ strict: {
18
+ preset: "strict",
19
+ hsts: true,
20
+ hstsMaxAge: 63072000,
21
+ frameGuard: "DENY",
22
+ noSniff: true,
23
+ xssProtection: true,
24
+ referrerPolicy: "strict-origin-when-cross-origin",
25
+ permissionsPolicy: "geolocation=(), microphone=(), camera=(), usb=()",
26
+ csp: {
27
+ "default-src": ["'self'"],
28
+ "script-src": ["'self'"],
29
+ "style-src": ["'self'", "'unsafe-inline'"],
30
+ "img-src": ["'self'", "data:", "blob:"],
31
+ "font-src": ["'self'"],
32
+ "connect-src": ["'self'"],
33
+ "object-src": ["'none'"],
34
+ "frame-ancestors": ["'none'"],
35
+ },
36
+ cspReportOnly: false,
37
+ },
38
+ balanced: {
39
+ preset: "balanced",
40
+ hsts: true,
41
+ hstsMaxAge: 31536000,
42
+ frameGuard: "SAMEORIGIN",
43
+ noSniff: true,
44
+ xssProtection: true,
45
+ referrerPolicy: "no-referrer-when-downgrade",
46
+ permissionsPolicy: "geolocation=(), microphone=()",
47
+ csp: {
48
+ "default-src": ["'self'"],
49
+ "script-src": ["'self'", "https://cdn.jsdelivr.net"],
50
+ "style-src": [
51
+ "'self'",
52
+ "'unsafe-inline'",
53
+ "https://fonts.googleapis.com",
54
+ ],
55
+ "img-src": ["'self'", "data:", "https://images.example.com"],
56
+ "connect-src": ["'self'", "https://api.example.com"],
57
+ },
58
+ cspReportOnly: true,
59
+ },
60
+ dev: {
61
+ preset: "dev",
62
+ hsts: false,
63
+ frameGuard: "SAMEORIGIN",
64
+ noSniff: false,
65
+ xssProtection: false,
66
+ referrerPolicy: "no-referrer",
67
+ permissionsPolicy: "",
68
+ csp: {
69
+ "default-src": [
70
+ "'self'",
71
+ "'unsafe-inline'",
72
+ "'unsafe-eval'",
73
+ "http://localhost:3000",
74
+ ],
75
+ "img-src": ["'self'", "data:", "blob:"],
76
+ },
77
+ cspReportOnly: true,
78
+ },
79
+ };
80
+ const setHeader = (ctx, name, value) => {
81
+ if (typeof ctx.setHeader === "function")
82
+ ctx.setHeader(name, value);
83
+ else if (ctx.response?.setHeader)
84
+ ctx.response.setHeader(name, value);
85
+ else if (ctx.headersOut)
86
+ ctx.headersOut[name] = value;
87
+ };
88
+ const secureHeaders = (userOpts = {}) => {
89
+ const preset = userOpts.preset ?? "balanced";
90
+ const base = {
91
+ ...(defaultPresets[preset] || defaultPresets.balanced),
92
+ ...userOpts,
93
+ };
94
+ const hstsHeader = base.hsts
95
+ ? `max-age=${base.hstsMaxAge || 31536000}; includeSubDomains; preload`
96
+ : "";
97
+ const frameHeader = base.frameGuard || "SAMEORIGIN";
98
+ const noSniffHeader = base.noSniff ? "nosniff" : "";
99
+ const xssHeader = base.xssProtection ? "1; mode=block" : "0";
100
+ const referrerHeader = base.referrerPolicy || "no-referrer";
101
+ const permissionsHeader = base.permissionsPolicy || "";
102
+ let cspStatic = null;
103
+ let cspNeedsNonce = !!base.cspUseNonce;
104
+ if (typeof base.csp === "string")
105
+ cspStatic = base.csp;
106
+ else if (base.csp && typeof base.csp === "object")
107
+ cspStatic = buildCSPString(base.csp);
108
+ if (base.ultraFastMode)
109
+ cspNeedsNonce = false;
110
+ const cspReportOnly = !!base.cspReportOnly;
111
+ return async (ctx, next) => {
112
+ try {
113
+ if (base.hsts)
114
+ setHeader(ctx, "Strict-Transport-Security", hstsHeader);
115
+ setHeader(ctx, "X-Frame-Options", frameHeader);
116
+ setHeader(ctx, "X-Content-Type-Options", noSniffHeader);
117
+ setHeader(ctx, "X-XSS-Protection", xssHeader);
118
+ setHeader(ctx, "Referrer-Policy", referrerHeader);
119
+ if (permissionsHeader)
120
+ setHeader(ctx, "Permissions-Policy", permissionsHeader);
121
+ if (cspNeedsNonce) {
122
+ const nonce = crypto_1.default.randomBytes(12).toString("base64");
123
+ let cspHeader = cspStatic || `default-src 'self'; script-src 'self' 'nonce-${nonce}'`;
124
+ if (cspReportOnly)
125
+ setHeader(ctx, "Content-Security-Policy-Report-Only", cspHeader);
126
+ else
127
+ setHeader(ctx, "Content-Security-Policy", cspHeader);
128
+ ctx.cspNonce = nonce;
129
+ }
130
+ else if (cspStatic) {
131
+ if (cspReportOnly)
132
+ setHeader(ctx, "Content-Security-Policy-Report-Only", cspStatic);
133
+ else
134
+ setHeader(ctx, "Content-Security-Policy", cspStatic);
135
+ }
136
+ return await next();
137
+ }
138
+ catch {
139
+ return await next();
140
+ }
141
+ };
142
+ };
143
+ exports.secureHeaders = secureHeaders;