tezx 1.0.43 → 1.0.45

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.
@@ -7,13 +7,15 @@ const context_1 = require("./context");
7
7
  const router_1 = require("./router");
8
8
  const params_1 = require("../utils/params");
9
9
  class TezX extends router_1.Router {
10
- constructor({ basePath = "/", env = {}, debugMode = false, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
10
+ #onPathResolve;
11
+ constructor({ basePath = "/", env = {}, debugMode = false, onPathResolve, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
11
12
  config_1.GlobalConfig.allowDuplicateMw = allowDuplicateMw;
12
13
  config_1.GlobalConfig.overwriteMethod = overwriteMethod;
13
14
  if (debugMode) {
14
15
  config_1.GlobalConfig.debugMode = debugMode;
15
16
  }
16
17
  super({ basePath, env });
18
+ this.#onPathResolve = onPathResolve;
17
19
  this.serve = this.serve.bind(this);
18
20
  }
19
21
  #hashRouter(method, pathname) {
@@ -121,11 +123,19 @@ class TezX extends router_1.Router {
121
123
  let ctx = new context_1.Context(req, connInfo);
122
124
  const urlRef = ctx.req.urlRef;
123
125
  const { pathname } = urlRef;
124
- let middlewares = this.#findMiddleware(pathname);
126
+ let resolvePath = pathname;
127
+ if (this.#onPathResolve) {
128
+ resolvePath = this.#onPathResolve(pathname);
129
+ config_1.GlobalConfig.debugging.warn(`${colors_1.COLORS.white} PATH RESOLVE ${colors_1.COLORS.reset} ${colors_1.COLORS.red}${pathname}${colors_1.COLORS.reset} ➞ ${colors_1.COLORS.cyan}${resolvePath}${colors_1.COLORS.reset}`);
130
+ }
131
+ if (typeof resolvePath !== 'string') {
132
+ throw new Error(`Path resolution failed: expected a string, got ${typeof resolvePath}`);
133
+ }
134
+ let middlewares = this.#findMiddleware(resolvePath);
125
135
  ctx.env = this.env;
126
136
  try {
127
137
  let callback = async (ctx) => {
128
- const find = this.findRoute(ctx.req.method, pathname);
138
+ const find = this.findRoute(ctx.req.method, resolvePath);
129
139
  if (find?.callback) {
130
140
  ctx.params = find.params;
131
141
  const callback = find.callback;
package/cjs/index.js CHANGED
@@ -7,4 +7,4 @@ var server_1 = require("./core/server");
7
7
  Object.defineProperty(exports, "TezX", { enumerable: true, get: function () { return server_1.TezX; } });
8
8
  var params_1 = require("./utils/params");
9
9
  Object.defineProperty(exports, "useParams", { enumerable: true, get: function () { return params_1.useParams; } });
10
- exports.version = "1.0.43";
10
+ exports.version = "1.0.45";
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.basicAuth = void 0;
4
+ const config_1 = require("../core/config");
5
+ const colors_1 = require("../utils/colors");
6
+ const detectBot_1 = require("./detectBot");
7
+ const basicAuth = (options) => {
8
+ const { validateCredentials, getRealm = () => "Restricted Area", onUnauthorized = (ctx, error) => {
9
+ const realm = getRealm(ctx);
10
+ ctx.setStatus = 401;
11
+ ctx.header("WWW-Authenticate", `Basic realm="${realm}"`);
12
+ ctx.body = { error: error?.message };
13
+ }, rateLimit, supportedMethods = ["basic", "api-key", "bearer-token"], checkAccess, } = options;
14
+ let storage = rateLimit?.storage;
15
+ if (rateLimit && !rateLimit.storage) {
16
+ storage = (0, detectBot_1.createRateLimitDefaultStorage)();
17
+ }
18
+ return async (ctx, next) => {
19
+ let authMethod;
20
+ let credentials = {};
21
+ const authHeader = ctx.req.headers.get("authorization");
22
+ if (authHeader) {
23
+ if (authHeader.startsWith("Basic ")) {
24
+ authMethod = "basic";
25
+ const base64Credentials = authHeader.split(" ")[1];
26
+ const decoded = Buffer.from(base64Credentials, "base64").toString("utf-8");
27
+ const [username, password] = decoded.split(":");
28
+ credentials = { username, password };
29
+ }
30
+ else if (authHeader.startsWith("Bearer ")) {
31
+ authMethod = "bearer-token";
32
+ credentials = { token: authHeader.split(" ")[1] };
33
+ }
34
+ }
35
+ else if (ctx.headers.get("x-api-key")) {
36
+ authMethod = "api-key";
37
+ credentials = { apiKey: ctx.headers.get("x-api-key") };
38
+ }
39
+ if (!authMethod || !supportedMethods.includes(authMethod)) {
40
+ config_1.GlobalConfig.debugging.error(`${colors_1.COLORS.bgRed}[AUTH]${colors_1.COLORS.reset} Unsupported or missing authentication method.`);
41
+ return onUnauthorized(ctx, new Error("Unsupported authentication method"));
42
+ }
43
+ if (rateLimit) {
44
+ let key = `${ctx.req.remoteAddress.address}:${ctx.req.remoteAddress.port}`;
45
+ const { check, entry } = (0, detectBot_1.isRateLimit)(ctx, key, storage, rateLimit.maxRequests, rateLimit.windowMs);
46
+ if (check) {
47
+ const retryAfter = Math.ceil((entry.resetTime - Date.now()) / 1000);
48
+ ctx.headers.set("Retry-After", retryAfter.toString());
49
+ return onUnauthorized(ctx, new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds.`));
50
+ }
51
+ }
52
+ try {
53
+ const isValid = await validateCredentials(authMethod, credentials, ctx);
54
+ if (!isValid) {
55
+ throw new Error("Invalid credentials.");
56
+ }
57
+ if (checkAccess) {
58
+ const hasAccess = await checkAccess(ctx, credentials);
59
+ if (!hasAccess) {
60
+ return onUnauthorized(ctx, new Error("Access denied."));
61
+ }
62
+ }
63
+ return await next();
64
+ }
65
+ catch (error) {
66
+ config_1.GlobalConfig.debugging.error(`${colors_1.COLORS.bgRed}[AUTH]${colors_1.COLORS.reset} Failure for method: ${ctx.method}`);
67
+ return onUnauthorized(ctx, error);
68
+ }
69
+ };
70
+ };
71
+ exports.basicAuth = basicAuth;
@@ -29,3 +29,4 @@ __exportStar(require("./request-id"), exports);
29
29
  __exportStar(require("./sanitizeHeader"), exports);
30
30
  __exportStar(require("./secureHeaders"), exports);
31
31
  __exportStar(require("./xssProtection"), exports);
32
+ __exportStar(require("./basicAuth"), exports);
package/core/server.d.ts CHANGED
@@ -32,6 +32,25 @@ export type TezXConfig = {
32
32
  * @default true
33
33
  */
34
34
  overwriteMethod?: boolean;
35
+ /**
36
+ * 🔄 Hook to transform or normalize the incoming request pathname before routing.
37
+ *
38
+ * This function allows you to customize how incoming paths are handled.
39
+ * You can use it to:
40
+ * - Remove trailing slashes
41
+ * - Normalize casing
42
+ * - Rewrite certain paths dynamically
43
+ * - Add localization or versioning prefixes
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * onPathResolve: (pathname) => pathname.replace(/\/+$/, "").toLowerCase()
48
+ * ```
49
+ *
50
+ * @param pathname - The raw incoming request path (e.g., `/Api/Users/`)
51
+ * @returns The transformed or resolved path used for routing (e.g., `/api/users`)
52
+ */
53
+ onPathResolve?: (pathname: string) => string;
35
54
  /**
36
55
  * Enables or disables debugging for the middleware.
37
56
  * When set to `true`, detailed debug logs will be output,
@@ -43,7 +62,7 @@ export type TezXConfig = {
43
62
  } & RouterConfig;
44
63
  export declare class TezX<T extends Record<string, any> = {}> extends Router<T> {
45
64
  #private;
46
- constructor({ basePath, env, debugMode, allowDuplicateMw, overwriteMethod, }?: TezXConfig);
65
+ constructor({ basePath, env, debugMode, onPathResolve, allowDuplicateMw, overwriteMethod, }?: TezXConfig);
47
66
  protected findRoute(method: HTTPMethod, pathname: string): {
48
67
  callback: any;
49
68
  middlewares: Middleware<T>[];
package/core/server.js CHANGED
@@ -4,13 +4,15 @@ import { Context, httpStatusMap } from "./context";
4
4
  import { Router } from "./router";
5
5
  import { useParams } from "../utils/params";
6
6
  export class TezX extends Router {
7
- constructor({ basePath = "/", env = {}, debugMode = false, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
7
+ #onPathResolve;
8
+ constructor({ basePath = "/", env = {}, debugMode = false, onPathResolve, allowDuplicateMw = false, overwriteMethod = true, } = {}) {
8
9
  GlobalConfig.allowDuplicateMw = allowDuplicateMw;
9
10
  GlobalConfig.overwriteMethod = overwriteMethod;
10
11
  if (debugMode) {
11
12
  GlobalConfig.debugMode = debugMode;
12
13
  }
13
14
  super({ basePath, env });
15
+ this.#onPathResolve = onPathResolve;
14
16
  this.serve = this.serve.bind(this);
15
17
  }
16
18
  #hashRouter(method, pathname) {
@@ -118,11 +120,19 @@ export class TezX extends Router {
118
120
  let ctx = new Context(req, connInfo);
119
121
  const urlRef = ctx.req.urlRef;
120
122
  const { pathname } = urlRef;
121
- let middlewares = this.#findMiddleware(pathname);
123
+ let resolvePath = pathname;
124
+ if (this.#onPathResolve) {
125
+ resolvePath = this.#onPathResolve(pathname);
126
+ GlobalConfig.debugging.warn(`${COLORS.white} PATH RESOLVE ${COLORS.reset} ${COLORS.red}${pathname}${COLORS.reset} ➞ ${COLORS.cyan}${resolvePath}${COLORS.reset}`);
127
+ }
128
+ if (typeof resolvePath !== 'string') {
129
+ throw new Error(`Path resolution failed: expected a string, got ${typeof resolvePath}`);
130
+ }
131
+ let middlewares = this.#findMiddleware(resolvePath);
122
132
  ctx.env = this.env;
123
133
  try {
124
134
  let callback = async (ctx) => {
125
- const find = this.findRoute(ctx.req.method, pathname);
135
+ const find = this.findRoute(ctx.req.method, resolvePath);
126
136
  if (find?.callback) {
127
137
  ctx.params = find.params;
128
138
  const callback = find.callback;
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Router } from "./core/router";
2
2
  export { TezX } from "./core/server";
3
3
  export { useParams } from "./utils/params";
4
- export let version = "1.0.43";
4
+ export let version = "1.0.45";
@@ -0,0 +1,84 @@
1
+ import { Middleware } from "..";
2
+ import { Context } from "../core/context";
3
+ import { CallbackReturn } from "../core/router";
4
+ /**
5
+ * Supported authentication method types.
6
+ */
7
+ export type AuthMethod = "basic" | "api-key" | "bearer-token";
8
+ export type AuthCredential = {
9
+ username?: any;
10
+ password?: any;
11
+ token?: any;
12
+ apiKey?: any;
13
+ };
14
+ /**
15
+ * Configuration options for dynamic basic authentication.
16
+ */
17
+ type DynamicBasicAuthOptions = {
18
+ /**
19
+ * 🔐 Function to validate the provided credentials.
20
+ * @param method - The method of authentication.
21
+ * @param credentials - The extracted credentials.
22
+ * @param ctx - The current request context.
23
+ * @returns A boolean or Promise resolving to whether the credentials are valid.
24
+ */
25
+ validateCredentials: (method: AuthMethod, credentials: AuthCredential, ctx: Context) => boolean | Promise<boolean>;
26
+ /**
27
+ * 🔒 Function to dynamically determine the realm for authentication prompt.
28
+ * @param ctx - The current request context.
29
+ * @returns The authentication realm string.
30
+ */
31
+ getRealm?: (ctx: Context) => string;
32
+ /**
33
+ * ❌ Custom handler for unauthorized access.
34
+ * @param ctx - The current request context.
35
+ * @param error - Optional error information.
36
+ * @returns A CallbackReturn to end the response.
37
+ */
38
+ onUnauthorized?: (ctx: Context, error?: Error) => CallbackReturn;
39
+ /**
40
+ * 🚦 Rate-limiting configuration.
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
+ delete: (key: string) => void;
56
+ clearExpired: () => void;
57
+ };
58
+ /** 🔁 Max requests allowed within the window */
59
+ maxRequests: number;
60
+ /** ⏲️ Duration of window in milliseconds */
61
+ windowMs: number;
62
+ };
63
+ /**
64
+ * 🛠 Supported authentication types.
65
+ * @default ["basic"]
66
+ */
67
+ supportedMethods?: AuthMethod[];
68
+ /**
69
+ * 🧑‍⚖️ Optional RBAC (Role-Based Access Control) check.
70
+ * @param ctx - The current request context.
71
+ * @param credentials - The validated credentials.
72
+ * @returns Whether access is allowed.
73
+ */
74
+ checkAccess?: (ctx: Context, credentials: AuthCredential) => boolean | Promise<boolean>;
75
+ };
76
+ /**
77
+ * 🔐 Middleware for flexible authentication using Basic, API Key, or Bearer Token.
78
+ * Supports rate limiting, IP filtering, and role-based access control.
79
+ *
80
+ * @param options - Custom authentication handler options.
81
+ * @returns A middleware function.
82
+ */
83
+ export declare const basicAuth: (options: DynamicBasicAuthOptions) => Middleware;
84
+ export {};
@@ -0,0 +1,67 @@
1
+ import { GlobalConfig } from "../core/config";
2
+ import { COLORS } from "../utils/colors";
3
+ import { createRateLimitDefaultStorage, isRateLimit } from "./detectBot";
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 (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(`${COLORS.bgRed}[AUTH]${COLORS.reset} 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(`${COLORS.bgRed}[AUTH]${COLORS.reset} Failure for method: ${ctx.method}`);
64
+ return onUnauthorized(ctx, error);
65
+ }
66
+ };
67
+ };
@@ -12,3 +12,4 @@ export * from "./request-id";
12
12
  export * from "./sanitizeHeader";
13
13
  export * from "./secureHeaders";
14
14
  export * from "./xssProtection";
15
+ export * from "./basicAuth";
@@ -10,3 +10,4 @@ export * from "./request-id";
10
10
  export * from "./sanitizeHeader";
11
11
  export * from "./secureHeaders";
12
12
  export * from "./xssProtection";
13
+ export * from "./basicAuth";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tezx",
3
- "version": "1.0.43",
3
+ "version": "1.0.45",
4
4
  "description": "TezX is a high-performance, lightweight JavaScript framework designed for speed, scalability, and flexibility. It enables efficient routing, middleware management, and static file serving with minimal configuration. Fully compatible with Node.js, Deno, and Bun.",
5
5
  "main": "cjs/index.js",
6
6
  "module": "index.js",