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
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { Context } from "../core/context.js";
|
|
2
|
-
import { CallbackReturn, Middleware } from "../types/index.js";
|
|
3
|
-
/**
|
|
4
|
-
* Supported authentication method types.
|
|
5
|
-
*/
|
|
6
|
-
export type AuthMethod = "basic" | "api-key" | "bearer-token";
|
|
7
|
-
export type AuthCredential = {
|
|
8
|
-
username?: any;
|
|
9
|
-
password?: any;
|
|
10
|
-
token?: any;
|
|
11
|
-
apiKey?: any;
|
|
12
|
-
};
|
|
13
|
-
/**
|
|
14
|
-
* Configuration options for dynamic basic authentication.
|
|
15
|
-
*/
|
|
16
|
-
export type DynamicBasicAuthOptions = {
|
|
17
|
-
/**
|
|
18
|
-
* 🔐 Function to validate the provided credentials.
|
|
19
|
-
* @param method - The method of authentication.
|
|
20
|
-
* @param credentials - The extracted credentials.
|
|
21
|
-
* @param ctx - The current request context.
|
|
22
|
-
* @returns A boolean or Promise resolving to whether the credentials are valid.
|
|
23
|
-
*/
|
|
24
|
-
validateCredentials: (method: AuthMethod, credentials: AuthCredential, ctx: Context) => boolean | Promise<boolean>;
|
|
25
|
-
/**
|
|
26
|
-
* 🔒 Function to dynamically determine the realm for authentication prompt.
|
|
27
|
-
* @param ctx - The current request context.
|
|
28
|
-
* @returns The authentication realm string.
|
|
29
|
-
*/
|
|
30
|
-
getRealm?: (ctx: Context) => string;
|
|
31
|
-
/**
|
|
32
|
-
* ❌ Custom handler for unauthorized access.
|
|
33
|
-
* @param ctx - The current request context.
|
|
34
|
-
* @param error - Optional error information.
|
|
35
|
-
* @returns A CallbackReturn to end the response.
|
|
36
|
-
*/
|
|
37
|
-
onUnauthorized?: (ctx: Context, error?: Error) => CallbackReturn;
|
|
38
|
-
/**
|
|
39
|
-
* 🚦 Rate-limiting configuration.
|
|
40
|
-
*/
|
|
41
|
-
rateLimit?: {
|
|
42
|
-
/**
|
|
43
|
-
* 🧠 Custom cache or storage for rate-limit tracking.
|
|
44
|
-
*/
|
|
45
|
-
storage?: {
|
|
46
|
-
get: (key: string) => {
|
|
47
|
-
count: number;
|
|
48
|
-
resetTime: number;
|
|
49
|
-
} | undefined;
|
|
50
|
-
set: (key: string, value: {
|
|
51
|
-
count: number;
|
|
52
|
-
resetTime: number;
|
|
53
|
-
}) => void;
|
|
54
|
-
clearExpired: () => void;
|
|
55
|
-
};
|
|
56
|
-
/** 🔁 Max requests allowed within the window */
|
|
57
|
-
maxRequests: number;
|
|
58
|
-
/** ⏲️ Duration of window in milliseconds */
|
|
59
|
-
windowMs: number;
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* 🛠 Supported authentication types.
|
|
63
|
-
* @default ["basic"]
|
|
64
|
-
*/
|
|
65
|
-
supportedMethods?: AuthMethod[];
|
|
66
|
-
/**
|
|
67
|
-
* 🧑⚖️ Optional RBAC (Role-Based Access Control) check.
|
|
68
|
-
* @param ctx - The current request context.
|
|
69
|
-
* @param credentials - The validated credentials.
|
|
70
|
-
* @returns Whether access is allowed.
|
|
71
|
-
*/
|
|
72
|
-
checkAccess?: (ctx: Context, credentials: AuthCredential) => boolean | Promise<boolean>;
|
|
73
|
-
};
|
|
74
|
-
/**
|
|
75
|
-
* 🔐 Middleware for flexible authentication using Basic, API Key, or Bearer Token.
|
|
76
|
-
* Supports rate limiting, IP filtering, and role-based access control.
|
|
77
|
-
*
|
|
78
|
-
* @param options - Custom authentication handler options.
|
|
79
|
-
* @returns A middleware function.
|
|
80
|
-
*/
|
|
81
|
-
export declare const basicAuth: (options: DynamicBasicAuthOptions) => Middleware;
|
package/middleware/basicAuth.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
import { colorText } from "../utils/colors.js";
|
|
3
|
-
import { createRateLimitDefaultStorage, isRateLimit } from "./detectBot.js";
|
|
4
|
-
export const basicAuth = (options) => {
|
|
5
|
-
const { validateCredentials, getRealm = () => "Restricted Area", onUnauthorized = (ctx, error) => {
|
|
6
|
-
const realm = getRealm(ctx);
|
|
7
|
-
ctx.setStatus = 401;
|
|
8
|
-
ctx.header("WWW-Authenticate", `Basic realm="${realm}"`);
|
|
9
|
-
ctx.body = { error: error?.message };
|
|
10
|
-
}, rateLimit, supportedMethods = ["basic", "api-key", "bearer-token"], checkAccess, } = options;
|
|
11
|
-
let storage = rateLimit?.storage;
|
|
12
|
-
if (rateLimit && !rateLimit.storage) {
|
|
13
|
-
storage = createRateLimitDefaultStorage();
|
|
14
|
-
}
|
|
15
|
-
return async function basicAuth(ctx, next) {
|
|
16
|
-
let authMethod;
|
|
17
|
-
let credentials = {};
|
|
18
|
-
const authHeader = ctx.req.headers.get("authorization");
|
|
19
|
-
if (authHeader) {
|
|
20
|
-
if (authHeader.startsWith("Basic ")) {
|
|
21
|
-
authMethod = "basic";
|
|
22
|
-
const base64Credentials = authHeader.split(" ")[1];
|
|
23
|
-
const decoded = Buffer.from(base64Credentials, "base64").toString("utf-8");
|
|
24
|
-
const [username, password] = decoded.split(":");
|
|
25
|
-
credentials = { username, password };
|
|
26
|
-
}
|
|
27
|
-
else if (authHeader.startsWith("Bearer ")) {
|
|
28
|
-
authMethod = "bearer-token";
|
|
29
|
-
credentials = { token: authHeader.split(" ")[1] };
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
else if (ctx.headers.get("x-api-key")) {
|
|
33
|
-
authMethod = "api-key";
|
|
34
|
-
credentials = { apiKey: ctx.headers.get("x-api-key") };
|
|
35
|
-
}
|
|
36
|
-
if (!authMethod || !supportedMethods.includes(authMethod)) {
|
|
37
|
-
GlobalConfig.debugging.error(`${colorText("[AUTH]", "bgRed")} Unsupported or missing authentication method.`);
|
|
38
|
-
return onUnauthorized(ctx, new Error("Unsupported authentication method"));
|
|
39
|
-
}
|
|
40
|
-
if (rateLimit) {
|
|
41
|
-
let key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`;
|
|
42
|
-
const { check, entry } = isRateLimit(ctx, key, storage, rateLimit.maxRequests, rateLimit.windowMs);
|
|
43
|
-
if (check) {
|
|
44
|
-
const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
|
|
45
|
-
ctx.headers.set("Retry-After", retryAfter.toString());
|
|
46
|
-
return onUnauthorized(ctx, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const isValid = await validateCredentials(authMethod, credentials, ctx);
|
|
51
|
-
if (!isValid) {
|
|
52
|
-
throw new Error("Invalid credentials.");
|
|
53
|
-
}
|
|
54
|
-
if (checkAccess) {
|
|
55
|
-
const hasAccess = await checkAccess(ctx, credentials);
|
|
56
|
-
if (!hasAccess) {
|
|
57
|
-
return onUnauthorized(ctx, new Error("Access denied."));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return await next();
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
GlobalConfig.debugging.error(`${colorText("[AUTH]", "bgRed")} Failure for method: ${ctx.method}`);
|
|
64
|
-
return onUnauthorized(ctx, error);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { Context } from "../core/context.js";
|
|
2
|
-
import { Middleware } from "../types/index.js";
|
|
3
|
-
export type CacheRule = {
|
|
4
|
-
/**
|
|
5
|
-
* 🎯 Condition to determine if this rule applies.
|
|
6
|
-
*/
|
|
7
|
-
condition: (ctx: Context) => boolean;
|
|
8
|
-
/**
|
|
9
|
-
* ⏳ Maximum age (in seconds) for caching.
|
|
10
|
-
*/
|
|
11
|
-
maxAge: number;
|
|
12
|
-
/**
|
|
13
|
-
* 🌐 Cache scope: "public" or "private".
|
|
14
|
-
*/
|
|
15
|
-
scope: "public" | "private";
|
|
16
|
-
/**
|
|
17
|
-
* 🔄 Enable or disable revalidation with ETag.
|
|
18
|
-
*/
|
|
19
|
-
enableETag: boolean;
|
|
20
|
-
/**
|
|
21
|
-
* 🏷️ Vary header for cache variations.
|
|
22
|
-
*/
|
|
23
|
-
vary?: string[];
|
|
24
|
-
};
|
|
25
|
-
export type CacheSettings = Pick<CacheRule, "maxAge" | "scope" | "enableETag" | "vary">;
|
|
26
|
-
export type CacheOptions = {
|
|
27
|
-
/**
|
|
28
|
-
* 🧪 Weak ETag generation (optional).
|
|
29
|
-
*/
|
|
30
|
-
useWeakETag?: boolean;
|
|
31
|
-
/**
|
|
32
|
-
* 📝 Logging function for cache events.
|
|
33
|
-
*/
|
|
34
|
-
logEvent?: (event: "cached" | "no-cache" | "error", ctx: Context, error?: Error) => void;
|
|
35
|
-
/**
|
|
36
|
-
* 🛠️ Default cache settings.
|
|
37
|
-
*/
|
|
38
|
-
defaultSettings: CacheSettings;
|
|
39
|
-
/**
|
|
40
|
-
* 🔧 Custom rules for dynamic caching behavior.
|
|
41
|
-
*/
|
|
42
|
-
rules?: CacheRule[];
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* Middleware to manage HTTP caching headers dynamically.
|
|
46
|
-
* @param options - Custom options for dynamic caching behavior.
|
|
47
|
-
*/
|
|
48
|
-
export declare const cacheControl: (options: CacheOptions) => Middleware;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
export const cacheControl = (options) => {
|
|
3
|
-
const { defaultSettings, useWeakETag = false, rules = [], logEvent = (event, ctx, error) => {
|
|
4
|
-
if (event === "error") {
|
|
5
|
-
GlobalConfig.debugging.error(`[CACHE] ${event.toUpperCase()}: ${error?.message}`);
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
GlobalConfig.debugging.success(`[CACHE] ${event.toUpperCase()} for ${ctx.method} ${ctx.pathname}`);
|
|
9
|
-
}
|
|
10
|
-
}, } = options;
|
|
11
|
-
return async function cacheControl(ctx, next) {
|
|
12
|
-
if (!["GET", "HEAD"].includes(ctx.method)) {
|
|
13
|
-
return await next();
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
await next();
|
|
17
|
-
const matchedRule = rules.find((rule) => rule.condition(ctx)) || null;
|
|
18
|
-
const { maxAge, scope, enableETag, vary } = matchedRule || defaultSettings;
|
|
19
|
-
const cacheControlValue = `${scope}, max-age=${maxAge}`;
|
|
20
|
-
ctx.header("Cache-Control", cacheControlValue);
|
|
21
|
-
const expiresDate = new Date(Date.now() + maxAge * 1000).toUTCString();
|
|
22
|
-
ctx.header("Expires", expiresDate);
|
|
23
|
-
if (vary?.length) {
|
|
24
|
-
ctx.header("Vary", vary.join(", "));
|
|
25
|
-
}
|
|
26
|
-
if (enableETag) {
|
|
27
|
-
const responseBody = typeof ctx.resBody === "string"
|
|
28
|
-
? ctx.resBody
|
|
29
|
-
: JSON.stringify(ctx.resBody ?? "");
|
|
30
|
-
const etag = await generateETag(responseBody, useWeakETag);
|
|
31
|
-
const ifNoneMatch = ctx.req.headers.get("if-none-match");
|
|
32
|
-
if (ifNoneMatch === etag) {
|
|
33
|
-
ctx.setStatus = 304;
|
|
34
|
-
ctx.body = null;
|
|
35
|
-
logEvent("cached", ctx);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
ctx.header("ETag", etag);
|
|
39
|
-
}
|
|
40
|
-
logEvent("cached", ctx);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
logEvent("error", ctx, error);
|
|
44
|
-
ctx.setStatus = 500;
|
|
45
|
-
ctx.body = { error: "Failed to set cache headers." };
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
const generateETag = async (content, weak = false) => {
|
|
50
|
-
const crypto = await import("node:crypto");
|
|
51
|
-
const hash = crypto.createHash("md5").update(content).digest("hex");
|
|
52
|
-
return weak ? `W/"${hash}"` : `"${hash}"`;
|
|
53
|
-
};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
import { CallbackReturn } from "../types/index.js";
|
|
3
|
-
export type DetectBotReason = "User-Agent" | "Blacklisted IP" | "Query Parameter" | "Rate Limiting" | "Custom Detector" | "Multiple Indicators";
|
|
4
|
-
export type BotDetectionResult = {
|
|
5
|
-
isBot: boolean;
|
|
6
|
-
reason?: DetectBotReason;
|
|
7
|
-
indicators: string[];
|
|
8
|
-
};
|
|
9
|
-
export type DetectBotOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* 🤖 List of bot-like user-agent substrings to detect
|
|
12
|
-
* @default ["bot", "spider", "crawl", "slurp"]
|
|
13
|
-
*/
|
|
14
|
-
botUserAgents?: string[];
|
|
15
|
-
/**
|
|
16
|
-
* ⚠️ Maximum allowed requests in the time window
|
|
17
|
-
* @default 30 requests
|
|
18
|
-
*/
|
|
19
|
-
maxRequests?: number;
|
|
20
|
-
/**
|
|
21
|
-
* ⏱️ Time window in milliseconds for rate limiting
|
|
22
|
-
* @default 60000 (1 minute)
|
|
23
|
-
*/
|
|
24
|
-
windowMs?: number;
|
|
25
|
-
/**
|
|
26
|
-
* 🚫 IP blacklist checker
|
|
27
|
-
* @default () => false
|
|
28
|
-
*/
|
|
29
|
-
isBlacklisted?: (ctx: Context, remoteAddress: string) => boolean | Promise<boolean>;
|
|
30
|
-
/**
|
|
31
|
-
* 🔍 Query parameter name for bot identification
|
|
32
|
-
* @default "bot"
|
|
33
|
-
*/
|
|
34
|
-
queryKeyBot?: string;
|
|
35
|
-
/**
|
|
36
|
-
* 🛡️ Action to take when bot is detected
|
|
37
|
-
* @default "block"
|
|
38
|
-
*/
|
|
39
|
-
onBotDetected?: "block" | ((ctx: Context, result: BotDetectionResult) => CallbackReturn);
|
|
40
|
-
/**
|
|
41
|
-
* ⚖️ Enable rate-limiting based detection
|
|
42
|
-
* @default false
|
|
43
|
-
*/
|
|
44
|
-
enableRateLimiting?: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* 🔎 Custom bot detection logic
|
|
47
|
-
* @default () => false
|
|
48
|
-
*/
|
|
49
|
-
customBotDetector?: (ctx: Context) => boolean | Promise<boolean>;
|
|
50
|
-
/**
|
|
51
|
-
* ✉️ Custom response for blocked requests
|
|
52
|
-
*/
|
|
53
|
-
customBlockedResponse?: (ctx: Context, result: BotDetectionResult) => CallbackReturn;
|
|
54
|
-
/**
|
|
55
|
-
* 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
|
|
56
|
-
* By default, it uses a `Map<string, { count: number; resetTime: number }>`.
|
|
57
|
-
*/
|
|
58
|
-
storage?: {
|
|
59
|
-
get: (key: string) => {
|
|
60
|
-
count: number;
|
|
61
|
-
resetTime: number;
|
|
62
|
-
} | undefined;
|
|
63
|
-
set: (key: string, value: {
|
|
64
|
-
count: number;
|
|
65
|
-
resetTime: number;
|
|
66
|
-
}) => void;
|
|
67
|
-
clearExpired: () => void;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* 📊 Minimum confidence score to consider as bot (0-1)
|
|
71
|
-
* @default 0.5
|
|
72
|
-
*/
|
|
73
|
-
confidenceThreshold?: number;
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* 🤖 Advanced bot detection middleware with multiple detection methods
|
|
77
|
-
*
|
|
78
|
-
* Features:
|
|
79
|
-
* - User-Agent analysis
|
|
80
|
-
* - IP blacklisting
|
|
81
|
-
* - Query parameter detection
|
|
82
|
-
* - Rate limiting
|
|
83
|
-
* - Custom detection logic
|
|
84
|
-
* - Confidence-based scoring
|
|
85
|
-
*
|
|
86
|
-
* @param {DetectBotOptions} options - Configuration options
|
|
87
|
-
* @returns {Middleware} Configured middleware
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* // Basic usage
|
|
91
|
-
* app.use(detectBot());
|
|
92
|
-
*
|
|
93
|
-
* // Custom configuration
|
|
94
|
-
* app.use(detectBot({
|
|
95
|
-
* botUserAgents: ["bot", "crawler"],
|
|
96
|
-
* isBlacklistedIP: async (ip) => await checkIPReputation(ip),
|
|
97
|
-
* onBotDetected: (ctx, { reason }) => {
|
|
98
|
-
* ctx.status = 403;
|
|
99
|
-
* return ctx.json({ error: `Bot detected (${reason})` });
|
|
100
|
-
* }
|
|
101
|
-
* }));
|
|
102
|
-
*/
|
|
103
|
-
export declare const detectBot: (options?: DetectBotOptions) => Middleware;
|
|
104
|
-
export declare function createRateLimitDefaultStorage(): {
|
|
105
|
-
get: (key: string) => {
|
|
106
|
-
count: number;
|
|
107
|
-
resetTime: number;
|
|
108
|
-
} | undefined;
|
|
109
|
-
set: (key: string, value: {
|
|
110
|
-
count: number;
|
|
111
|
-
resetTime: number;
|
|
112
|
-
}) => Map<string, {
|
|
113
|
-
count: number;
|
|
114
|
-
resetTime: number;
|
|
115
|
-
}>;
|
|
116
|
-
clearExpired: () => void;
|
|
117
|
-
};
|
|
118
|
-
export declare function isRateLimit(ctx: Context, key: string, store: any, maxRequests: number, windowMs: number): {
|
|
119
|
-
check: boolean;
|
|
120
|
-
entry: any;
|
|
121
|
-
};
|
package/middleware/detectBot.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
export const detectBot = (options = {}) => {
|
|
3
|
-
const { botUserAgents = ["bot", "spider", "crawl", "slurp"], maxRequests = 30, windowMs = 60000, isBlacklisted = async () => false, queryKeyBot = "bot", onBotDetected = "block", enableRateLimiting = false, customBotDetector = async () => false, customBlockedResponse = (ctx, { reason }) => {
|
|
4
|
-
ctx.setStatus = 403;
|
|
5
|
-
return ctx.json({ error: `Bot detected: ${reason}` });
|
|
6
|
-
}, storage, confidenceThreshold = 0.5, } = options;
|
|
7
|
-
let store = storage;
|
|
8
|
-
if (enableRateLimiting) {
|
|
9
|
-
store = createRateLimitDefaultStorage();
|
|
10
|
-
}
|
|
11
|
-
return async function detectBot(ctx, next) {
|
|
12
|
-
const detectionResult = {
|
|
13
|
-
isBot: false,
|
|
14
|
-
indicators: [],
|
|
15
|
-
};
|
|
16
|
-
const userAgent = ctx.headers.get("user-agent")?.toLowerCase() || "";
|
|
17
|
-
const remoteAddress = `${ctx.req.remoteAddress?.address}:${ctx.req.remoteAddress?.port}` ||
|
|
18
|
-
"unknown";
|
|
19
|
-
const isBotQuery = ctx.req.query[queryKeyBot] === "true";
|
|
20
|
-
if (botUserAgents.some((agent) => userAgent.includes(agent))) {
|
|
21
|
-
detectionResult.indicators.push("User-Agent");
|
|
22
|
-
}
|
|
23
|
-
if (await isBlacklisted(ctx, remoteAddress)) {
|
|
24
|
-
detectionResult.indicators.push("Blacklisted IP");
|
|
25
|
-
}
|
|
26
|
-
if (isBotQuery) {
|
|
27
|
-
detectionResult.indicators.push("Query Parameter");
|
|
28
|
-
}
|
|
29
|
-
const key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`;
|
|
30
|
-
if (enableRateLimiting &&
|
|
31
|
-
isRateLimit(ctx, key, store, maxRequests, windowMs).check) {
|
|
32
|
-
detectionResult.indicators.push("Rate Limiting");
|
|
33
|
-
}
|
|
34
|
-
if (await customBotDetector(ctx)) {
|
|
35
|
-
detectionResult.indicators.push("Custom Detector");
|
|
36
|
-
}
|
|
37
|
-
detectionResult.isBot = detectionResult.indicators.length > 0;
|
|
38
|
-
if (detectionResult.indicators.length > 1) {
|
|
39
|
-
detectionResult.reason = "Multiple Indicators";
|
|
40
|
-
const confidence = Math.min(0.3 * detectionResult.indicators.length, 1);
|
|
41
|
-
detectionResult.isBot = confidence >= confidenceThreshold;
|
|
42
|
-
}
|
|
43
|
-
else if (detectionResult.indicators.length === 1) {
|
|
44
|
-
detectionResult.reason = detectionResult.indicators[0];
|
|
45
|
-
}
|
|
46
|
-
if (detectionResult.isBot) {
|
|
47
|
-
GlobalConfig.debugging.warn(`Bot detected: ${detectionResult.reason}`, {
|
|
48
|
-
ip: remoteAddress,
|
|
49
|
-
userAgent,
|
|
50
|
-
indicators: detectionResult.indicators,
|
|
51
|
-
});
|
|
52
|
-
if (onBotDetected === "block") {
|
|
53
|
-
return customBlockedResponse(ctx, detectionResult);
|
|
54
|
-
}
|
|
55
|
-
else if (typeof onBotDetected === "function") {
|
|
56
|
-
return onBotDetected(ctx, detectionResult);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return await next();
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
export function createRateLimitDefaultStorage() {
|
|
63
|
-
const store = new Map();
|
|
64
|
-
return {
|
|
65
|
-
get: (key) => store.get(key),
|
|
66
|
-
set: (key, value) => store.set(key, value),
|
|
67
|
-
clearExpired: () => {
|
|
68
|
-
const now = Date.now();
|
|
69
|
-
for (const [key, entry] of store.entries()) {
|
|
70
|
-
if (now >= entry.resetTime) {
|
|
71
|
-
store.delete(key);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
export function isRateLimit(ctx, key, store, maxRequests, windowMs) {
|
|
78
|
-
store.clearExpired();
|
|
79
|
-
const now = Date.now();
|
|
80
|
-
let entry = store.get(key) || { count: 0, resetTime: now + windowMs };
|
|
81
|
-
if (now < entry.resetTime) {
|
|
82
|
-
entry.count++;
|
|
83
|
-
if (entry.count > maxRequests) {
|
|
84
|
-
return {
|
|
85
|
-
check: true,
|
|
86
|
-
entry: entry,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
entry = { count: 1, resetTime: now + windowMs };
|
|
92
|
-
}
|
|
93
|
-
store.set(key, entry);
|
|
94
|
-
return {
|
|
95
|
-
check: false,
|
|
96
|
-
entry: entry,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Options for the detectLocale middleware.
|
|
4
|
-
*/
|
|
5
|
-
export type DetectLocaleOptions = {
|
|
6
|
-
/**
|
|
7
|
-
* 🌐 List of allowed locales.
|
|
8
|
-
* e.g., ["en", "fr", "bn"]
|
|
9
|
-
*/
|
|
10
|
-
supportedLocales: string[];
|
|
11
|
-
/**
|
|
12
|
-
* 🏠 Default locale if none is matched from query, cookie, or headers.
|
|
13
|
-
* @default "en"
|
|
14
|
-
*/
|
|
15
|
-
defaultLocale?: string;
|
|
16
|
-
/**
|
|
17
|
-
* 🔍 Name of the query parameter to check for locale.
|
|
18
|
-
* Example: /?lang=fr
|
|
19
|
-
* @default "lang"
|
|
20
|
-
*/
|
|
21
|
-
queryKeyLocale?: string;
|
|
22
|
-
/**
|
|
23
|
-
* 🍪 Name of the cookie used to store locale preference.
|
|
24
|
-
* @default "locale"
|
|
25
|
-
*/
|
|
26
|
-
cookieKeyLocale?: string;
|
|
27
|
-
/**
|
|
28
|
-
* 🗺️ Key under which the locale will be attached to the context object.
|
|
29
|
-
* Example: ctx.locale = "en"
|
|
30
|
-
* @default "locale"
|
|
31
|
-
*/
|
|
32
|
-
localeContextKey?: string;
|
|
33
|
-
/**
|
|
34
|
-
* 🛠️ Optional custom function to programmatically detect locale.
|
|
35
|
-
* Called last before fallback.
|
|
36
|
-
* Should return a supported locale or undefined.
|
|
37
|
-
*/
|
|
38
|
-
customLocaleDetector?: (ctx: Context) => string | undefined;
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* 🌍 Middleware that detects and sets the user's preferred locale.
|
|
42
|
-
*
|
|
43
|
-
* Detection order:
|
|
44
|
-
* 1. Query parameter (e.g., ?lang=fr)
|
|
45
|
-
* 2. Cookie value (e.g., locale=fr)
|
|
46
|
-
* 3. Accept-Language HTTP header
|
|
47
|
-
* 4. Custom detector function (if provided)
|
|
48
|
-
* 5. Default locale (fallback)
|
|
49
|
-
*
|
|
50
|
-
* The detected locale is stored in `ctx[localeContextKey]`.
|
|
51
|
-
*
|
|
52
|
-
* @param options - Configuration options for locale detection.
|
|
53
|
-
* @returns Middleware function that attaches locale to the context.
|
|
54
|
-
*/
|
|
55
|
-
export declare const detectLocale: (options: DetectLocaleOptions) => Middleware;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { GlobalConfig } from "../core/config.js";
|
|
2
|
-
export const detectLocale = (options) => {
|
|
3
|
-
const { supportedLocales, defaultLocale = "en", queryKeyLocale = "lang", cookieKeyLocale = "locale", localeContextKey = "locale", customLocaleDetector, } = options;
|
|
4
|
-
return async function detectLocale(ctx, next) {
|
|
5
|
-
let detectedLocale;
|
|
6
|
-
const queryLocale = ctx.req.query[queryKeyLocale];
|
|
7
|
-
if (queryLocale && supportedLocales.includes(queryLocale)) {
|
|
8
|
-
detectedLocale = queryLocale;
|
|
9
|
-
}
|
|
10
|
-
if (!detectedLocale) {
|
|
11
|
-
const cookieLocale = ctx.cookies.get(cookieKeyLocale);
|
|
12
|
-
if (cookieLocale && supportedLocales.includes(cookieLocale)) {
|
|
13
|
-
detectedLocale = cookieLocale;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
if (!detectedLocale) {
|
|
17
|
-
const acceptLanguage = ctx.req.headers.get("accept-language");
|
|
18
|
-
if (acceptLanguage) {
|
|
19
|
-
const preferredLocales = acceptLanguage
|
|
20
|
-
.split(",")
|
|
21
|
-
.map((lang) => lang.split(";")[0].trim())
|
|
22
|
-
.filter((lang) => supportedLocales.includes(lang));
|
|
23
|
-
detectedLocale = preferredLocales[0];
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (!detectedLocale && customLocaleDetector) {
|
|
27
|
-
const customLocale = customLocaleDetector(ctx);
|
|
28
|
-
if (customLocale && supportedLocales.includes(customLocale)) {
|
|
29
|
-
detectedLocale = customLocale;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (!detectedLocale) {
|
|
33
|
-
detectedLocale = defaultLocale;
|
|
34
|
-
}
|
|
35
|
-
ctx[localeContextKey] = detectedLocale;
|
|
36
|
-
GlobalConfig.debugging.success(`Detected locale: ${detectedLocale}`);
|
|
37
|
-
return await next();
|
|
38
|
-
};
|
|
39
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Context, Middleware } from "../index.js";
|
|
2
|
-
export type LazyModuleLoader<T> = () => Promise<T>;
|
|
3
|
-
export interface CacheItem<T = any> {
|
|
4
|
-
module: T;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
}
|
|
7
|
-
export interface LazyLoadOptions<T> {
|
|
8
|
-
/**
|
|
9
|
-
* 🗺️ Key to identify the module to load. This can be a function that extracts the module name from the request context.
|
|
10
|
-
* @default (ctx) => ctx.req.params[queryKeyModule] || ctx.req.query[queryKeyModule]
|
|
11
|
-
*/
|
|
12
|
-
moduleKey?: (ctx: Context) => string;
|
|
13
|
-
/**
|
|
14
|
-
* 🛠️ Function that returns a loader function for the specified module, used to dynamically load the module.
|
|
15
|
-
* If this function returns null, it indicates no loader is available for the module.
|
|
16
|
-
*/
|
|
17
|
-
getModuleLoader: (ctx: Context) => Promise<LazyModuleLoader<T> | null> | null | LazyModuleLoader<T>;
|
|
18
|
-
/**
|
|
19
|
-
* 🔍 Query parameter name to select which module to load (e.g., "module").
|
|
20
|
-
* @default "module"
|
|
21
|
-
*/
|
|
22
|
-
queryKeyModule?: string;
|
|
23
|
-
/**
|
|
24
|
-
* 📦 Key to attach the loaded module to the context object.
|
|
25
|
-
* @default "module"
|
|
26
|
-
*/
|
|
27
|
-
moduleContextKey?: string;
|
|
28
|
-
/**
|
|
29
|
-
* 🔄 Enable caching of loaded modules to avoid re-loading them repeatedly.
|
|
30
|
-
* @default true
|
|
31
|
-
*/
|
|
32
|
-
enableCache?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
|
|
35
|
-
* By default, it uses a `Map<string, CacheItem<T>>`.
|
|
36
|
-
*/
|
|
37
|
-
cacheStorage?: {
|
|
38
|
-
get: (key: string) => CacheItem<T> | undefined;
|
|
39
|
-
set: (key: string, value: CacheItem<T>) => void;
|
|
40
|
-
delete: (key: string) => void;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* ⏳ Cache Time-To-Live (TTL) in milliseconds. This determines how long cached modules are valid.
|
|
44
|
-
* @default 3600000 (1 hour)
|
|
45
|
-
*/
|
|
46
|
-
cacheTTL?: number;
|
|
47
|
-
/**
|
|
48
|
-
* 🌟 Lifecycle hooks for the module loading process.
|
|
49
|
-
* These hooks allow for custom actions at various stages of loading the module (e.g., when a module is loaded or when cache is hit).
|
|
50
|
-
*/
|
|
51
|
-
lifecycleHooks?: {
|
|
52
|
-
onLoad?: (moduleName: string, ctx: Context) => void;
|
|
53
|
-
onError?: (moduleName: string, error: Error, ctx: Context) => void;
|
|
54
|
-
onComplete?: (moduleName: string, module: T, ctx: Context) => void;
|
|
55
|
-
onCacheHit?: (moduleName: string, module: T, ctx: Context) => void;
|
|
56
|
-
onCacheSet?: (moduleName: string, module: T, ctx: Context) => void;
|
|
57
|
-
};
|
|
58
|
-
/**
|
|
59
|
-
* 🛡️ Module validation function to ensure the module meets specific criteria before use.
|
|
60
|
-
* This function will be called after the module is loaded to verify its structure or behavior.
|
|
61
|
-
* If validation fails, an error is thrown.
|
|
62
|
-
*/
|
|
63
|
-
validateModule?: (module: T) => boolean;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Middleware for handling lazy loading of modules. This middleware allows dynamically loading modules based on route or query parameters.
|
|
67
|
-
* It supports caching, lifecycle hooks, and module validation.
|
|
68
|
-
*
|
|
69
|
-
* @param options - Custom options for lazy loading, including caching, hooks, and module validation.
|
|
70
|
-
* @returns A middleware function to use in your application.
|
|
71
|
-
*/
|
|
72
|
-
export declare const lazyLoadModules: <T = any>(options: LazyLoadOptions<T>) => Middleware;
|