tezx 3.0.9-beta → 3.0.11-beta
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/index.d.ts +4 -2
- package/bun/index.js +3 -2
- package/bun/ws.d.ts +37 -0
- package/bun/ws.js +23 -0
- package/cjs/bun/index.js +37 -5
- package/cjs/bun/ws.js +25 -0
- package/cjs/core/context.js +86 -88
- package/cjs/core/error.js +41 -0
- package/cjs/core/request.js +6 -6
- package/cjs/core/router.js +6 -6
- package/cjs/core/server.js +45 -63
- package/cjs/index.js +5 -2
- package/cjs/middleware/basic-auth.js +28 -54
- package/cjs/middleware/bearer-auth.js +34 -0
- package/cjs/middleware/cors.js +14 -24
- package/cjs/middleware/index.js +25 -0
- package/cjs/middleware/logger.js +6 -3
- package/cjs/middleware/pagination.js +1 -1
- package/cjs/middleware/powered-by.js +1 -1
- package/cjs/middleware/rate-limiter.js +20 -7
- package/cjs/middleware/request-id.js +4 -7
- package/cjs/middleware/sanitize-headers.js +8 -40
- package/cjs/middleware/xss-protection.js +2 -6
- package/cjs/registry/RadixRouter.js +72 -23
- package/cjs/utils/cookie.js +1 -1
- package/cjs/utils/rateLimit.js +2 -2
- package/cjs/utils/regexRouter.js +1 -0
- package/cjs/utils/response.js +21 -30
- package/core/context.d.ts +68 -68
- package/core/context.js +87 -89
- package/core/error.d.ts +95 -0
- package/core/error.js +37 -0
- package/core/request.d.ts +2 -2
- package/core/request.js +6 -6
- package/core/router.d.ts +11 -6
- package/core/router.js +6 -6
- package/core/server.js +45 -63
- package/index.d.ts +5 -3
- package/index.js +4 -2
- package/middleware/basic-auth.d.ts +38 -66
- package/middleware/basic-auth.js +28 -54
- package/middleware/bearer-auth.d.ts +52 -0
- package/middleware/bearer-auth.js +30 -0
- package/middleware/cors.d.ts +7 -21
- package/middleware/cors.js +14 -24
- package/middleware/index.d.ts +9 -0
- package/middleware/index.js +9 -0
- package/middleware/logger.d.ts +3 -1
- package/middleware/logger.js +6 -3
- package/middleware/pagination.d.ts +8 -6
- package/middleware/pagination.js +1 -1
- package/middleware/powered-by.js +1 -1
- package/middleware/rate-limiter.d.ts +0 -4
- package/middleware/rate-limiter.js +20 -7
- package/middleware/request-id.d.ts +1 -1
- package/middleware/request-id.js +3 -6
- package/middleware/sanitize-headers.d.ts +3 -11
- package/middleware/sanitize-headers.js +8 -40
- package/middleware/xss-protection.d.ts +1 -1
- package/middleware/xss-protection.js +1 -5
- package/package.json +6 -1
- package/registry/RadixRouter.js +72 -23
- package/types/index.d.ts +2 -1
- package/utils/cookie.js +1 -1
- package/utils/rateLimit.d.ts +1 -2
- package/utils/rateLimit.js +2 -2
- package/utils/regexRouter.js +1 -0
- package/utils/response.d.ts +12 -14
- package/utils/response.js +20 -29
- package/cjs/middleware/cache-control.js +0 -93
- package/cjs/middleware/detect-bot.js +0 -66
- package/cjs/middleware/detect-locale.js +0 -45
- package/cjs/middleware/i18n.js +0 -93
- package/cjs/middleware/lazy-loader.js +0 -74
- package/cjs/middleware/request-timeout.js +0 -43
- package/cjs/middleware/secure-headers.js +0 -43
- package/middleware/cache-control.d.ts +0 -56
- package/middleware/cache-control.js +0 -56
- package/middleware/detect-bot.d.ts +0 -111
- package/middleware/detect-bot.js +0 -62
- package/middleware/detect-locale.d.ts +0 -56
- package/middleware/detect-locale.js +0 -41
- package/middleware/i18n.d.ts +0 -102
- package/middleware/i18n.js +0 -89
- package/middleware/lazy-loader.d.ts +0 -73
- package/middleware/lazy-loader.js +0 -70
- package/middleware/request-timeout.d.ts +0 -26
- package/middleware/request-timeout.js +0 -39
- package/middleware/secure-headers.d.ts +0 -78
- package/middleware/secure-headers.js +0 -39
package/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { TezXError } from "./core/error.js";
|
|
1
2
|
import { Router } from "./core/router.js";
|
|
2
3
|
import { TezX } from "./core/server.js";
|
|
3
|
-
export { Router, TezX };
|
|
4
|
-
export let version = "3.0.
|
|
4
|
+
export { Router, TezX, TezXError };
|
|
5
|
+
export let version = "3.0.11-beta";
|
|
5
6
|
export default {
|
|
6
7
|
Router,
|
|
7
8
|
TezX,
|
|
8
9
|
version,
|
|
10
|
+
TezXError,
|
|
9
11
|
};
|
|
@@ -1,83 +1,55 @@
|
|
|
1
1
|
import { Context } from "../core/context.js";
|
|
2
2
|
import { HttpBaseResponse, Middleware } from "../types/index.js";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Options for Basic Authentication middleware.
|
|
5
5
|
*/
|
|
6
|
-
export type
|
|
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 = {
|
|
6
|
+
export type BasicAuthOptions = {
|
|
17
7
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
8
|
+
* Function to validate the username and password.
|
|
9
|
+
* Can return a boolean or a Promise<boolean>.
|
|
10
|
+
*
|
|
11
|
+
* @param username - The username extracted from the request.
|
|
12
|
+
* @param password - The password extracted from the request.
|
|
21
13
|
* @param ctx - The current request context.
|
|
22
|
-
* @returns
|
|
14
|
+
* @returns Whether the credentials are valid.
|
|
23
15
|
*/
|
|
24
|
-
|
|
16
|
+
validate: (username: string, password: string, ctx: Context) => boolean | Promise<boolean>;
|
|
25
17
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @returns The authentication realm string.
|
|
18
|
+
* Realm name shown in the `WWW-Authenticate` header.
|
|
19
|
+
* Defaults to `"Restricted Area"`.
|
|
29
20
|
*/
|
|
30
|
-
|
|
21
|
+
realm?: string;
|
|
31
22
|
/**
|
|
32
|
-
*
|
|
23
|
+
* Custom handler for unauthorized requests.
|
|
24
|
+
*
|
|
33
25
|
* @param ctx - The current request context.
|
|
34
|
-
* @param error - Optional error
|
|
35
|
-
* @returns
|
|
26
|
+
* @param error - Optional error object describing why access was denied.
|
|
27
|
+
* @returns HttpBaseResponse to send to the client.
|
|
36
28
|
*/
|
|
37
29
|
onUnauthorized?: (ctx: Context, error?: Error) => HttpBaseResponse;
|
|
38
|
-
/**
|
|
39
|
-
* 🚦 Rate-limiting configuration.
|
|
40
|
-
* @requires getConnInfo middleware. for parse remote address.
|
|
41
|
-
*/
|
|
42
|
-
rateLimit?: {
|
|
43
|
-
/**
|
|
44
|
-
* 🧠 Custom cache or storage for rate-limit tracking.
|
|
45
|
-
*/
|
|
46
|
-
storage?: {
|
|
47
|
-
get: (key: string) => {
|
|
48
|
-
count: number;
|
|
49
|
-
resetTime: number;
|
|
50
|
-
} | undefined;
|
|
51
|
-
set: (key: string, value: {
|
|
52
|
-
count: number;
|
|
53
|
-
resetTime: number;
|
|
54
|
-
}) => void;
|
|
55
|
-
clearExpired: () => void;
|
|
56
|
-
};
|
|
57
|
-
/** 🔁 Max requests allowed within the window */
|
|
58
|
-
maxRequests: number;
|
|
59
|
-
/** ⏲️ Duration of window in milliseconds */
|
|
60
|
-
windowMs: number;
|
|
61
|
-
};
|
|
62
|
-
/**
|
|
63
|
-
* 🛠 Supported authentication types.
|
|
64
|
-
* @default ["basic"]
|
|
65
|
-
*/
|
|
66
|
-
supportedMethods?: AuthMethod[];
|
|
67
|
-
/**
|
|
68
|
-
* 🧑⚖️ Optional RBAC (Role-Based Access Control) check.
|
|
69
|
-
* @param ctx - The current request context.
|
|
70
|
-
* @param credentials - The validated credentials.
|
|
71
|
-
* @returns Whether access is allowed.
|
|
72
|
-
*/
|
|
73
|
-
checkAccess?: (ctx: Context, credentials: AuthCredential) => boolean | Promise<boolean>;
|
|
74
30
|
};
|
|
75
31
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
32
|
+
* Basic Authentication Middleware
|
|
33
|
+
*
|
|
34
|
+
* Verifies that incoming requests contain a valid Basic Auth header
|
|
35
|
+
* (`Authorization: Basic <base64-credentials>`). Supports async
|
|
36
|
+
* validation and custom unauthorized handling.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Configuration options for validation, realm, and error handling.
|
|
39
|
+
* @returns Middleware function to use in routes.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import basicAuth from "./middleware/basicAuth.js";
|
|
44
|
+
*
|
|
45
|
+
* const auth = basicAuth({
|
|
46
|
+
* validate: async (username, password) => username === "admin" && password === "1234",
|
|
47
|
+
* });
|
|
78
48
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
49
|
+
* app.use("/admin", auth, (ctx) => {
|
|
50
|
+
* ctx.json({ message: "Access granted" });
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
81
53
|
*/
|
|
82
|
-
declare const basicAuth: (options:
|
|
83
|
-
export
|
|
54
|
+
export declare const basicAuth: <T extends Record<string, any> = {}, Path extends string = any>(options: BasicAuthOptions) => Middleware<T, Path>;
|
|
55
|
+
export default basicAuth;
|
package/middleware/basic-auth.js
CHANGED
|
@@ -1,69 +1,43 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { createRateLimitDefaultStorage, isRateLimit, } from "../utils/rateLimit.js";
|
|
5
|
-
const basicAuth = (options) => {
|
|
6
|
-
const { validateCredentials, getRealm = () => "Restricted Area", onUnauthorized = (ctx, error) => {
|
|
7
|
-
const realm = getRealm(ctx);
|
|
2
|
+
export const basicAuth = (options) => {
|
|
3
|
+
const { validate, realm = "Restricted Area", onUnauthorized = (ctx, error) => {
|
|
8
4
|
ctx.setStatus = 401;
|
|
9
5
|
ctx.setHeader("WWW-Authenticate", `Basic realm="${realm}"`);
|
|
10
|
-
ctx.
|
|
11
|
-
},
|
|
12
|
-
|
|
13
|
-
if (rateLimit && !rateLimit.storage) {
|
|
14
|
-
storage = createRateLimitDefaultStorage();
|
|
15
|
-
}
|
|
16
|
-
return async function basicAuth(ctx, next) {
|
|
17
|
-
let authMethod;
|
|
18
|
-
let credentials = {};
|
|
6
|
+
return ctx.json({ error: error?.message || "Unauthorized" });
|
|
7
|
+
}, } = options;
|
|
8
|
+
return async (ctx, next) => {
|
|
19
9
|
const auth = ctx.req.header("authorization");
|
|
20
|
-
if (auth) {
|
|
21
|
-
|
|
22
|
-
authMethod = "basic";
|
|
23
|
-
const base64Credentials = auth.split(" ")[1];
|
|
24
|
-
const decoded = Buffer.from(base64Credentials, "base64").toString("utf-8");
|
|
25
|
-
const [username, password] = decoded.split(":");
|
|
26
|
-
credentials = { username, password };
|
|
27
|
-
}
|
|
28
|
-
else if (auth.startsWith("Bearer ")) {
|
|
29
|
-
authMethod = "bearer-token";
|
|
30
|
-
credentials = { token: auth.split(" ")[1] };
|
|
31
|
-
}
|
|
10
|
+
if (!auth || !auth.startsWith("Basic ")) {
|
|
11
|
+
return onUnauthorized(ctx, new Error("Basic authentication required"));
|
|
32
12
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
const base64 = auth.slice(6).trim();
|
|
14
|
+
if (!base64) {
|
|
15
|
+
return onUnauthorized(ctx, new Error("Empty credentials"));
|
|
36
16
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
let username, password;
|
|
18
|
+
try {
|
|
19
|
+
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
|
20
|
+
const idx = decoded.indexOf(":");
|
|
21
|
+
if (idx === -1)
|
|
22
|
+
throw new Error("Missing colon in credentials");
|
|
23
|
+
username = decoded.slice(0, idx);
|
|
24
|
+
password = decoded.slice(idx + 1);
|
|
40
25
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const { check, entry } = isRateLimit(ctx, key, storage, rateLimit.maxRequests, rateLimit.windowMs);
|
|
44
|
-
if (check) {
|
|
45
|
-
const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
|
|
46
|
-
ctx.setHeader("Retry-After", retryAfter.toString());
|
|
47
|
-
return onUnauthorized(ctx, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
|
|
48
|
-
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return onUnauthorized(ctx, new Error("Invalid Basic auth format"));
|
|
49
28
|
}
|
|
50
29
|
try {
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
if (checkAccess) {
|
|
56
|
-
const hasAccess = await checkAccess(ctx, credentials);
|
|
57
|
-
if (!hasAccess) {
|
|
58
|
-
return onUnauthorized(ctx, new Error("Access denied."));
|
|
59
|
-
}
|
|
30
|
+
const valid = await validate(username, password, ctx);
|
|
31
|
+
if (!valid) {
|
|
32
|
+
return onUnauthorized(ctx, new Error("Invalid username or password"));
|
|
60
33
|
}
|
|
61
|
-
|
|
34
|
+
ctx.user = { username };
|
|
35
|
+
await next();
|
|
62
36
|
}
|
|
63
|
-
catch (
|
|
64
|
-
|
|
37
|
+
catch (err) {
|
|
38
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
65
39
|
return onUnauthorized(ctx, error);
|
|
66
40
|
}
|
|
67
41
|
};
|
|
68
42
|
};
|
|
69
|
-
export
|
|
43
|
+
export default basicAuth;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Context } from "../core/context.js";
|
|
2
|
+
import { HttpBaseResponse, Middleware } from "../types/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for Bearer Authentication middleware.
|
|
5
|
+
*/
|
|
6
|
+
export type BearerAuthOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Function to validate the token.
|
|
9
|
+
* Can be synchronous or asynchronous.
|
|
10
|
+
* @param token - The Bearer token from the request.
|
|
11
|
+
* @param ctx - The current request context.
|
|
12
|
+
* @returns Boolean indicating if the token is valid.
|
|
13
|
+
*/
|
|
14
|
+
validate: (token: string, ctx: Context) => boolean | Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Realm name shown in the `WWW-Authenticate` header.
|
|
17
|
+
* Defaults to `"API"`.
|
|
18
|
+
*/
|
|
19
|
+
realm?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Custom handler for unauthorized requests.
|
|
22
|
+
* @param ctx - The current request context.
|
|
23
|
+
* @param error - Optional error object describing the reason.
|
|
24
|
+
* @returns HttpBaseResponse to send to the client.
|
|
25
|
+
*/
|
|
26
|
+
onUnauthorized?: (ctx: Context, error?: Error) => HttpBaseResponse;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Bearer Authentication Middleware
|
|
30
|
+
*
|
|
31
|
+
* Verifies that incoming requests contain a valid Bearer token
|
|
32
|
+
* in the `Authorization` header. Supports async token validation
|
|
33
|
+
* and custom error handling.
|
|
34
|
+
*
|
|
35
|
+
* @param options - Configuration options for token validation, realm, and error handling.
|
|
36
|
+
* @returns Middleware function to use in routes.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { bearerAuth } from "./middleware/bearerAuth.js";
|
|
41
|
+
*
|
|
42
|
+
* const auth = bearerAuth({
|
|
43
|
+
* validate: async (token) => token === "secret-token",
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* app.use("/api", auth, (ctx) => {
|
|
47
|
+
* ctx.json({ message: "Access granted" });
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare const bearerAuth: <T extends Record<string, any> = {}, Path extends string = any>(options: BearerAuthOptions) => Middleware<T, Path>;
|
|
52
|
+
export default bearerAuth;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const bearerAuth = (options) => {
|
|
2
|
+
const { validate, realm = "API", onUnauthorized = (ctx, error) => {
|
|
3
|
+
ctx.setStatus = 401;
|
|
4
|
+
ctx.setHeader("WWW-Authenticate", `Bearer realm="${realm}"`);
|
|
5
|
+
return ctx.json({ error: error?.message || "Unauthorized" });
|
|
6
|
+
}, } = options;
|
|
7
|
+
return async (ctx, next) => {
|
|
8
|
+
const auth = ctx.req.header("authorization");
|
|
9
|
+
if (!auth || !auth.startsWith("Bearer ")) {
|
|
10
|
+
return onUnauthorized(ctx, new Error("Bearer token required"));
|
|
11
|
+
}
|
|
12
|
+
const token = auth.slice(7).trim();
|
|
13
|
+
if (!token) {
|
|
14
|
+
return onUnauthorized(ctx, new Error("Empty token"));
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const valid = await validate(token, ctx);
|
|
18
|
+
if (!valid) {
|
|
19
|
+
return onUnauthorized(ctx, new Error("Invalid or expired token"));
|
|
20
|
+
}
|
|
21
|
+
ctx.token = token;
|
|
22
|
+
await next();
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
26
|
+
return onUnauthorized(ctx, error);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export default bearerAuth;
|
package/middleware/cors.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Middleware } from "../types/index.js";
|
|
2
2
|
export type CorsOptions = {
|
|
3
3
|
/**
|
|
4
4
|
* Allowed origins for CORS.
|
|
5
|
-
* Can be a string,
|
|
6
|
-
* that takes the request origin and returns a boolean indicating if it is allowed.
|
|
5
|
+
* Can be a string, an array of strings, or a function that returns a boolean.
|
|
7
6
|
*/
|
|
8
|
-
origin?: string |
|
|
7
|
+
origin?: string | string[] | ((reqOrigin: string) => boolean);
|
|
9
8
|
/**
|
|
10
9
|
* Allowed HTTP methods for CORS requests.
|
|
11
10
|
* Defaults to ['GET', 'POST', 'PUT', 'DELETE'].
|
|
@@ -21,31 +20,18 @@ export type CorsOptions = {
|
|
|
21
20
|
*/
|
|
22
21
|
exposedHeaders?: string[];
|
|
23
22
|
/**
|
|
24
|
-
* Indicates whether
|
|
25
|
-
* when the credentials flag is true.
|
|
23
|
+
* Indicates whether credentials are allowed.
|
|
26
24
|
*/
|
|
27
25
|
credentials?: boolean;
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
30
|
-
* can be cached (in seconds).
|
|
27
|
+
* Preflight cache duration in seconds.
|
|
31
28
|
*/
|
|
32
29
|
maxAge?: number;
|
|
33
30
|
};
|
|
34
31
|
/**
|
|
35
32
|
* Middleware for handling Cross-Origin Resource Sharing (CORS).
|
|
36
33
|
*
|
|
37
|
-
* @param
|
|
38
|
-
* @returns {Function} Middleware function compatible with the framework's middleware signature.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* app.use(cors({
|
|
43
|
-
* origin: ["https://example.com", /https:\/\/.*\.example\.com/],
|
|
44
|
-
* methods: ["GET", "POST"],
|
|
45
|
-
* credentials: true,
|
|
46
|
-
* maxAge: 600,
|
|
47
|
-
* }));
|
|
48
|
-
* ```
|
|
34
|
+
* @param option - Configuration options for CORS.
|
|
49
35
|
*/
|
|
50
|
-
declare function cors
|
|
36
|
+
declare function cors<T extends Record<string, any> = {}, Path extends string = any>(option?: CorsOptions): Middleware<T, Path>;
|
|
51
37
|
export { cors, cors as default };
|
package/middleware/cors.js
CHANGED
|
@@ -1,45 +1,35 @@
|
|
|
1
1
|
function cors(option = {}) {
|
|
2
|
-
const {
|
|
2
|
+
const { credentials, maxAge, origin, } = option;
|
|
3
|
+
let methods = (option.methods || ["GET", "POST", "PUT", "DELETE"]).join(", ");
|
|
4
|
+
let allowedHeaders = (option.allowedHeaders || ["Content-Type", "Authorization"]).join(", ");
|
|
5
|
+
let exposedHeaders = option?.exposedHeaders?.join(", ");
|
|
3
6
|
return async function cors(ctx, next) {
|
|
4
7
|
const reqOrigin = ctx.req.header("origin") || "";
|
|
5
8
|
let allowOrigin = "*";
|
|
6
9
|
if (typeof origin === "string") {
|
|
7
10
|
allowOrigin = origin;
|
|
8
11
|
}
|
|
9
|
-
else if (origin instanceof RegExp) {
|
|
10
|
-
allowOrigin = origin.test(reqOrigin) ? reqOrigin : "";
|
|
11
|
-
}
|
|
12
12
|
else if (Array.isArray(origin)) {
|
|
13
|
-
|
|
14
|
-
if (typeof item === "string") {
|
|
15
|
-
return item === reqOrigin;
|
|
16
|
-
}
|
|
17
|
-
else if (item instanceof RegExp) {
|
|
18
|
-
return item.test(reqOrigin);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
allowOrigin = isAllowed ? reqOrigin : "";
|
|
13
|
+
allowOrigin = origin.includes(reqOrigin) ? reqOrigin : "";
|
|
22
14
|
}
|
|
23
15
|
else if (typeof origin === "function") {
|
|
24
16
|
allowOrigin = origin(reqOrigin) ? reqOrigin : "";
|
|
25
17
|
}
|
|
26
|
-
ctx.
|
|
27
|
-
ctx.
|
|
28
|
-
ctx.
|
|
18
|
+
ctx.headers.set("Access-Control-Allow-Origin", allowOrigin);
|
|
19
|
+
ctx.headers.set("Access-Control-Allow-Methods", methods);
|
|
20
|
+
ctx.headers.set("Access-Control-Allow-Headers", allowedHeaders);
|
|
29
21
|
if (exposedHeaders) {
|
|
30
|
-
ctx.
|
|
22
|
+
ctx.headers.set("Access-Control-Expose-Headers", exposedHeaders);
|
|
31
23
|
}
|
|
32
24
|
if (credentials) {
|
|
33
|
-
ctx.
|
|
25
|
+
ctx.headers.set("Access-Control-Allow-Credentials", "true");
|
|
34
26
|
}
|
|
35
27
|
if (maxAge) {
|
|
36
|
-
ctx.
|
|
28
|
+
ctx.headers.set("Access-Control-Max-Age", maxAge.toString());
|
|
37
29
|
}
|
|
38
|
-
if (ctx.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
headers: ctx.header(),
|
|
42
|
-
});
|
|
30
|
+
if (ctx.method === "OPTIONS") {
|
|
31
|
+
ctx.setStatus = 204;
|
|
32
|
+
return;
|
|
43
33
|
}
|
|
44
34
|
return await next();
|
|
45
35
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./basic-auth.js";
|
|
2
|
+
export * from "./bearer-auth.js";
|
|
3
|
+
export * from "./cors.js";
|
|
4
|
+
export * from "./logger.js";
|
|
5
|
+
export * from "./pagination.js";
|
|
6
|
+
export * from "./powered-by.js";
|
|
7
|
+
export * from "./request-id.js";
|
|
8
|
+
export * from "./sanitize-headers.js";
|
|
9
|
+
export * from "./xss-protection.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./basic-auth.js";
|
|
2
|
+
export * from "./bearer-auth.js";
|
|
3
|
+
export * from "./cors.js";
|
|
4
|
+
export * from "./logger.js";
|
|
5
|
+
export * from "./pagination.js";
|
|
6
|
+
export * from "./powered-by.js";
|
|
7
|
+
export * from "./request-id.js";
|
|
8
|
+
export * from "./sanitize-headers.js";
|
|
9
|
+
export * from "./xss-protection.js";
|
package/middleware/logger.d.ts
CHANGED
package/middleware/logger.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { colorText } from "../utils/colors.js";
|
|
2
|
-
function logger() {
|
|
2
|
+
function logger(options = { enabled: true }) {
|
|
3
3
|
return async function logger(ctx, next) {
|
|
4
4
|
try {
|
|
5
|
+
if (!options?.enabled) {
|
|
6
|
+
return next();
|
|
7
|
+
}
|
|
5
8
|
console.log(`${colorText("<--", "bold")} ${colorText(ctx.method, "bgMagenta")} ${ctx.pathname}`);
|
|
6
9
|
const startTime = performance.now();
|
|
7
|
-
let n = await next();
|
|
10
|
+
let n = (await next());
|
|
8
11
|
const elapsed = performance.now() - startTime;
|
|
9
12
|
console.log(`${colorText("-->", "bold")} ${colorText(ctx.method, "bgBlue")} ${ctx.pathname} ` +
|
|
10
|
-
`${colorText(ctx.getStatus, "yellow")} ${colorText(`${elapsed.toFixed(2)}ms`, "magenta")}`);
|
|
13
|
+
`${colorText(n ? ctx.getStatus : 404, "yellow")} ${colorText(`${elapsed.toFixed(2)}ms`, "magenta")}`);
|
|
11
14
|
return n;
|
|
12
15
|
}
|
|
13
16
|
catch (err) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Context
|
|
2
|
-
export type PaginationOptions = {
|
|
1
|
+
import { Context } from "../index.js";
|
|
2
|
+
export type PaginationOptions<DataKey extends string = "data", CountKey extends string = "total", Item = any> = {
|
|
3
3
|
/**
|
|
4
4
|
* 🔢 Default page number when not specified
|
|
5
5
|
* @default 1
|
|
@@ -35,13 +35,13 @@ export type PaginationOptions = {
|
|
|
35
35
|
* @default "total"
|
|
36
36
|
* @example "totalCount" // Read from response.totalCount
|
|
37
37
|
*/
|
|
38
|
-
countKey?:
|
|
38
|
+
countKey?: CountKey;
|
|
39
39
|
/**
|
|
40
40
|
* 📦 Key containing the data array in response
|
|
41
41
|
* @default "data"
|
|
42
42
|
* @example "items" // Process response.items array
|
|
43
43
|
*/
|
|
44
|
-
dataKey?:
|
|
44
|
+
dataKey?: DataKey;
|
|
45
45
|
/**
|
|
46
46
|
* 🛠️ Function to fetch data dynamically
|
|
47
47
|
* @param ctx - Request context
|
|
@@ -57,7 +57,9 @@ export type PaginationOptions = {
|
|
|
57
57
|
limit: number;
|
|
58
58
|
offset: number;
|
|
59
59
|
}) => Promise<{
|
|
60
|
-
[
|
|
60
|
+
[K in DataKey]: Item[];
|
|
61
|
+
} & {
|
|
62
|
+
[K in CountKey]: number;
|
|
61
63
|
}>;
|
|
62
64
|
};
|
|
63
65
|
export type PaginationBodyType = {
|
|
@@ -100,5 +102,5 @@ export type PaginationBodyType = {
|
|
|
100
102
|
* }
|
|
101
103
|
* }));
|
|
102
104
|
*/
|
|
103
|
-
declare const paginationHandler: (options?: PaginationOptions) =>
|
|
105
|
+
declare const paginationHandler: <DataKey extends string = "data", CountKey extends string = "total", Item = any>(options?: PaginationOptions<DataKey, CountKey, Item>) => any;
|
|
104
106
|
export { paginationHandler, paginationHandler as default };
|
package/middleware/pagination.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const paginationHandler = (options = {}) => {
|
|
2
|
-
|
|
2
|
+
let { defaultPage = 1, defaultLimit = 10, maxLimit = 100, queryKeyPage = "page", queryKeyLimit = "limit", countKey = "total", dataKey = "data", getDataSource, } = options;
|
|
3
3
|
return async function paginationHandler(ctx, next) {
|
|
4
4
|
const rawPage = ctx.req.query[queryKeyPage];
|
|
5
5
|
const rawLimit = ctx.req.query[queryKeyLimit];
|
package/middleware/powered-by.js
CHANGED
|
@@ -20,10 +20,6 @@ export type RateLimiterOptions = {
|
|
|
20
20
|
* keyGenerator: (ctx) => ctx.user?.id || ctx.ip // Use user ID if authenticated
|
|
21
21
|
*/
|
|
22
22
|
keyGenerator?: (ctx: Context) => string;
|
|
23
|
-
/**
|
|
24
|
-
// * ⚠️ (Future) Storage backend - currently memory only
|
|
25
|
-
// * @todo Implement Redis storage
|
|
26
|
-
// */
|
|
27
23
|
/**
|
|
28
24
|
* 🔄 Custom cache storage implementation (e.g., using `Map`, `Redis`, etc.).
|
|
29
25
|
* By default, it uses a `Map<string, { count: number; resetTime: number }>`.
|
|
@@ -1,20 +1,33 @@
|
|
|
1
|
+
import { TezXError } from "../core/error.js";
|
|
1
2
|
import { createRateLimitDefaultStorage, isRateLimit, } from "../utils/rateLimit.js";
|
|
2
3
|
const rateLimiter = (options) => {
|
|
3
|
-
const { maxRequests, windowMs, keyGenerator = (ctx) =>
|
|
4
|
+
const { maxRequests, windowMs, keyGenerator = (ctx) => {
|
|
5
|
+
const xForwardedFor = ctx.req.header("x-forwarded-for");
|
|
6
|
+
if (xForwardedFor) {
|
|
7
|
+
const ip = xForwardedFor.split(",")[0].trim();
|
|
8
|
+
return ip;
|
|
9
|
+
}
|
|
10
|
+
const clientIp = ctx.req.header("client-ip");
|
|
11
|
+
if (clientIp)
|
|
12
|
+
return clientIp;
|
|
13
|
+
const addr = ctx.req.remoteAddress?.address || "unknown";
|
|
14
|
+
const port = ctx.req.remoteAddress?.port || "0";
|
|
15
|
+
return `${addr}:${port}`;
|
|
16
|
+
}, storage = createRateLimitDefaultStorage(), onError = (ctx, retryAfter, error) => {
|
|
4
17
|
ctx.setStatus = 429;
|
|
5
|
-
throw new
|
|
18
|
+
throw new TezXError(`Rate limit exceeded. Try again in ${retryAfter} seconds.`, 429);
|
|
6
19
|
}, } = options;
|
|
7
20
|
return async function rateLimiter(ctx, next) {
|
|
8
21
|
const key = keyGenerator(ctx);
|
|
9
|
-
const { check, entry } = isRateLimit(
|
|
22
|
+
const { check, entry } = isRateLimit(key, storage, maxRequests, windowMs);
|
|
10
23
|
if (check) {
|
|
11
24
|
const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
|
|
12
|
-
ctx.
|
|
25
|
+
ctx.headers.set("Retry-After", retryAfter.toString());
|
|
13
26
|
return onError(ctx, retryAfter, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
|
|
14
27
|
}
|
|
15
|
-
ctx.
|
|
16
|
-
ctx.
|
|
17
|
-
ctx.
|
|
28
|
+
ctx.headers.set("X-RateLimit-Limit", maxRequests.toString());
|
|
29
|
+
ctx.headers.set("X-RateLimit-Remaining", (maxRequests - entry.count).toString());
|
|
30
|
+
ctx.headers.set("X-RateLimit-Reset", entry.resetTime.toString());
|
|
18
31
|
return await next();
|
|
19
32
|
};
|
|
20
33
|
};
|
package/middleware/request-id.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { generateUUID } from "../helper/index.js";
|
|
2
2
|
const requestID = (headerName = "X-Request-ID", contextKey = "requestID") => {
|
|
3
3
|
return function requestID(ctx, next) {
|
|
4
|
-
let requestId = ctx.
|
|
5
|
-
if (!requestId) {
|
|
6
|
-
requestId = `req-${generateUUID()}`;
|
|
7
|
-
}
|
|
4
|
+
let requestId = ctx.headers.get(headerName) ?? `req-${generateUUID()}`;
|
|
8
5
|
ctx[contextKey] = requestId;
|
|
9
|
-
ctx.
|
|
6
|
+
ctx.headers.set(headerName, requestId);
|
|
10
7
|
return next();
|
|
11
8
|
};
|
|
12
9
|
};
|
|
13
|
-
export { requestID
|
|
10
|
+
export { requestID as default, requestID };
|