hi-secure 1.0.15 → 1.0.17

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 (95) hide show
  1. package/dist/adapters/ArgonAdapter.d.ts +1 -1
  2. package/dist/adapters/ArgonAdapter.d.ts.map +1 -1
  3. package/dist/adapters/ArgonAdapter.js +7 -5
  4. package/dist/adapters/ArgonAdapter.js.map +1 -1
  5. package/dist/adapters/BcryptAdapter.d.ts.map +1 -1
  6. package/dist/adapters/BcryptAdapter.js +7 -3
  7. package/dist/adapters/BcryptAdapter.js.map +1 -1
  8. package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
  9. package/dist/adapters/ExpressRLAdapter.js +10 -6
  10. package/dist/adapters/ExpressRLAdapter.js.map +1 -1
  11. package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
  12. package/dist/adapters/ExpressValidatorAdapter.js +14 -10
  13. package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
  14. package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
  15. package/dist/adapters/GoogleAdapter.js +19 -16
  16. package/dist/adapters/GoogleAdapter.js.map +1 -1
  17. package/dist/adapters/JWTAdapter.d.ts.map +1 -1
  18. package/dist/adapters/JWTAdapter.js +25 -15
  19. package/dist/adapters/JWTAdapter.js.map +1 -1
  20. package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
  21. package/dist/adapters/RLFlexibleAdapter.js +23 -12
  22. package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
  23. package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
  24. package/dist/adapters/SanitizeHtmlAdapter.js +17 -13
  25. package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
  26. package/dist/adapters/XSSAdapter.d.ts +1 -1
  27. package/dist/adapters/XSSAdapter.d.ts.map +1 -1
  28. package/dist/adapters/XSSAdapter.js +21 -20
  29. package/dist/adapters/XSSAdapter.js.map +1 -1
  30. package/dist/adapters/ZodAdapter.d.ts +1 -1
  31. package/dist/adapters/ZodAdapter.d.ts.map +1 -1
  32. package/dist/adapters/ZodAdapter.js +10 -8
  33. package/dist/adapters/ZodAdapter.js.map +1 -1
  34. package/dist/core/HiSecure.d.ts +3 -4
  35. package/dist/core/HiSecure.d.ts.map +1 -1
  36. package/dist/core/HiSecure.js +91 -120
  37. package/dist/core/HiSecure.js.map +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +8 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/logging/morganSetup.d.ts.map +1 -1
  43. package/dist/logging/morganSetup.js +8 -1
  44. package/dist/logging/morganSetup.js.map +1 -1
  45. package/dist/logging/winstonSetup.d.ts.map +1 -1
  46. package/dist/logging/winstonSetup.js +17 -3
  47. package/dist/logging/winstonSetup.js.map +1 -1
  48. package/dist/managers/AuthManager.d.ts +2 -2
  49. package/dist/managers/AuthManager.d.ts.map +1 -1
  50. package/dist/managers/AuthManager.js +59 -31
  51. package/dist/managers/AuthManager.js.map +1 -1
  52. package/dist/managers/CorsManager.d.ts.map +1 -1
  53. package/dist/managers/CorsManager.js +18 -11
  54. package/dist/managers/CorsManager.js.map +1 -1
  55. package/dist/managers/HashManager.d.ts +1 -1
  56. package/dist/managers/HashManager.d.ts.map +1 -1
  57. package/dist/managers/HashManager.js +35 -17
  58. package/dist/managers/HashManager.js.map +1 -1
  59. package/dist/managers/JsonManager.d.ts +1 -1
  60. package/dist/managers/JsonManager.d.ts.map +1 -1
  61. package/dist/managers/JsonManager.js +44 -16
  62. package/dist/managers/JsonManager.js.map +1 -1
  63. package/dist/managers/RateLimitManager.d.ts +1 -1
  64. package/dist/managers/RateLimitManager.d.ts.map +1 -1
  65. package/dist/managers/RateLimitManager.js +43 -22
  66. package/dist/managers/RateLimitManager.js.map +1 -1
  67. package/dist/managers/SanitizerManager.d.ts.map +1 -1
  68. package/dist/managers/SanitizerManager.js +32 -15
  69. package/dist/managers/SanitizerManager.js.map +1 -1
  70. package/dist/managers/ValidatorManager.d.ts.map +1 -1
  71. package/dist/managers/ValidatorManager.js +31 -7
  72. package/dist/managers/ValidatorManager.js.map +1 -1
  73. package/package.json +2 -6
  74. package/readme.md +3 -6
  75. package/src/adapters/ArgonAdapter.ts +10 -6
  76. package/src/adapters/BcryptAdapter.ts +7 -8
  77. package/src/adapters/ExpressRLAdapter.ts +14 -9
  78. package/src/adapters/ExpressValidatorAdapter.ts +17 -11
  79. package/src/adapters/GoogleAdapter.ts +24 -21
  80. package/src/adapters/JWTAdapter.ts +33 -21
  81. package/src/adapters/RLFlexibleAdapter.ts +31 -16
  82. package/src/adapters/SanitizeHtmlAdapter.ts +28 -18
  83. package/src/adapters/XSSAdapter.ts +33 -38
  84. package/src/adapters/ZodAdapter.ts +10 -10
  85. package/src/core/HiSecure.ts +127 -161
  86. package/src/index.ts +4 -0
  87. package/src/logging/morganSetup.ts +11 -1
  88. package/src/logging/winstonSetup.ts +35 -8
  89. package/src/managers/AuthManager.ts +64 -34
  90. package/src/managers/CorsManager.ts +23 -16
  91. package/src/managers/HashManager.ts +48 -19
  92. package/src/managers/JsonManager.ts +57 -15
  93. package/src/managers/RateLimitManager.ts +61 -29
  94. package/src/managers/SanitizerManager.ts +47 -25
  95. package/src/managers/ValidatorManager.ts +40 -15
@@ -1,6 +1,6 @@
1
1
  import { validationResult } from "express-validator";
2
- import { ValidationError } from "../core/errors/ValidationError.js";
3
- import { logger } from "../logging/index.js";
2
+ import { ValidationError } from "../core/errors/ValidationError";
3
+ import { logger } from "../logging";
4
4
 
5
5
  export class ExpressValidatorAdapter {
6
6
  private globalSchema?: any[];
@@ -23,24 +23,30 @@ export class ExpressValidatorAdapter {
23
23
  const errors = validationResult(req);
24
24
 
25
25
  if (!errors.isEmpty()) {
26
- const formatted = errors.array().map(err => ({
26
+ const formattedErrors = errors.array().map(err => ({
27
27
  message: err.msg,
28
- // param: err.param ,
29
- // location: err.location
28
+ field: err.type
30
29
  }));
31
30
 
32
- logger.warn("express-validator failed", {
33
- path: req.path,
31
+ logger.warn("Request validation failed", {
32
+ adapter: "express-validator",
33
+ operation: "validate",
34
34
  method: req.method,
35
- errors: formatted,
36
- preview: JSON.stringify(req.body).slice(0, 200)
35
+ path: req.path,
36
+ errorCount: formattedErrors.length,
37
+ errors: formattedErrors,
38
+ bodyPreview: req.body
39
+ ? JSON.stringify(req.body).slice(0, 150)
40
+ : undefined
37
41
  });
38
42
 
39
- return next(new ValidationError("Validation failed.", formatted as any));
43
+ return next(
44
+ new ValidationError("Validation failed.", formattedErrors as any)
45
+ );
40
46
  }
41
47
 
42
48
  next();
43
49
  }
44
50
  ];
45
51
  }
46
- }
52
+ }
@@ -1,7 +1,6 @@
1
1
  import { OAuth2Client, LoginTicket } from "google-auth-library";
2
- import { AdapterError } from "../core/errors/AdapterError.js";
3
-
4
- import {logger} from '../logging';
2
+ import { AdapterError } from "../core/errors/AdapterError";
3
+ import { logger } from "../logging";
5
4
 
6
5
  export interface GoogleTokenPayload {
7
6
  sub: string;
@@ -20,60 +19,64 @@ export class GoogleAdapter {
20
19
  if (clientId && clientId.trim().length === 0) {
21
20
  throw new AdapterError("Google clientId cannot be empty string");
22
21
  }
23
-
22
+
24
23
  this.client = new OAuth2Client(clientId);
25
24
  this.clientId = clientId;
26
25
  }
27
26
 
28
27
  async verifyIdToken(idToken: string): Promise<GoogleTokenPayload> {
29
28
  try {
30
- if (!idToken || typeof idToken !== 'string') {
29
+ if (!idToken || typeof idToken !== "string") {
31
30
  throw new AdapterError("Invalid ID token provided");
32
31
  }
33
32
 
34
- const options: { idToken: string; audience?: string | string[] } = {
35
- idToken
33
+ const options: { idToken: string; audience?: string | string[] } = {
34
+ idToken
36
35
  };
37
36
 
38
- // audience only if clientId is provided and not empty
39
37
  if (this.clientId && this.clientId.trim().length > 0) {
40
38
  options.audience = this.clientId;
41
39
  }
42
40
 
43
41
  const ticket: LoginTicket = await this.client.verifyIdToken(options);
44
42
  const payload = ticket.getPayload();
45
-
43
+
46
44
  if (!payload) {
47
- logger.warn("GoogleAdapter: Empty payload");
45
+ logger.warn("Google ID token payload empty", {
46
+ adapter: "google-auth",
47
+ operation: "verifyIdToken",
48
+ hasClientId: !!this.clientId
49
+ });
50
+
48
51
  throw new AdapterError("Invalid Google ID token payload.");
49
52
  }
50
53
 
51
- // result object
52
54
  const result: GoogleTokenPayload = {
53
55
  sub: payload.sub,
54
- email: payload.email || '',
56
+ email: payload.email || "",
55
57
  email_verified: payload.email_verified || false,
56
58
  name: payload.name,
57
59
  picture: payload.picture
58
60
  };
59
61
 
60
- // remaining properties from payload
61
62
  const { sub, email, email_verified, name, picture, ...rest } = payload;
62
63
  Object.assign(result, rest);
63
64
 
64
65
  return result;
65
66
 
66
67
  } catch (err: any) {
67
- logger.error("GoogleAdapter.verifyIdToken failed", {
68
- error: err?.message,
69
- hasClientId: !!this.clientId
68
+ logger.error("Google ID token verification failed", {
69
+ adapter: "google-auth",
70
+ operation: "verifyIdToken",
71
+ hasClientId: !!this.clientId,
72
+ reason: err?.message
70
73
  });
71
-
72
- if (err.message?.includes('audience')) {
74
+
75
+ if (err?.message?.includes("audience")) {
73
76
  throw new AdapterError("Invalid Google client ID configured.");
74
77
  }
75
-
76
- throw new AdapterError(err?.message || "Google token verification failed");
78
+
79
+ throw new AdapterError("Google token verification failed.");
77
80
  }
78
81
  }
79
- }
82
+ }
@@ -1,7 +1,6 @@
1
1
  import jwt from "jsonwebtoken";
2
- import { randomUUID } from "crypto";
3
- import { AdapterError } from "../core/errors/AdapterError.js";
4
- import { logError } from "../logging/index.js";
2
+ import { randomUUID } from "crypto";
3
+ import { AdapterError } from "../core/errors/AdapterError";
5
4
  import { logger } from "../logging";
6
5
 
7
6
  export interface JWTAdapterOptions {
@@ -14,7 +13,7 @@ export interface JWTAdapterOptions {
14
13
 
15
14
  export interface SignOptions {
16
15
  expiresIn?: string | number;
17
- jti?: string;
16
+ jti?: string;
18
17
  subject?: string;
19
18
  issuer?: string;
20
19
  audience?: string | string[];
@@ -33,13 +32,16 @@ export class JWTAdapter {
33
32
  }
34
33
 
35
34
  if (options.secret.length < 32) {
36
- logger.warn("JWT secret shorter than 32 chars. Consider using stronger secret.");
37
- // logError("JWT secret is too short (minimum 32 characters recommended)");
35
+ logger.warn("Weak JWT secret detected", {
36
+ adapter: "jwt",
37
+ operation: "init",
38
+ secretLength: options.secret.length
39
+ });
38
40
  }
39
41
 
40
42
  this.secret = options.secret;
41
43
  this.expiresIn = options.expiresIn;
42
- this.algorithm = options.algorithm || 'HS256'; // Default algorithm
44
+ this.algorithm = options.algorithm || "HS256";
43
45
  this.issuer = options.issuer;
44
46
  this.audience = options.audience;
45
47
  }
@@ -50,21 +52,26 @@ export class JWTAdapter {
50
52
  algorithm: this.algorithm,
51
53
  issuer: options?.issuer || this.issuer,
52
54
  audience: options?.audience || this.audience,
53
- jwtid: options?.jti || randomUUID(),
55
+ jwtid: options?.jti || randomUUID(),
54
56
  subject: options?.subject
55
57
  };
56
58
 
57
59
  if (options?.expiresIn !== undefined) {
58
- jwtOptions.expiresIn = options.expiresIn as number;
60
+ jwtOptions.expiresIn = options.expiresIn as any;
59
61
  } else if (this.expiresIn !== undefined) {
60
- jwtOptions.expiresIn = this.expiresIn as number;
62
+ jwtOptions.expiresIn = this.expiresIn as any;
61
63
  }
62
64
 
63
65
  return jwt.sign(payload, this.secret, jwtOptions);
64
66
 
65
67
  } catch (err: any) {
66
- logError("JWTAdapter.sign failed", { error: err?.message });
67
- throw new AdapterError(err?.message || "JWT sign failed");
68
+ logger.error("JWT signing failed", {
69
+ adapter: "jwt",
70
+ operation: "sign",
71
+ reason: err?.message
72
+ });
73
+
74
+ throw new AdapterError("JWT sign failed");
68
75
  }
69
76
  }
70
77
 
@@ -73,22 +80,27 @@ export class JWTAdapter {
73
80
  const verifyOptions: jwt.VerifyOptions = {
74
81
  algorithms: [this.algorithm],
75
82
  issuer: this.issuer,
76
- audience: options?.audience as string || this.audience as string
83
+ audience: (options?.audience || this.audience) as string
77
84
  };
78
85
 
79
86
  return jwt.verify(token, this.secret, verifyOptions);
87
+
80
88
  } catch (err: any) {
81
- logError("JWTAdapter.verify failed", { error: err?.message });
82
-
83
-
84
- if (err.name === 'TokenExpiredError') {
89
+ logger.error("JWT verification failed", {
90
+ adapter: "jwt",
91
+ operation: "verify",
92
+ reason: err?.message
93
+ });
94
+
95
+ if (err?.name === "TokenExpiredError") {
85
96
  throw new AdapterError("JWT token has expired");
86
97
  }
87
- if (err.name === 'JsonWebTokenError') {
98
+
99
+ if (err?.name === "JsonWebTokenError") {
88
100
  throw new AdapterError("Invalid JWT token");
89
101
  }
90
-
91
- throw new AdapterError(err?.message || "JWT verification failed");
102
+
103
+ throw new AdapterError("JWT verification failed");
92
104
  }
93
105
  }
94
- }
106
+ }
@@ -1,10 +1,10 @@
1
1
  import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
2
- import { logger } from "../logging/index.js";
3
- import { AdapterError } from "../core/errors/AdapterError.js";
2
+ import { logger } from "../logging";
3
+ import { AdapterError } from "../core/errors/AdapterError";
4
4
 
5
5
  export interface RLOptions {
6
6
  points?: number;
7
- duration?: number;
7
+ duration?: number;
8
8
  message?: any;
9
9
  blockDuration?: number;
10
10
  }
@@ -27,8 +27,16 @@ export class RLFlexibleAdapter {
27
27
  blockDuration: finalOptions.blockDuration
28
28
  });
29
29
 
30
+
31
+ logger.info("Rate limiter initialized", {
32
+ adapter: "rate-limiter-flexible",
33
+ operation: "init",
34
+ points: finalOptions.points,
35
+ duration: finalOptions.duration,
36
+ blockDuration: finalOptions.blockDuration
37
+ });
38
+
30
39
  return async (req: any, res: any, next: any) => {
31
-
32
40
  const ip = this.extractIP(req);
33
41
 
34
42
  try {
@@ -37,15 +45,20 @@ export class RLFlexibleAdapter {
37
45
  } catch (err: any) {
38
46
  const rlErr = err as RateLimiterRes;
39
47
 
40
- logger.warn("RLFlexibleAdapter: rate limit exceeded", {
48
+ logger.warn("Rate limit exceeded", {
49
+ adapter: "rate-limiter-flexible",
50
+ operation: "consume",
41
51
  ip,
42
- path: req.path,
43
52
  method: req.method,
44
- retryAfter: rlErr.msBeforeNext
53
+ path: req.path,
54
+ retryAfterMs: rlErr.msBeforeNext
45
55
  });
46
56
 
47
- res.setHeader('Retry-After', Math.ceil(rlErr.msBeforeNext / 1000));
48
-
57
+ res.setHeader(
58
+ "Retry-After",
59
+ Math.ceil(rlErr.msBeforeNext / 1000)
60
+ );
61
+
49
62
  return res.status(429).json({
50
63
  success: false,
51
64
  error: "RATE_LIMIT_EXCEEDED",
@@ -54,23 +67,25 @@ export class RLFlexibleAdapter {
54
67
  });
55
68
  }
56
69
  };
57
-
58
70
  } catch (err: any) {
59
- logger.error("RLFlexibleAdapter: failed to initialize limiter", {
60
- error: err?.message || err
71
+ logger.error("Rate limiter initialization failed", {
72
+ adapter: "rate-limiter-flexible",
73
+ operation: "init",
74
+ reason: err?.message
61
75
  });
76
+
62
77
  throw new AdapterError("RateLimiterFlexible creation failed.");
63
78
  }
64
79
  }
65
80
 
66
81
  private extractIP(req: any): string {
67
82
  return (
68
- req.headers['x-real-ip'] ||
69
- req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
83
+ req.headers["x-real-ip"] ||
84
+ req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
70
85
  req.ip ||
71
86
  req.connection?.remoteAddress ||
72
87
  req.socket?.remoteAddress ||
73
- 'unknown'
88
+ "unknown"
74
89
  );
75
90
  }
76
- }
91
+ }
@@ -1,6 +1,6 @@
1
1
  import sanitizeHtml from "sanitize-html";
2
- import { AdapterError } from "../core/errors/AdapterError.js";
3
- import { logger } from "../logging/index.js";
2
+ import { AdapterError } from "../core/errors/AdapterError";
3
+ import { logger } from "../logging";
4
4
 
5
5
  export class SanitizeHtmlAdapter {
6
6
  private globalOptions: sanitizeHtml.IOptions;
@@ -12,27 +12,25 @@ export class SanitizeHtmlAdapter {
12
12
  sanitize(input: string, dynamicOptions?: any): string {
13
13
  try {
14
14
  const opts = { ...this.globalOptions, ...(dynamicOptions || {}) };
15
-
16
15
  const clean = sanitizeHtml(input, opts);
16
+
17
17
  return typeof clean === "string" ? clean : String(clean);
18
18
 
19
19
  } catch (err: any) {
20
- logger.error("sanitize-html failed", {
21
- error: err?.message || err,
22
- preview: typeof input === "string" ? input.slice(0, 100) : undefined
20
+ logger.error("HTML sanitization failed", {
21
+ adapter: "sanitize-html",
22
+ operation: "sanitize",
23
+ reason: err?.message
23
24
  });
24
25
 
25
26
  throw new AdapterError("sanitize-html adapter failed.");
26
27
  }
27
28
  }
28
29
 
29
- // Deep Sanitization - Recursively
30
+ // Deep Sanitization - recursively
30
31
  private deepSanitize(obj: any, dynamicOptions?: any, visited = new WeakSet()): any {
31
-
32
32
  if (obj && typeof obj === "object") {
33
- if (visited.has(obj)) {
34
- return obj;
35
- }
33
+ if (visited.has(obj)) return obj;
36
34
  visited.add(obj);
37
35
  }
38
36
 
@@ -41,13 +39,19 @@ export class SanitizeHtmlAdapter {
41
39
  }
42
40
 
43
41
  if (Array.isArray(obj)) {
44
- return obj.map((item) => this.deepSanitize(item, dynamicOptions, visited));
42
+ return obj.map(item =>
43
+ this.deepSanitize(item, dynamicOptions, visited)
44
+ );
45
45
  }
46
46
 
47
47
  if (obj && typeof obj === "object") {
48
48
  const result: any = {};
49
49
  for (const key of Object.keys(obj)) {
50
- result[key] = this.deepSanitize(obj[key], dynamicOptions, visited);
50
+ result[key] = this.deepSanitize(
51
+ obj[key],
52
+ dynamicOptions,
53
+ visited
54
+ );
51
55
  }
52
56
  return result;
53
57
  }
@@ -61,18 +65,24 @@ export class SanitizeHtmlAdapter {
61
65
  if (req.body) {
62
66
  req.body = this.deepSanitize(req.body, dynamicOptions);
63
67
 
64
- logger.debug("sanitize-html applied", {
68
+
69
+ logger.info("HTML sanitization applied", {
70
+ adapter: "sanitize-html",
71
+ operation: "middleware",
65
72
  keys: Object.keys(req.body)
66
73
  });
67
74
  }
68
- next();
69
75
 
76
+ next();
70
77
  } catch (err: any) {
71
- logger.error("sanitize-html middleware failed", {
72
- error: err?.message || err
78
+ logger.error("HTML sanitization middleware failed", {
79
+ adapter: "sanitize-html",
80
+ operation: "middleware",
81
+ reason: err?.message
73
82
  });
83
+
74
84
  next(err);
75
85
  }
76
86
  };
77
87
  }
78
- }
88
+ }
@@ -1,6 +1,6 @@
1
- import { FilterXSS, getDefaultWhiteList, whiteList } from 'xss';
2
- import { AdapterError } from "../core/errors/AdapterError.js";
3
- import { logger } from "../logging/index.js";
1
+ import { FilterXSS, getDefaultWhiteList, whiteList } from "xss";
2
+ import { AdapterError } from "../core/errors/AdapterError";
3
+ import { logger } from "../logging";
4
4
 
5
5
  export interface XSSOptions {
6
6
  whiteList?: typeof whiteList;
@@ -20,18 +20,19 @@ export class XSSAdapter {
20
20
 
21
21
  constructor(options: XSSOptions = {}) {
22
22
  this.globalOptions = options;
23
-
24
- // Default safe configuration
23
+
25
24
  const defaultOptions: XSSOptions = {
26
25
  whiteList: getDefaultWhiteList(),
27
- stripIgnoreTag: true,
28
- stripIgnoreTagBody: ['script', 'style', 'iframe', 'object', 'embed'],
26
+ stripIgnoreTag: true,
27
+ stripIgnoreTagBody: ["script", "style", "iframe", "object", "embed"],
29
28
  allowCommentTag: false,
30
- css: false,
31
- onTag: (tag, html, options) => {
32
-
33
- if (tag === 'a') {
34
- return html.replace(/<a /i, '<a target="_blank" rel="noopener noreferrer" ');
29
+ css: false,
30
+ onTag: (tag, html) => {
31
+ if (tag === "a") {
32
+ return html.replace(
33
+ /<a /i,
34
+ '<a target="_blank" rel="noopener noreferrer" '
35
+ );
35
36
  }
36
37
  return html;
37
38
  }
@@ -41,86 +42,80 @@ export class XSSAdapter {
41
42
  this.defaultFilter = new FilterXSS(finalOptions);
42
43
  }
43
44
 
44
-
45
45
  sanitize(input: string, dynamicOptions?: XSSOptions): string {
46
46
  try {
47
- if (typeof input !== "string") {
48
- return input as any;
49
- }
47
+ if (typeof input !== "string") return input as any;
50
48
 
51
-
52
49
  if (!dynamicOptions || Object.keys(dynamicOptions).length === 0) {
53
50
  return this.defaultFilter.process(input);
54
51
  }
55
52
 
56
-
57
53
  const mergedOptions = { ...this.globalOptions, ...dynamicOptions };
58
54
  const customFilter = new FilterXSS(mergedOptions);
59
-
55
+
60
56
  return customFilter.process(input);
61
57
 
62
58
  } catch (err: any) {
63
- logger.error("XSS sanitizer failed", {
64
- error: err?.message,
65
- preview: input?.slice?.(0, 80)
59
+ logger.error("XSS sanitization failed", {
60
+ adapter: "xss",
61
+ operation: "sanitize",
62
+ reason: err?.message
66
63
  });
64
+
67
65
  throw new AdapterError("XSS sanitizer failed.");
68
66
  }
69
67
  }
70
68
 
71
-
72
69
  middleware(dynamicOptions?: XSSOptions) {
73
70
  return (req: any, _res: any, next: any) => {
74
71
  try {
75
72
  if (req.body && typeof req.body === "object") {
76
73
  const originalBody = req.body;
77
74
  const sanitizedBody: any = Array.isArray(originalBody) ? [] : {};
78
-
75
+
79
76
  for (const key of Object.keys(originalBody)) {
80
77
  const val = originalBody[key];
81
78
 
82
79
  if (typeof val === "string") {
83
80
  sanitizedBody[key] = this.sanitize(val, dynamicOptions);
84
81
  } else if (Array.isArray(val)) {
85
- sanitizedBody[key] = val.map((v) =>
82
+ sanitizedBody[key] = val.map(v =>
86
83
  typeof v === "string"
87
84
  ? this.sanitize(v, dynamicOptions)
88
85
  : v
89
86
  );
90
87
  } else if (val && typeof val === "object") {
91
-
92
88
  sanitizedBody[key] = this.deepSanitize(val, dynamicOptions);
93
89
  } else {
94
90
  sanitizedBody[key] = val;
95
91
  }
96
92
  }
97
-
98
-
93
+
99
94
  req.sanitizedBody = sanitizedBody;
95
+
100
96
 
101
- logger.debug("XSS sanitizer applied", {
102
- originalKeys: Object.keys(originalBody),
103
- sanitizedKeys: Object.keys(sanitizedBody)
97
+ logger.info("XSS sanitization applied", {
98
+ adapter: "xss",
99
+ operation: "middleware",
100
+ keys: Object.keys(sanitizedBody)
104
101
  });
105
102
  }
106
103
 
107
104
  next();
108
105
  } catch (err: any) {
109
106
  logger.error("XSS middleware failed", {
110
- error: err?.message || err
107
+ adapter: "xss",
108
+ operation: "middleware",
109
+ reason: err?.message
111
110
  });
112
111
  next(err);
113
112
  }
114
113
  };
115
114
  }
116
115
 
117
-
118
116
  private deepSanitize(obj: any, options?: XSSOptions, visited = new WeakSet()): any {
119
-
120
117
  if (obj && typeof obj === "object") {
121
- if (visited.has(obj)) {
122
- return obj;
123
- }
118
+ if (visited.has(obj)) return obj;
124
119
  visited.add(obj);
125
120
  }
126
121
 
@@ -142,4 +137,4 @@ export class XSSAdapter {
142
137
 
143
138
  return obj;
144
139
  }
145
- }
140
+ }
@@ -1,6 +1,6 @@
1
1
  import { ZodSchema, ZodError } from "zod";
2
- import { ValidationError } from "../core/errors/ValidationError.js";
3
- import { logger } from "../logging/index.js";
2
+ import { ValidationError } from "../core/errors/ValidationError";
3
+ import { logger } from "../logging";
4
4
 
5
5
  export class ZodAdapter {
6
6
  private globalSchema?: ZodSchema;
@@ -10,13 +10,11 @@ export class ZodAdapter {
10
10
  }
11
11
 
12
12
  validate(dynamicSchema?: ZodSchema) {
13
- return (req: any, res: any, next: any) => {
13
+ return (req: any, _res: any, next: any) => {
14
14
  const schema = dynamicSchema || this.globalSchema;
15
-
16
15
  if (!schema) return next();
17
16
 
18
17
  const result = schema.safeParse(req.body);
19
-
20
18
  if (result.success) return next();
21
19
 
22
20
  const zodErr: ZodError = result.error;
@@ -28,15 +26,17 @@ export class ZodAdapter {
28
26
  }));
29
27
 
30
28
  logger.warn("Zod validation failed", {
31
- path: req.path,
29
+ adapter: "zod",
30
+ operation: "validate",
32
31
  method: req.method,
33
- issues,
34
- preview: JSON.stringify(req.body).slice(0, 200)
32
+ path: req.path,
33
+ issueCount: issues.length,
34
+ issues
35
35
  });
36
36
 
37
37
  return next(
38
- new ValidationError("Validation failed.", issues as any)
38
+ new ValidationError("Validation failed.", issues as any)
39
39
  );
40
40
  };
41
41
  }
42
- }
42
+ }