@pawells/nestjs-shared 1.0.0-dev.4c8c698

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 (286) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +802 -0
  3. package/build/LICENSE +21 -0
  4. package/build/README.md +802 -0
  5. package/build/common/common.module.d.ts +49 -0
  6. package/build/common/common.module.d.ts.map +1 -0
  7. package/build/common/common.module.js +178 -0
  8. package/build/common/common.module.js.map +1 -0
  9. package/build/common/constants/histogram-buckets.constants.d.ts +12 -0
  10. package/build/common/constants/histogram-buckets.constants.d.ts.map +1 -0
  11. package/build/common/constants/histogram-buckets.constants.js +51 -0
  12. package/build/common/constants/histogram-buckets.constants.js.map +1 -0
  13. package/build/common/constants/http-status.constants.d.ts +27 -0
  14. package/build/common/constants/http-status.constants.d.ts.map +1 -0
  15. package/build/common/constants/http-status.constants.js +27 -0
  16. package/build/common/constants/http-status.constants.js.map +1 -0
  17. package/build/common/constants/timeout.constants.d.ts +29 -0
  18. package/build/common/constants/timeout.constants.d.ts.map +1 -0
  19. package/build/common/constants/timeout.constants.js +45 -0
  20. package/build/common/constants/timeout.constants.js.map +1 -0
  21. package/build/common/controllers/metrics.controller.d.ts +23 -0
  22. package/build/common/controllers/metrics.controller.d.ts.map +1 -0
  23. package/build/common/controllers/metrics.controller.js +66 -0
  24. package/build/common/controllers/metrics.controller.js.map +1 -0
  25. package/build/common/decorators/common-decorators.d.ts +90 -0
  26. package/build/common/decorators/common-decorators.d.ts.map +1 -0
  27. package/build/common/decorators/common-decorators.js +101 -0
  28. package/build/common/decorators/common-decorators.js.map +1 -0
  29. package/build/common/decorators/decorator-factory.d.ts +108 -0
  30. package/build/common/decorators/decorator-factory.d.ts.map +1 -0
  31. package/build/common/decorators/decorator-factory.js +104 -0
  32. package/build/common/decorators/decorator-factory.js.map +1 -0
  33. package/build/common/decorators/guard.decorators.d.ts +48 -0
  34. package/build/common/decorators/guard.decorators.d.ts.map +1 -0
  35. package/build/common/decorators/guard.decorators.js +49 -0
  36. package/build/common/decorators/guard.decorators.js.map +1 -0
  37. package/build/common/decorators/index.d.ts +10 -0
  38. package/build/common/decorators/index.d.ts.map +1 -0
  39. package/build/common/decorators/index.js +11 -0
  40. package/build/common/decorators/index.js.map +1 -0
  41. package/build/common/decorators/instrument.decorator.d.ts +128 -0
  42. package/build/common/decorators/instrument.decorator.d.ts.map +1 -0
  43. package/build/common/decorators/instrument.decorator.js +165 -0
  44. package/build/common/decorators/instrument.decorator.js.map +1 -0
  45. package/build/common/decorators/metric.decorators.d.ts +42 -0
  46. package/build/common/decorators/metric.decorators.d.ts.map +1 -0
  47. package/build/common/decorators/metric.decorators.js +85 -0
  48. package/build/common/decorators/metric.decorators.js.map +1 -0
  49. package/build/common/decorators/request-property.decorator.d.ts +65 -0
  50. package/build/common/decorators/request-property.decorator.d.ts.map +1 -0
  51. package/build/common/decorators/request-property.decorator.js +102 -0
  52. package/build/common/decorators/request-property.decorator.js.map +1 -0
  53. package/build/common/errors/base-application-error.d.ts +98 -0
  54. package/build/common/errors/base-application-error.d.ts.map +1 -0
  55. package/build/common/errors/base-application-error.js +133 -0
  56. package/build/common/errors/base-application-error.js.map +1 -0
  57. package/build/common/errors/error-factory.d.ts +93 -0
  58. package/build/common/errors/error-factory.d.ts.map +1 -0
  59. package/build/common/errors/error-factory.js +105 -0
  60. package/build/common/errors/error-factory.js.map +1 -0
  61. package/build/common/errors/index.d.ts +13 -0
  62. package/build/common/errors/index.d.ts.map +1 -0
  63. package/build/common/errors/index.js +15 -0
  64. package/build/common/errors/index.js.map +1 -0
  65. package/build/common/factories/index.d.ts +5 -0
  66. package/build/common/factories/index.d.ts.map +1 -0
  67. package/build/common/factories/index.js +3 -0
  68. package/build/common/factories/index.js.map +1 -0
  69. package/build/common/factories/module-factory.d.ts +178 -0
  70. package/build/common/factories/module-factory.d.ts.map +1 -0
  71. package/build/common/factories/module-factory.js +253 -0
  72. package/build/common/factories/module-factory.js.map +1 -0
  73. package/build/common/factories/rate-limit-config.factory.d.ts +79 -0
  74. package/build/common/factories/rate-limit-config.factory.d.ts.map +1 -0
  75. package/build/common/factories/rate-limit-config.factory.js +115 -0
  76. package/build/common/factories/rate-limit-config.factory.js.map +1 -0
  77. package/build/common/factories/security-bootstrap.factory.d.ts +77 -0
  78. package/build/common/factories/security-bootstrap.factory.d.ts.map +1 -0
  79. package/build/common/factories/security-bootstrap.factory.js +222 -0
  80. package/build/common/factories/security-bootstrap.factory.js.map +1 -0
  81. package/build/common/filters/global-exception.filter.d.ts +78 -0
  82. package/build/common/filters/global-exception.filter.d.ts.map +1 -0
  83. package/build/common/filters/global-exception.filter.js +192 -0
  84. package/build/common/filters/global-exception.filter.js.map +1 -0
  85. package/build/common/filters/http-exception.filter.d.ts +37 -0
  86. package/build/common/filters/http-exception.filter.d.ts.map +1 -0
  87. package/build/common/filters/http-exception.filter.js +91 -0
  88. package/build/common/filters/http-exception.filter.js.map +1 -0
  89. package/build/common/guards/csrf.guard.d.ts +53 -0
  90. package/build/common/guards/csrf.guard.d.ts.map +1 -0
  91. package/build/common/guards/csrf.guard.js +109 -0
  92. package/build/common/guards/csrf.guard.js.map +1 -0
  93. package/build/common/guards/metrics.guard.d.ts +42 -0
  94. package/build/common/guards/metrics.guard.d.ts.map +1 -0
  95. package/build/common/guards/metrics.guard.js +124 -0
  96. package/build/common/guards/metrics.guard.js.map +1 -0
  97. package/build/common/index.d.ts +43 -0
  98. package/build/common/index.d.ts.map +1 -0
  99. package/build/common/index.js +50 -0
  100. package/build/common/index.js.map +1 -0
  101. package/build/common/interceptors/http-client.interceptor.d.ts +11 -0
  102. package/build/common/interceptors/http-client.interceptor.d.ts.map +1 -0
  103. package/build/common/interceptors/http-client.interceptor.js +69 -0
  104. package/build/common/interceptors/http-client.interceptor.js.map +1 -0
  105. package/build/common/interceptors/http-instrumentation.interceptor.d.ts +64 -0
  106. package/build/common/interceptors/http-instrumentation.interceptor.d.ts.map +1 -0
  107. package/build/common/interceptors/http-instrumentation.interceptor.js +148 -0
  108. package/build/common/interceptors/http-instrumentation.interceptor.js.map +1 -0
  109. package/build/common/interceptors/http-metrics.interceptor.d.ts +46 -0
  110. package/build/common/interceptors/http-metrics.interceptor.d.ts.map +1 -0
  111. package/build/common/interceptors/http-metrics.interceptor.js +120 -0
  112. package/build/common/interceptors/http-metrics.interceptor.js.map +1 -0
  113. package/build/common/interceptors/logging.interceptor.d.ts +22 -0
  114. package/build/common/interceptors/logging.interceptor.d.ts.map +1 -0
  115. package/build/common/interceptors/logging.interceptor.js +67 -0
  116. package/build/common/interceptors/logging.interceptor.js.map +1 -0
  117. package/build/common/interfaces/cache-provider.interface.d.ts +54 -0
  118. package/build/common/interfaces/cache-provider.interface.d.ts.map +1 -0
  119. package/build/common/interfaces/cache-provider.interface.js +6 -0
  120. package/build/common/interfaces/cache-provider.interface.js.map +1 -0
  121. package/build/common/interfaces/index.d.ts +7 -0
  122. package/build/common/interfaces/index.d.ts.map +1 -0
  123. package/build/common/interfaces/index.js +3 -0
  124. package/build/common/interfaces/index.js.map +1 -0
  125. package/build/common/interfaces/log-context.interface.d.ts +77 -0
  126. package/build/common/interfaces/log-context.interface.d.ts.map +1 -0
  127. package/build/common/interfaces/log-context.interface.js +2 -0
  128. package/build/common/interfaces/log-context.interface.js.map +1 -0
  129. package/build/common/interfaces/log-entry.interface.d.ts +26 -0
  130. package/build/common/interfaces/log-entry.interface.d.ts.map +1 -0
  131. package/build/common/interfaces/log-entry.interface.js +33 -0
  132. package/build/common/interfaces/log-entry.interface.js.map +1 -0
  133. package/build/common/interfaces/logger.interface.d.ts +62 -0
  134. package/build/common/interfaces/logger.interface.d.ts.map +1 -0
  135. package/build/common/interfaces/logger.interface.js +2 -0
  136. package/build/common/interfaces/logger.interface.js.map +1 -0
  137. package/build/common/interfaces/metrics-exporter.interface.d.ts +275 -0
  138. package/build/common/interfaces/metrics-exporter.interface.d.ts.map +1 -0
  139. package/build/common/interfaces/metrics-exporter.interface.js +8 -0
  140. package/build/common/interfaces/metrics-exporter.interface.js.map +1 -0
  141. package/build/common/metrics/base-metrics-collector.d.ts +81 -0
  142. package/build/common/metrics/base-metrics-collector.d.ts.map +1 -0
  143. package/build/common/metrics/base-metrics-collector.js +88 -0
  144. package/build/common/metrics/base-metrics-collector.js.map +1 -0
  145. package/build/common/metrics/index.d.ts +2 -0
  146. package/build/common/metrics/index.d.ts.map +1 -0
  147. package/build/common/metrics/index.js +2 -0
  148. package/build/common/metrics/index.js.map +1 -0
  149. package/build/common/metrics.module.d.ts +50 -0
  150. package/build/common/metrics.module.d.ts.map +1 -0
  151. package/build/common/metrics.module.js +77 -0
  152. package/build/common/metrics.module.js.map +1 -0
  153. package/build/common/modules/throttler.module.d.ts +69 -0
  154. package/build/common/modules/throttler.module.d.ts.map +1 -0
  155. package/build/common/modules/throttler.module.js +117 -0
  156. package/build/common/modules/throttler.module.js.map +1 -0
  157. package/build/common/pipes/base-validation.pipe.d.ts +67 -0
  158. package/build/common/pipes/base-validation.pipe.d.ts.map +1 -0
  159. package/build/common/pipes/base-validation.pipe.js +95 -0
  160. package/build/common/pipes/base-validation.pipe.js.map +1 -0
  161. package/build/common/pipes/validation.pipe.d.ts +32 -0
  162. package/build/common/pipes/validation.pipe.d.ts.map +1 -0
  163. package/build/common/pipes/validation.pipe.js +60 -0
  164. package/build/common/pipes/validation.pipe.js.map +1 -0
  165. package/build/common/registry/instrumentation-registry.d.ts +227 -0
  166. package/build/common/registry/instrumentation-registry.d.ts.map +1 -0
  167. package/build/common/registry/instrumentation-registry.js +414 -0
  168. package/build/common/registry/instrumentation-registry.js.map +1 -0
  169. package/build/common/services/audit-logger.service.d.ts +91 -0
  170. package/build/common/services/audit-logger.service.d.ts.map +1 -0
  171. package/build/common/services/audit-logger.service.js +180 -0
  172. package/build/common/services/audit-logger.service.js.map +1 -0
  173. package/build/common/services/csrf.service.d.ts +202 -0
  174. package/build/common/services/csrf.service.d.ts.map +1 -0
  175. package/build/common/services/csrf.service.js +478 -0
  176. package/build/common/services/csrf.service.js.map +1 -0
  177. package/build/common/services/error-categorizer.service.d.ts +82 -0
  178. package/build/common/services/error-categorizer.service.d.ts.map +1 -0
  179. package/build/common/services/error-categorizer.service.js +339 -0
  180. package/build/common/services/error-categorizer.service.js.map +1 -0
  181. package/build/common/services/error-sanitizer.service.d.ts +146 -0
  182. package/build/common/services/error-sanitizer.service.d.ts.map +1 -0
  183. package/build/common/services/error-sanitizer.service.js +287 -0
  184. package/build/common/services/error-sanitizer.service.js.map +1 -0
  185. package/build/common/services/health-check.service.d.ts +86 -0
  186. package/build/common/services/health-check.service.d.ts.map +1 -0
  187. package/build/common/services/health-check.service.js +132 -0
  188. package/build/common/services/health-check.service.js.map +1 -0
  189. package/build/common/services/http-client.service.d.ts +113 -0
  190. package/build/common/services/http-client.service.d.ts.map +1 -0
  191. package/build/common/services/http-client.service.js +294 -0
  192. package/build/common/services/http-client.service.js.map +1 -0
  193. package/build/common/services/logger.service.d.ts +189 -0
  194. package/build/common/services/logger.service.d.ts.map +1 -0
  195. package/build/common/services/logger.service.js +423 -0
  196. package/build/common/services/logger.service.js.map +1 -0
  197. package/build/common/services/metrics-registry.service.d.ts +98 -0
  198. package/build/common/services/metrics-registry.service.d.ts.map +1 -0
  199. package/build/common/services/metrics-registry.service.js +262 -0
  200. package/build/common/services/metrics-registry.service.js.map +1 -0
  201. package/build/common/services/nest-logger-adapter.service.d.ts +62 -0
  202. package/build/common/services/nest-logger-adapter.service.d.ts.map +1 -0
  203. package/build/common/services/nest-logger-adapter.service.js +120 -0
  204. package/build/common/services/nest-logger-adapter.service.js.map +1 -0
  205. package/build/common/utils/error.utils.d.ts +16 -0
  206. package/build/common/utils/error.utils.d.ts.map +1 -0
  207. package/build/common/utils/error.utils.js +26 -0
  208. package/build/common/utils/error.utils.js.map +1 -0
  209. package/build/common/utils/lazy-getter.types.d.ts +190 -0
  210. package/build/common/utils/lazy-getter.types.d.ts.map +1 -0
  211. package/build/common/utils/lazy-getter.types.js +114 -0
  212. package/build/common/utils/lazy-getter.types.js.map +1 -0
  213. package/build/common/utils/module.utils.d.ts +33 -0
  214. package/build/common/utils/module.utils.d.ts.map +1 -0
  215. package/build/common/utils/module.utils.js +48 -0
  216. package/build/common/utils/module.utils.js.map +1 -0
  217. package/build/common/utils/sanitization.utils.d.ts +69 -0
  218. package/build/common/utils/sanitization.utils.d.ts.map +1 -0
  219. package/build/common/utils/sanitization.utils.js +141 -0
  220. package/build/common/utils/sanitization.utils.js.map +1 -0
  221. package/build/config/config.module.d.ts +30 -0
  222. package/build/config/config.module.d.ts.map +1 -0
  223. package/build/config/config.module.js +49 -0
  224. package/build/config/config.module.js.map +1 -0
  225. package/build/config/config.service.d.ts +74 -0
  226. package/build/config/config.service.d.ts.map +1 -0
  227. package/build/config/config.service.js +145 -0
  228. package/build/config/config.service.js.map +1 -0
  229. package/build/config/config.types.d.ts +143 -0
  230. package/build/config/config.types.d.ts.map +1 -0
  231. package/build/config/config.types.js +2 -0
  232. package/build/config/config.types.js.map +1 -0
  233. package/build/config/decorators/config.decorators.d.ts +43 -0
  234. package/build/config/decorators/config.decorators.d.ts.map +1 -0
  235. package/build/config/decorators/config.decorators.js +68 -0
  236. package/build/config/decorators/config.decorators.js.map +1 -0
  237. package/build/config/decorators/index.d.ts +2 -0
  238. package/build/config/decorators/index.d.ts.map +1 -0
  239. package/build/config/decorators/index.js +2 -0
  240. package/build/config/decorators/index.js.map +1 -0
  241. package/build/config/index.d.ts +7 -0
  242. package/build/config/index.d.ts.map +1 -0
  243. package/build/config/index.js +9 -0
  244. package/build/config/index.js.map +1 -0
  245. package/build/config/validation.utils.d.ts +136 -0
  246. package/build/config/validation.utils.d.ts.map +1 -0
  247. package/build/config/validation.utils.js +263 -0
  248. package/build/config/validation.utils.js.map +1 -0
  249. package/build/errors/index.d.ts +9 -0
  250. package/build/errors/index.d.ts.map +1 -0
  251. package/build/errors/index.js +12 -0
  252. package/build/errors/index.js.map +1 -0
  253. package/build/guards/custom-throttle.guard.d.ts +28 -0
  254. package/build/guards/custom-throttle.guard.d.ts.map +1 -0
  255. package/build/guards/custom-throttle.guard.js +52 -0
  256. package/build/guards/custom-throttle.guard.js.map +1 -0
  257. package/build/guards/index.d.ts +2 -0
  258. package/build/guards/index.d.ts.map +1 -0
  259. package/build/guards/index.js +2 -0
  260. package/build/guards/index.js.map +1 -0
  261. package/build/index.d.ts +53 -0
  262. package/build/index.d.ts.map +1 -0
  263. package/build/index.js +61 -0
  264. package/build/index.js.map +1 -0
  265. package/build/logging/index.d.ts +7 -0
  266. package/build/logging/index.d.ts.map +1 -0
  267. package/build/logging/index.js +7 -0
  268. package/build/logging/index.js.map +1 -0
  269. package/build/metrics/index.d.ts +6 -0
  270. package/build/metrics/index.d.ts.map +1 -0
  271. package/build/metrics/index.js +11 -0
  272. package/build/metrics/index.js.map +1 -0
  273. package/build/package.json +72 -0
  274. package/build/security/index.d.ts +8 -0
  275. package/build/security/index.d.ts.map +1 -0
  276. package/build/security/index.js +11 -0
  277. package/build/security/index.js.map +1 -0
  278. package/build/test-setup.d.ts +2 -0
  279. package/build/test-setup.d.ts.map +1 -0
  280. package/build/test-setup.js +40 -0
  281. package/build/test-setup.js.map +1 -0
  282. package/build/validation/index.d.ts +6 -0
  283. package/build/validation/index.d.ts.map +1 -0
  284. package/build/validation/index.js +8 -0
  285. package/build/validation/index.js.map +1 -0
  286. package/package.json +71 -0
@@ -0,0 +1,478 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var CSRFService_1;
14
+ import { Injectable, Logger, HttpException, HttpStatus, Inject, Optional } from '@nestjs/common';
15
+ import { ConfigService } from '@nestjs/config';
16
+ import { doubleCsrf } from 'csrf-csrf';
17
+ import { escapeNewlines } from '../utils/sanitization.utils.js';
18
+ /**
19
+ * CSRF Service.
20
+ * Provides CSRF token generation and validation using the Double Submit Cookie pattern.
21
+ * Uses cryptographic signing and per-session/IP binding for secure token verification.
22
+ *
23
+ * Features:
24
+ * - Double-CSRF token pattern (cookie + request header/body)
25
+ * - Per-IP rate limiting: 10 tokens per 60 seconds
26
+ * - Session binding (when available) or IP-based fallback
27
+ * - Automatic pruning of stale timestamps
28
+ * - Capacity monitoring with safety margin (80% threshold)
29
+ * - SSL-only, HTTPOnly cookies in production
30
+ *
31
+ * Configuration requirements:
32
+ * - CSRF_SECRET: Min 32 characters, cryptographically random, high entropy
33
+ * - Express app with cookie parser middleware
34
+ * - Optional: trustProxy for reverse proxy environments
35
+ *
36
+ * @remarks
37
+ * - In-memory rate limiting supports ~10,000 concurrent IPs per instance
38
+ * - For distributed deployments, use Redis-backed SharedThrottlerModule instead
39
+ * - Token generation timeout: 30 seconds (queue timeout)
40
+ * - Token timestamp pruning interval: 10 seconds
41
+ * - IP map capacity threshold: 80% (8,000 IPs) before pruning
42
+ * - Returns 503 Service Unavailable if at capacity after pruning
43
+ * - Returns 429 Too Many Requests if rate limit exceeded
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Generate token for form
48
+ * const token = await csrfService.generateToken(req, res);
49
+ * res.render('form', { csrfToken: token });
50
+ *
51
+ * // Validate incoming request (done automatically by CSRFGuard)
52
+ * const isValid = csrfService.validateToken(req);
53
+ *
54
+ * // Refresh token after sensitive operation (login, password change)
55
+ * const newToken = await csrfService.refreshToken(req, res);
56
+ * ```
57
+ */
58
+ let CSRFService = class CSRFService {
59
+ static { CSRFService_1 = this; }
60
+ configService;
61
+ // Configuration constants
62
+ // eslint-disable-next-line no-magic-numbers
63
+ static MIN_SECRET_LENGTH = 32;
64
+ // eslint-disable-next-line no-magic-numbers
65
+ static MIN_CHARACTER_SET_DIVERSITY = 3;
66
+ // eslint-disable-next-line no-magic-numbers
67
+ static ANONYMOUS_TOKEN_RANDOMNESS_BYTES = 4;
68
+ // eslint-disable-next-line no-magic-numbers
69
+ static RATE_LIMIT_WINDOW_MS = 60_000; // 60 seconds
70
+ // eslint-disable-next-line no-magic-numbers
71
+ static RATE_LIMIT_COUNT = 10;
72
+ /**
73
+ * Maximum number of unique IP addresses tracked for rate limiting.
74
+ * This in-memory strategy supports ~10,000 concurrent IPs per instance.
75
+ *
76
+ * **Scaling guidance**: For deployments exceeding 10,000 concurrent unique IPs,
77
+ * migrate to a Redis-backed rate limiting solution via SharedThrottlerModule:
78
+ * - Configure SharedThrottlerModule with Redis backend
79
+ * - Adjust limits and TTL for your traffic pattern
80
+ *
81
+ * **Memory management**: When the tracked IPs map reaches 80% capacity
82
+ * (8,000 IPs), the pruning logic removes stale entries (>60s old).
83
+ * If after pruning the map remains >= 10,000 IPs, token generation is
84
+ * rejected with HTTP 503 to prevent unbounded memory growth.
85
+ * The 80% threshold provides a safety margin before hard limits are hit.
86
+ *
87
+ * @see SharedThrottlerModule for Redis-backed distributed rate limiting
88
+ */
89
+ // eslint-disable-next-line no-magic-numbers
90
+ static MAX_TRACKED_IPS = 10000;
91
+ // eslint-disable-next-line no-magic-numbers
92
+ static TIMESTAMP_PRUNING_INTERVAL_MS = 10 * 1000; // Prune every 10 seconds
93
+ // eslint-disable-next-line no-magic-numbers
94
+ static IP_LOCK_TIMEOUT_MS = 30_000; // 30 second timeout for queued requests
95
+ // eslint-disable-next-line no-magic-numbers
96
+ static CAPACITY_THRESHOLD_PERCENT = 0.8;
97
+ csrfProtection;
98
+ logger = new Logger('CSRFService');
99
+ tokenGenTimestamps = new Map();
100
+ ipLocks = new Map();
101
+ trustProxy;
102
+ capacityThresholdCrossedCount = 0;
103
+ pruneIntervalHandle;
104
+ _isPruning = false;
105
+ constructor(configService, options) {
106
+ this.configService = configService;
107
+ // Note: CSRF_SECRET validation is deferred to onModuleInit() to ensure
108
+ // NestJS surfaces the error at bootstrap time rather than during DI resolution
109
+ this.trustProxy = options?.trustProxy ?? false;
110
+ }
111
+ /**
112
+ * NestJS lifecycle hook: validate required CSRF_SECRET environment variable
113
+ * at application bootstrap time
114
+ */
115
+ onModuleInit() {
116
+ const csrfSecret = this.configService?.get('CSRF_SECRET') ?? process.env['CSRF_SECRET'];
117
+ if (!csrfSecret) {
118
+ throw new Error('CSRF_SECRET environment variable is required but not set. ' +
119
+ 'Set it in your .env file or as an environment variable before starting the application.');
120
+ }
121
+ // Validate CSRF_SECRET entropy
122
+ if (csrfSecret.length < CSRFService_1.MIN_SECRET_LENGTH) {
123
+ throw new Error(`CSRF_SECRET must be at least ${CSRFService_1.MIN_SECRET_LENGTH} characters long for adequate entropy. ` +
124
+ `Current length: ${csrfSecret.length}. ` +
125
+ 'Generate a cryptographically secure value with: openssl rand -base64 32');
126
+ }
127
+ // Check for obviously weak patterns as secondary check
128
+ if (this.isWeakSecret(csrfSecret)) {
129
+ throw new Error('CSRF_SECRET contains obviously weak pattern. ' +
130
+ 'Must have a mix of different characters, not repeated or common strings. ' +
131
+ 'Generate a cryptographically secure value with: openssl rand -base64 32');
132
+ }
133
+ // Validate entropy (primary check)
134
+ const entropy = this.calculateEntropy(csrfSecret);
135
+ const MIN_ENTROPY = 4.0;
136
+ if (entropy < MIN_ENTROPY) {
137
+ throw new Error(`CSRF_SECRET entropy is insufficient (${entropy.toFixed(2)} bits/char). ` +
138
+ `Minimum required: ${MIN_ENTROPY} bits/char. ` +
139
+ 'Generate a cryptographically secure value with: openssl rand -base64 32');
140
+ }
141
+ // Initialize CSRF protection now that we know the secret is available and valid
142
+ this.csrfProtection = doubleCsrf({
143
+ getSecret: () => csrfSecret,
144
+ getSessionIdentifier: (req) => this.getSessionIdentifier(req),
145
+ cookieName: '__Host-psifi.x-csrf-token',
146
+ cookieOptions: {
147
+ httpOnly: true,
148
+ secure: process.env['NODE_ENV'] === 'production',
149
+ sameSite: 'strict',
150
+ path: '/',
151
+ },
152
+ });
153
+ // Check for trust proxy misconfiguration at boot time
154
+ this.validateTrustProxyConfiguration();
155
+ // Prune old token generation timestamps more frequently to prevent unbounded growth
156
+ // More aggressive pruning than 5 minutes to handle high-traffic scenarios
157
+ this.pruneIntervalHandle = setInterval(() => this.pruneTokenTimestamps(), CSRFService_1.TIMESTAMP_PRUNING_INTERVAL_MS);
158
+ }
159
+ /**
160
+ * NestJS lifecycle hook: clear the pruning interval on module destroy
161
+ */
162
+ onModuleDestroy() {
163
+ if (this.pruneIntervalHandle !== undefined) {
164
+ clearInterval(this.pruneIntervalHandle);
165
+ this.pruneIntervalHandle = undefined;
166
+ }
167
+ // Clear maps to release memory on shutdown
168
+ this.tokenGenTimestamps.clear();
169
+ this.ipLocks.clear();
170
+ }
171
+ /**
172
+ * Validate trust proxy configuration to detect mismatches early
173
+ * Checks if X-Forwarded-For header support is configured correctly in both directions:
174
+ * - If trustProxy=false but X-Forwarded-For is present: may miss real client IP
175
+ * - If trustProxy=true but X-Forwarded-For is absent: proxy may not be configured correctly
176
+ */
177
+ validateTrustProxyConfiguration() {
178
+ // Check direction 1: trustProxy=false but service is likely behind a proxy
179
+ // We detect this by checking if the NODE_ENV and common proxy environments are misconfigured.
180
+ // Note: We do NOT call extractClientIp here to avoid spurious "X-Forwarded-For detected" warnings
181
+ // during module initialization with synthetic sample requests.
182
+ if (!this.trustProxy) {
183
+ // Nothing to warn about at init time — the runtime warning in extractClientIp
184
+ // will fire if real requests arrive with X-Forwarded-For headers.
185
+ return;
186
+ }
187
+ // Check direction 2: trustProxy=true but X-Forwarded-For absent (reverse proxy may not be configured)
188
+ // Use a sample request without X-Forwarded-For to detect this early
189
+ const sampleRequestWithoutHeader = { headers: {}, socket: { remoteAddress: '127.0.0.1' } };
190
+ const extractedIpWithoutHeader = this.extractClientIp(sampleRequestWithoutHeader);
191
+ if (extractedIpWithoutHeader === undefined || extractedIpWithoutHeader === null) {
192
+ this.logger.warn('Trust proxy is enabled but X-Forwarded-For header is not present in sample request. ' +
193
+ 'Your reverse proxy may not be configured to set X-Forwarded-For correctly. ' +
194
+ 'Verify your proxy (nginx, Apache, load balancer, etc.) is setting this header.');
195
+ }
196
+ }
197
+ /**
198
+ * Calculate Shannon entropy of a string to measure randomness.
199
+ * Higher entropy indicates better randomness quality.
200
+ *
201
+ * The minimum threshold of 4.0 bits/char is chosen because:
202
+ * - A uniformly random string from a 16-character alphabet (hex: 0-9a-f) has log2(16) = 4.0 bits/char
203
+ * - This ensures the CSRF_SECRET has sufficient entropy for cryptographic purposes
204
+ * - Values below 4.0 bits/char indicate the secret uses a limited character set or has patterns
205
+ *
206
+ * @param str - Input string to analyze
207
+ * @returns Entropy in bits per character
208
+ */
209
+ calculateEntropy(str) {
210
+ const freq = new Map();
211
+ for (const char of str) {
212
+ freq.set(char, (freq.get(char) ?? 0) + 1);
213
+ }
214
+ let entropy = 0;
215
+ for (const count of freq.values()) {
216
+ const p = count / str.length;
217
+ entropy -= p * Math.log2(p);
218
+ }
219
+ return entropy;
220
+ }
221
+ /**
222
+ * Check if the secret contains obviously weak patterns
223
+ */
224
+ isWeakSecret(secret) {
225
+ // Check if all characters are the same (e.g., 'aaaaa...')
226
+ if (/^(.)\1+$/.test(secret)) {
227
+ return true;
228
+ }
229
+ // Check for repeated character sequences (e.g., 'aaaa' or 'bbbb')
230
+ // Indicates low entropy and predictable patterns
231
+ if (/(.)\1{4,}/.test(secret)) {
232
+ return true;
233
+ }
234
+ // Check for common weak strings (case-insensitive)
235
+ const lowerSecret = secret.toLowerCase();
236
+ const weakPatterns = ['password', 'secret', '12345678', 'qwerty', '00000000', '11111111', '88888888', '99999999'];
237
+ if (weakPatterns.some(pattern => lowerSecret.includes(pattern))) {
238
+ return true;
239
+ }
240
+ return false;
241
+ }
242
+ /**
243
+ * Get session identifier for CSRF token binding
244
+ * Prefers session ID if available, falls back to IP address
245
+ * @param req - Express request object
246
+ * @returns Session identifier
247
+ */
248
+ getSessionIdentifier(req) {
249
+ const sessionId = req.session?.id;
250
+ if (sessionId)
251
+ return sessionId;
252
+ const ip = this.extractClientIp(req);
253
+ if (!ip) {
254
+ // Log warning — do not silently fall through to 'unknown'
255
+ this.logger.warn('CSRF session identifier unavailable: no session and no IP. ' +
256
+ 'Using fixed fallback bucket to enforce rate limiting on anonymous requests.');
257
+ // Use fixed fallback bucket so all anonymous requests share the same rate limit
258
+ return 'anon';
259
+ }
260
+ return ip;
261
+ }
262
+ /**
263
+ * Extract client IP address from request, respecting trustProxy setting
264
+ *
265
+ * **Note on Express vs Fastify**: This implementation uses `req.ip` (Express-specific).
266
+ * For Fastify deployments, ensure the `trustProxy` option is properly configured:
267
+ * - In Fastify, use `app.register(require('@fastify/proxy'), { ...config })`
268
+ * - Or set the Fastify instance option: `fastify({ trustProxy: true })`
269
+ * - Without correct Fastify configuration, IP detection will fail
270
+ *
271
+ * @param req - Express request object
272
+ * @returns Client IP address or null if unavailable
273
+ */
274
+ extractClientIp(req) {
275
+ // If trustProxy is enabled, use X-Forwarded-For header (first IP only)
276
+ if (this.trustProxy) {
277
+ const forwardedFor = req.headers['x-forwarded-for'];
278
+ if (forwardedFor) {
279
+ // X-Forwarded-For can contain multiple IPs: client, proxy1, proxy2...
280
+ // We only trust the first one (the original client)
281
+ const firstIp = Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor.split(',')[0];
282
+ return firstIp?.trim() ?? null;
283
+ }
284
+ }
285
+ else {
286
+ // Warn if X-Forwarded-For is present but trustProxy is false
287
+ // This may indicate a misconfiguration where the service is behind a reverse proxy
288
+ const forwardedFor = req.headers['x-forwarded-for'];
289
+ if (forwardedFor) {
290
+ this.logger.warn('X-Forwarded-For header detected but trustProxy is false. ' +
291
+ 'If this service is behind a reverse proxy (nginx, Apache, load balancer, etc.), ' +
292
+ 'enable trustProxy in CSRFService options to correctly identify client IPs. ' +
293
+ 'Otherwise, client IP detection will use direct socket IP.');
294
+ }
295
+ }
296
+ // Fall back to direct socket IP
297
+ return req.ip ?? (req.socket?.remoteAddress) ?? null;
298
+ }
299
+ /**
300
+ * Prune old token generation timestamps to prevent unbounded map growth
301
+ * Removes entries older than 60 seconds and cleans up idle IPs from both
302
+ * tokenGenTimestamps and ipLocks to prevent memory accumulation.
303
+ */
304
+ pruneTokenTimestamps() {
305
+ const now = Date.now();
306
+ const TIMESTAMP_TTL = 60_000; // 60 seconds
307
+ for (const [ip, timestamps] of this.tokenGenTimestamps.entries()) {
308
+ const recentTimestamps = timestamps.filter(ts => now - ts < TIMESTAMP_TTL);
309
+ if (recentTimestamps.length === 0) {
310
+ this.tokenGenTimestamps.delete(ip);
311
+ // Clean up corresponding ipLocks entry when IP has no timestamps
312
+ this.ipLocks.delete(ip);
313
+ }
314
+ else {
315
+ this.tokenGenTimestamps.set(ip, recentTimestamps);
316
+ }
317
+ }
318
+ }
319
+ /**
320
+ * Generate CSRF token with per-IP rate limiting
321
+ * Limits to 10 token generations per IP per 60 seconds
322
+ * Uses per-IP locking to serialize concurrent requests from the same IP,
323
+ * preventing race conditions in rate limit checks.
324
+ * @param req - Express request object
325
+ * @param res - Express response object
326
+ * @returns CSRF token
327
+ * @throws Error if CSRF_SECRET was not initialized in onModuleInit
328
+ * @throws {HttpException} 429 - When rate limit exceeded for this IP
329
+ * @throws {HttpException} 503 - When service is at capacity
330
+ */
331
+ async generateToken(req, res) {
332
+ if (!this.csrfProtection) {
333
+ throw new Error('CSRFService not initialized — call onModuleInit() first');
334
+ }
335
+ const ip = this.extractClientIp(req) ?? 'unknown';
336
+ // Serialize rate-limit check + token generation per IP to prevent race conditions
337
+ // Chain this request's work after any pending work for the same IP
338
+ const pendingWork = this.ipLocks.get(ip) ?? Promise.resolve();
339
+ // Wrap with a timeout to prevent indefinite waits in the queue
340
+ const currentWork = pendingWork.then(() => {
341
+ return new Promise((resolve, reject) => {
342
+ const timer = setTimeout(() => {
343
+ reject(new HttpException('CSRF token generation timed out waiting in queue', HttpStatus.SERVICE_UNAVAILABLE));
344
+ }, CSRFService_1.IP_LOCK_TIMEOUT_MS);
345
+ try {
346
+ const token = this.performRateLimitedTokenGeneration(req, res);
347
+ clearTimeout(timer);
348
+ resolve(token);
349
+ }
350
+ catch (err) {
351
+ clearTimeout(timer);
352
+ reject(err);
353
+ }
354
+ });
355
+ });
356
+ this.ipLocks.set(ip, currentWork);
357
+ // Clean up the lock after this work completes (only if no new work queued)
358
+ currentWork.finally(() => {
359
+ if (this.ipLocks.get(ip) === currentWork) {
360
+ this.ipLocks.delete(ip);
361
+ }
362
+ });
363
+ // eslint-disable-next-line @typescript-eslint/return-await
364
+ return await currentWork;
365
+ }
366
+ /**
367
+ * Perform rate-limited token generation for a single IP.
368
+ * This method is called within an IP-serialized lock to ensure atomicity.
369
+ * @param req - Express request object
370
+ * @param res - Express response object
371
+ * @returns CSRF token
372
+ * @throws HttpException with 429 status if rate limit exceeded
373
+ */
374
+ performRateLimitedTokenGeneration(req, res) {
375
+ // Check per-IP rate limit
376
+ const ip = this.extractClientIp(req) ?? 'unknown';
377
+ const now = Date.now();
378
+ // Check if map is approaching capacity (at 80% threshold with 20% safety margin)
379
+ // This safety margin prevents race conditions where concurrent requests might insert
380
+ // new IPs between the pruning step and the final capacity check.
381
+ const capacityThreshold = CSRFService_1.MAX_TRACKED_IPS * CSRFService_1.CAPACITY_THRESHOLD_PERCENT;
382
+ if (this.tokenGenTimestamps.size >= capacityThreshold) {
383
+ this.capacityThresholdCrossedCount++;
384
+ // eslint-disable-next-line no-magic-numbers
385
+ const capacityPercent = (this.tokenGenTimestamps.size / CSRFService_1.MAX_TRACKED_IPS * 100).toFixed(1);
386
+ this.logger.warn(`CSRF token generation approaching capacity (${capacityPercent}% of ${CSRFService_1.MAX_TRACKED_IPS} max IPs). ` +
387
+ `Threshold crossed ${this.capacityThresholdCrossedCount} times.`);
388
+ if (!this._isPruning) {
389
+ this._isPruning = true;
390
+ this.pruneTokenTimestamps();
391
+ this._isPruning = false;
392
+ }
393
+ // Check again after pruning
394
+ if (this.tokenGenTimestamps.size >= CSRFService_1.MAX_TRACKED_IPS) {
395
+ // eslint-disable-next-line no-magic-numbers
396
+ const finalCapacityPercent = (this.tokenGenTimestamps.size / CSRFService_1.MAX_TRACKED_IPS * 100).toFixed(1);
397
+ this.logger.error(`CSRF service at maximum capacity (${finalCapacityPercent}% of ${CSRFService_1.MAX_TRACKED_IPS} max IPs). ` +
398
+ 'Token generation rejected.');
399
+ throw new HttpException('CSRF token generation service is temporarily unavailable', HttpStatus.SERVICE_UNAVAILABLE);
400
+ }
401
+ }
402
+ let timestamps = this.tokenGenTimestamps.get(ip) ?? [];
403
+ // Filter to only timestamps within the last 60 seconds
404
+ timestamps = timestamps.filter(ts => now - ts < CSRFService_1.RATE_LIMIT_WINDOW_MS);
405
+ if (timestamps.length >= CSRFService_1.RATE_LIMIT_COUNT) {
406
+ this.logger.warn(`CSRF token rate limit exceeded for IP: ${escapeNewlines(ip)}`);
407
+ throw new HttpException('Rate limit exceeded for CSRF token generation', HttpStatus.TOO_MANY_REQUESTS);
408
+ }
409
+ // Record this generation
410
+ timestamps.push(now);
411
+ this.tokenGenTimestamps.set(ip, timestamps);
412
+ // Generate and return the token
413
+ // csrfProtection is guaranteed non-null: generateToken (our caller) checks it,
414
+ // and performRateLimitedTokenGeneration is only called from generateToken's chain.
415
+ const protection = this.csrfProtection;
416
+ if (!protection) {
417
+ throw new Error('CSRFService not initialized — call onModuleInit() first');
418
+ }
419
+ return protection.generateToken(req, res);
420
+ }
421
+ /**
422
+ * Validate CSRF token
423
+ * @param req - Express request object
424
+ * @returns true if token is valid, false otherwise
425
+ * @throws Error if CSRF_SECRET was not initialized in onModuleInit
426
+ */
427
+ validateToken(req) {
428
+ if (!this.csrfProtection) {
429
+ throw new Error('CSRFService not initialized — call onModuleInit() first');
430
+ }
431
+ try {
432
+ this.csrfProtection.validateRequest(req);
433
+ return true;
434
+ }
435
+ catch {
436
+ return false;
437
+ }
438
+ }
439
+ /**
440
+ * Refresh CSRF token by invalidating the current one and generating a new one
441
+ * Use this after sensitive operations like login, password change, or privilege escalation
442
+ * to ensure the user has a fresh token that cannot be replayed from before the operation
443
+ * @param req - Express request object
444
+ * @param res - Express response object
445
+ * @returns New CSRF token
446
+ * @throws Error if CSRF_SECRET was not initialized in onModuleInit
447
+ */
448
+ // eslint-disable-next-line @typescript-eslint/promise-function-async
449
+ refreshToken(req, res) {
450
+ if (!this.csrfProtection) {
451
+ throw new Error('CSRFService not initialized — call onModuleInit() first');
452
+ }
453
+ // Route through rate limiting to enforce per-IP token generation limits
454
+ // Clear any session-bound CSRF state by generating a fresh token
455
+ // The doubleCsrf library handles invalidation through cookie/session updates
456
+ return this.generateToken(req, res);
457
+ }
458
+ /**
459
+ * Get CSRF middleware
460
+ * @returns CSRF protection middleware
461
+ * @throws Error if CSRF_SECRET was not initialized in onModuleInit
462
+ */
463
+ getMiddleware() {
464
+ if (!this.csrfProtection) {
465
+ throw new Error('CSRFService not initialized — call onModuleInit() first');
466
+ }
467
+ return this.csrfProtection.doubleCsrfProtection;
468
+ }
469
+ };
470
+ CSRFService = CSRFService_1 = __decorate([
471
+ Injectable(),
472
+ __param(0, Inject(ConfigService)),
473
+ __param(0, Optional()),
474
+ __param(1, Optional()),
475
+ __metadata("design:paramtypes", [ConfigService, Object])
476
+ ], CSRFService);
477
+ export { CSRFService };
478
+ //# sourceMappingURL=csrf.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf.service.js","sourceRoot":"","sources":["../../../src/common/services/csrf.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import { LazyModuleRefService } from '../utils/lazy-getter.types.js';
3
+ import { AppLogger } from './logger.service.js';
4
+ /**
5
+ * Error category classification for recovery strategy determination.
6
+ */
7
+ export interface ErrorCategory {
8
+ type: 'transient' | 'permanent';
9
+ retryable: boolean;
10
+ strategy: 'retry' | 'fail' | 'backoff';
11
+ backoffMs?: number;
12
+ }
13
+ /**
14
+ * Error Categorizer Service.
15
+ * Classifies errors as transient or permanent and recommends recovery strategies.
16
+ *
17
+ * Categories:
18
+ * - **Transient** (retryable): Network errors, timeouts, database connection errors, rate limits, server errors (5xx)
19
+ * - **Permanent** (not retryable): Validation errors, bad requests (4xx), authentication/authorization, not found
20
+ *
21
+ * Recovery strategies:
22
+ * - **retry**: Immediate retry (for network errors)
23
+ * - **backoff**: Exponential backoff retry (for timeouts, database, rate limits)
24
+ * - **fail**: Fast failure without retry (for validation, authentication, not found)
25
+ *
26
+ * @remarks
27
+ * - Node.js error codes (ECONNRESET, ECONNREFUSED, ETIMEDOUT, ENOTFOUND, EAI_AGAIN) are always transient
28
+ * - Database connection errors are always transient with long backoff (5s)
29
+ * - Timeout errors get medium backoff (2s)
30
+ * - Rate limit errors get maximum backoff (10s)
31
+ * - Unknown errors default to permanent/fail strategy
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const category = errorCategorizer.categorizeError(error);
36
+ * if (category.retryable) {
37
+ * // Retry with backoff: category.backoffMs
38
+ * } else {
39
+ * // Fail fast
40
+ * }
41
+ * ```
42
+ */
43
+ export declare class ErrorCategorizerService implements LazyModuleRefService {
44
+ private _contextualLogger;
45
+ readonly Module: ModuleRef;
46
+ constructor(module: ModuleRef);
47
+ /**
48
+ * Get contextual logger for error categorizer
49
+ */
50
+ get Logger(): AppLogger;
51
+ /**
52
+ * Check if an error is retryable
53
+ */
54
+ isRetryable(error: unknown): boolean;
55
+ /**
56
+ * Categorize an error and determine recovery strategy
57
+ */
58
+ categorizeError(error: unknown): ErrorCategory;
59
+ /**
60
+ * Log error recovery attempt
61
+ */
62
+ logRecoveryAttempt(error: unknown, attempt: number, maxAttempts: number): void;
63
+ /**
64
+ * Log successful recovery
65
+ */
66
+ logRecoverySuccess(error: unknown, attempts: number): void;
67
+ /**
68
+ * Log failed recovery
69
+ */
70
+ logRecoveryFailed(error: unknown, attempts: number): void;
71
+ private isNetworkError;
72
+ private isTimeoutError;
73
+ private isDatabaseError;
74
+ private isBadRequestError;
75
+ private isValidationError;
76
+ private isAuthError;
77
+ private isAuthzError;
78
+ private isNotFoundError;
79
+ private isServerError;
80
+ private isRateLimitError;
81
+ }
82
+ //# sourceMappingURL=error-categorizer.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-categorizer.service.d.ts","sourceRoot":"","sources":["../../../src/common/services/error-categorizer.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAYzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAQhD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBACa,uBAAwB,YAAW,oBAAoB;IACnE,OAAO,CAAC,iBAAiB,CAAwB;IAEjD,SAAgB,MAAM,EAAE,SAAS,CAAC;gBAEtB,MAAM,EAAE,SAAS;IAI7B;;OAEG;IACH,IAAW,MAAM,IAAI,SAAS,CAM7B;IAED;;OAEG;IACI,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAK3C;;OAEG;IACI,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;IAuLrD;;OAEG;IACI,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAYrF;;OAEG;IACI,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAOjE;;OAEG;IACI,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWhE,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,gBAAgB;CAKxB"}