nestjs-ddd-cli 2.2.1 → 3.2.1

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 (254) hide show
  1. package/README.md +247 -408
  2. package/ddd.schema.json +111 -0
  3. package/dist/commands/aggregate-validator.d.ts +9 -0
  4. package/dist/commands/aggregate-validator.js +953 -0
  5. package/dist/commands/aggregate-validator.js.map +1 -0
  6. package/dist/commands/ai-assist.d.ts +8 -0
  7. package/dist/commands/ai-assist.js +337 -0
  8. package/dist/commands/ai-assist.js.map +1 -0
  9. package/dist/commands/api-contracts.d.ts +9 -0
  10. package/dist/commands/api-contracts.js +1368 -0
  11. package/dist/commands/api-contracts.js.map +1 -0
  12. package/dist/commands/api-docs.d.ts +8 -0
  13. package/dist/commands/api-docs.js +408 -0
  14. package/dist/commands/api-docs.js.map +1 -0
  15. package/dist/commands/api-versioning.d.ts +11 -0
  16. package/dist/commands/api-versioning.js +643 -0
  17. package/dist/commands/api-versioning.js.map +1 -0
  18. package/dist/commands/audit-logging.d.ts +9 -0
  19. package/dist/commands/audit-logging.js +1129 -0
  20. package/dist/commands/audit-logging.js.map +1 -0
  21. package/dist/commands/batch-generate.d.ts +10 -0
  22. package/dist/commands/batch-generate.js +405 -0
  23. package/dist/commands/batch-generate.js.map +1 -0
  24. package/dist/commands/caching-strategies.d.ts +9 -0
  25. package/dist/commands/caching-strategies.js +874 -0
  26. package/dist/commands/caching-strategies.js.map +1 -0
  27. package/dist/commands/code-analyzer.d.ts +42 -0
  28. package/dist/commands/code-analyzer.js +474 -0
  29. package/dist/commands/code-analyzer.js.map +1 -0
  30. package/dist/commands/database-seeding.d.ts +6 -0
  31. package/dist/commands/database-seeding.js +621 -0
  32. package/dist/commands/database-seeding.js.map +1 -0
  33. package/dist/commands/db-optimization.d.ts +7 -0
  34. package/dist/commands/db-optimization.js +687 -0
  35. package/dist/commands/db-optimization.js.map +1 -0
  36. package/dist/commands/dependency-graph.d.ts +6 -0
  37. package/dist/commands/dependency-graph.js +329 -0
  38. package/dist/commands/dependency-graph.js.map +1 -0
  39. package/dist/commands/doctor-enhanced.d.ts +22 -0
  40. package/dist/commands/doctor-enhanced.js +543 -0
  41. package/dist/commands/doctor-enhanced.js.map +1 -0
  42. package/dist/commands/doctor.d.ts +4 -0
  43. package/dist/commands/doctor.js +151 -0
  44. package/dist/commands/doctor.js.map +1 -0
  45. package/dist/commands/env-manager.d.ts +6 -0
  46. package/dist/commands/env-manager.js +419 -0
  47. package/dist/commands/env-manager.js.map +1 -0
  48. package/dist/commands/event-sourcing-full.d.ts +10 -0
  49. package/dist/commands/event-sourcing-full.js +1107 -0
  50. package/dist/commands/event-sourcing-full.js.map +1 -0
  51. package/dist/commands/feature-flags.d.ts +9 -0
  52. package/dist/commands/feature-flags.js +824 -0
  53. package/dist/commands/feature-flags.js.map +1 -0
  54. package/dist/commands/filter-dsl.d.ts +10 -0
  55. package/dist/commands/filter-dsl.js +1407 -0
  56. package/dist/commands/filter-dsl.js.map +1 -0
  57. package/dist/commands/generate-all.js +485 -32
  58. package/dist/commands/generate-all.js.map +1 -1
  59. package/dist/commands/generate-deployment.d.ts +8 -0
  60. package/dist/commands/generate-deployment.js +746 -0
  61. package/dist/commands/generate-deployment.js.map +1 -0
  62. package/dist/commands/generate-domain-service.d.ts +14 -0
  63. package/dist/commands/generate-domain-service.js +796 -0
  64. package/dist/commands/generate-domain-service.js.map +1 -0
  65. package/dist/commands/generate-entity.js +82 -24
  66. package/dist/commands/generate-entity.js.map +1 -1
  67. package/dist/commands/generate-from-schema.d.ts +56 -0
  68. package/dist/commands/generate-from-schema.js +222 -0
  69. package/dist/commands/generate-from-schema.js.map +1 -0
  70. package/dist/commands/generate-orchestrator.d.ts +14 -0
  71. package/dist/commands/generate-orchestrator.js +887 -0
  72. package/dist/commands/generate-orchestrator.js.map +1 -0
  73. package/dist/commands/generate-repository.d.ts +14 -0
  74. package/dist/commands/generate-repository.js +1019 -0
  75. package/dist/commands/generate-repository.js.map +1 -0
  76. package/dist/commands/generate-shared.d.ts +4 -0
  77. package/dist/commands/generate-shared.js +388 -0
  78. package/dist/commands/generate-shared.js.map +1 -0
  79. package/dist/commands/generate-value-object.d.ts +32 -0
  80. package/dist/commands/generate-value-object.js +700 -0
  81. package/dist/commands/generate-value-object.js.map +1 -0
  82. package/dist/commands/graphql-subscriptions.d.ts +6 -0
  83. package/dist/commands/graphql-subscriptions.js +607 -0
  84. package/dist/commands/graphql-subscriptions.js.map +1 -0
  85. package/dist/commands/graphql-types.d.ts +5 -0
  86. package/dist/commands/graphql-types.js +423 -0
  87. package/dist/commands/graphql-types.js.map +1 -0
  88. package/dist/commands/health-probes-advanced.d.ts +6 -0
  89. package/dist/commands/health-probes-advanced.js +655 -0
  90. package/dist/commands/health-probes-advanced.js.map +1 -0
  91. package/dist/commands/i18n-setup.d.ts +10 -0
  92. package/dist/commands/i18n-setup.js +677 -0
  93. package/dist/commands/i18n-setup.js.map +1 -0
  94. package/dist/commands/init-config.d.ts +6 -0
  95. package/dist/commands/init-config.js +370 -0
  96. package/dist/commands/init-config.js.map +1 -0
  97. package/dist/commands/init-project.js +56 -6
  98. package/dist/commands/init-project.js.map +1 -1
  99. package/dist/commands/interactive-scaffold.d.ts +5 -0
  100. package/dist/commands/interactive-scaffold.js +271 -0
  101. package/dist/commands/interactive-scaffold.js.map +1 -0
  102. package/dist/commands/metrics-prometheus.d.ts +6 -0
  103. package/dist/commands/metrics-prometheus.js +681 -0
  104. package/dist/commands/metrics-prometheus.js.map +1 -0
  105. package/dist/commands/migration-engine.d.ts +6 -0
  106. package/dist/commands/migration-engine.js +446 -0
  107. package/dist/commands/migration-engine.js.map +1 -0
  108. package/dist/commands/migration.d.ts +12 -0
  109. package/dist/commands/migration.js +484 -0
  110. package/dist/commands/migration.js.map +1 -0
  111. package/dist/commands/monorepo.d.ts +8 -0
  112. package/dist/commands/monorepo.js +483 -0
  113. package/dist/commands/monorepo.js.map +1 -0
  114. package/dist/commands/multi-database.d.ts +5 -0
  115. package/dist/commands/multi-database.js +439 -0
  116. package/dist/commands/multi-database.js.map +1 -0
  117. package/dist/commands/observability-tracing.d.ts +10 -0
  118. package/dist/commands/observability-tracing.js +740 -0
  119. package/dist/commands/observability-tracing.js.map +1 -0
  120. package/dist/commands/openapi-export.d.ts +8 -0
  121. package/dist/commands/openapi-export.js +359 -0
  122. package/dist/commands/openapi-export.js.map +1 -0
  123. package/dist/commands/perf-analyzer.d.ts +8 -0
  124. package/dist/commands/perf-analyzer.js +423 -0
  125. package/dist/commands/perf-analyzer.js.map +1 -0
  126. package/dist/commands/rate-limiting.d.ts +10 -0
  127. package/dist/commands/rate-limiting.js +953 -0
  128. package/dist/commands/rate-limiting.js.map +1 -0
  129. package/dist/commands/recipe-plugin.d.ts +56 -0
  130. package/dist/commands/recipe-plugin.js +315 -0
  131. package/dist/commands/recipe-plugin.js.map +1 -0
  132. package/dist/commands/recipe.d.ts +6 -0
  133. package/dist/commands/recipe.js +3941 -0
  134. package/dist/commands/recipe.js.map +1 -0
  135. package/dist/commands/recipes/elasticsearch.recipe.d.ts +1 -0
  136. package/dist/commands/recipes/elasticsearch.recipe.js +761 -0
  137. package/dist/commands/recipes/elasticsearch.recipe.js.map +1 -0
  138. package/dist/commands/recipes/event-sourcing.recipe.d.ts +1 -0
  139. package/dist/commands/recipes/event-sourcing.recipe.js +889 -0
  140. package/dist/commands/recipes/event-sourcing.recipe.js.map +1 -0
  141. package/dist/commands/recipes/index.d.ts +7 -0
  142. package/dist/commands/recipes/index.js +24 -0
  143. package/dist/commands/recipes/index.js.map +1 -0
  144. package/dist/commands/recipes/message-queue.recipe.d.ts +1 -0
  145. package/dist/commands/recipes/message-queue.recipe.js +706 -0
  146. package/dist/commands/recipes/message-queue.recipe.js.map +1 -0
  147. package/dist/commands/recipes/middleware.recipe.d.ts +1 -0
  148. package/dist/commands/recipes/middleware.recipe.js +383 -0
  149. package/dist/commands/recipes/middleware.recipe.js.map +1 -0
  150. package/dist/commands/recipes/multi-tenancy.recipe.d.ts +1 -0
  151. package/dist/commands/recipes/multi-tenancy.recipe.js +520 -0
  152. package/dist/commands/recipes/multi-tenancy.recipe.js.map +1 -0
  153. package/dist/commands/recipes/oauth2.recipe.d.ts +1 -0
  154. package/dist/commands/recipes/oauth2.recipe.js +472 -0
  155. package/dist/commands/recipes/oauth2.recipe.js.map +1 -0
  156. package/dist/commands/recipes/websocket.recipe.d.ts +1 -0
  157. package/dist/commands/recipes/websocket.recipe.js +453 -0
  158. package/dist/commands/recipes/websocket.recipe.js.map +1 -0
  159. package/dist/commands/resilience-patterns.d.ts +13 -0
  160. package/dist/commands/resilience-patterns.js +1029 -0
  161. package/dist/commands/resilience-patterns.js.map +1 -0
  162. package/dist/commands/security-patterns.d.ts +11 -0
  163. package/dist/commands/security-patterns.js +2233 -0
  164. package/dist/commands/security-patterns.js.map +1 -0
  165. package/dist/commands/template-debug.d.ts +27 -0
  166. package/dist/commands/template-debug.js +388 -0
  167. package/dist/commands/template-debug.js.map +1 -0
  168. package/dist/commands/test-factory-full.d.ts +9 -0
  169. package/dist/commands/test-factory-full.js +1570 -0
  170. package/dist/commands/test-factory-full.js.map +1 -0
  171. package/dist/commands/test-scaffold.d.ts +7 -0
  172. package/dist/commands/test-scaffold.js +621 -0
  173. package/dist/commands/test-scaffold.js.map +1 -0
  174. package/dist/index.js +1088 -0
  175. package/dist/index.js.map +1 -1
  176. package/dist/templates/ai-context/CLAUDE.md.hbs +158 -0
  177. package/dist/templates/ai-context/conventions.md.hbs +154 -0
  178. package/dist/templates/command/create-command.hbs +6 -14
  179. package/dist/templates/command/delete-command.hbs +19 -0
  180. package/dist/templates/command/update-command.hbs +24 -0
  181. package/dist/templates/controller/controller.hbs +64 -17
  182. package/dist/templates/dto/create-dto.hbs +29 -5
  183. package/dist/templates/dto/filter-dto.hbs +52 -0
  184. package/dist/templates/dto/filter-query.dto.hbs +148 -0
  185. package/dist/templates/dto/paginated-response.dto.hbs +29 -0
  186. package/dist/templates/dto/pagination-query.dto.hbs +30 -0
  187. package/dist/templates/dto/response-dto.hbs +38 -0
  188. package/dist/templates/dto/update-dto.hbs +11 -0
  189. package/dist/templates/entity/entity.hbs +32 -1
  190. package/dist/templates/event/domain-event.hbs +33 -7
  191. package/dist/templates/event/event-handler.hbs +40 -0
  192. package/dist/templates/exception/base-exceptions.hbs +69 -0
  193. package/dist/templates/exception/entity-not-found.exception.hbs +7 -0
  194. package/dist/templates/mapper/mapper.hbs +49 -24
  195. package/dist/templates/module/module.hbs +34 -10
  196. package/dist/templates/orm-entity/orm-entity.hbs +63 -12
  197. package/dist/templates/prisma/prisma-mapper.hbs +71 -0
  198. package/dist/templates/prisma/prisma-repository.hbs +114 -0
  199. package/dist/templates/prisma/prisma-schema.hbs +20 -0
  200. package/dist/templates/prisma/prisma-service.hbs +51 -0
  201. package/dist/templates/query/get-all.query.hbs +50 -0
  202. package/dist/templates/query/get-by-id.query.hbs +31 -0
  203. package/dist/templates/repository/repository.hbs +55 -13
  204. package/dist/templates/resolver/graphql-input.hbs +54 -0
  205. package/dist/templates/resolver/graphql-type.hbs +58 -0
  206. package/dist/templates/resolver/pagination-args.hbs +33 -0
  207. package/dist/templates/resolver/resolver.hbs +62 -0
  208. package/dist/templates/shared/prisma-query-builder.util.hbs +189 -0
  209. package/dist/templates/shared/query-builder.util.hbs +218 -0
  210. package/dist/templates/test/controller.spec.hbs +124 -0
  211. package/dist/templates/test/repository.spec.hbs +158 -0
  212. package/dist/templates/test/usecase.spec.hbs +116 -0
  213. package/dist/templates/usecase/create-usecase.hbs +19 -7
  214. package/dist/templates/usecase/delete-usecase.hbs +17 -0
  215. package/dist/templates/usecase/update-usecase.hbs +31 -0
  216. package/dist/utils/config.utils.d.ts +45 -0
  217. package/dist/utils/config.utils.js +211 -0
  218. package/dist/utils/config.utils.js.map +1 -0
  219. package/dist/utils/error.utils.d.ts +145 -0
  220. package/dist/utils/error.utils.js +422 -0
  221. package/dist/utils/error.utils.js.map +1 -0
  222. package/dist/utils/field.utils.d.ts +54 -0
  223. package/dist/utils/field.utils.js +389 -0
  224. package/dist/utils/field.utils.js.map +1 -0
  225. package/dist/utils/file.utils.d.ts +19 -8
  226. package/dist/utils/file.utils.js +135 -4
  227. package/dist/utils/file.utils.js.map +1 -1
  228. package/dist/utils/idempotency.utils.d.ts +123 -0
  229. package/dist/utils/idempotency.utils.js +444 -0
  230. package/dist/utils/idempotency.utils.js.map +1 -0
  231. package/dist/utils/naming.utils.js +24 -5
  232. package/dist/utils/naming.utils.js.map +1 -1
  233. package/dist/utils/performance.utils.d.ts +37 -0
  234. package/dist/utils/performance.utils.js +158 -0
  235. package/dist/utils/performance.utils.js.map +1 -0
  236. package/dist/utils/relation.utils.d.ts +92 -0
  237. package/dist/utils/relation.utils.js +388 -0
  238. package/dist/utils/relation.utils.js.map +1 -0
  239. package/dist/utils/rollback.utils.d.ts +49 -0
  240. package/dist/utils/rollback.utils.js +306 -0
  241. package/dist/utils/rollback.utils.js.map +1 -0
  242. package/dist/utils/schema.utils.d.ts +123 -0
  243. package/dist/utils/schema.utils.js +419 -0
  244. package/dist/utils/schema.utils.js.map +1 -0
  245. package/dist/utils/security.utils.d.ts +57 -0
  246. package/dist/utils/security.utils.js +315 -0
  247. package/dist/utils/security.utils.js.map +1 -0
  248. package/dist/utils/template-engine.utils.d.ts +80 -0
  249. package/dist/utils/template-engine.utils.js +463 -0
  250. package/dist/utils/template-engine.utils.js.map +1 -0
  251. package/dist/utils/validation-registry.utils.d.ts +160 -0
  252. package/dist/utils/validation-registry.utils.js +526 -0
  253. package/dist/utils/validation-registry.utils.js.map +1 -0
  254. package/package.json +3 -1
@@ -0,0 +1,2233 @@
1
+ "use strict";
2
+ /**
3
+ * Comprehensive Security Patterns Generator
4
+ * RBAC, encryption, OWASP protections, secret management
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.setupSecurityPatterns = setupSecurityPatterns;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const chalk_1 = __importDefault(require("chalk"));
47
+ async function setupSecurityPatterns(basePath, options = {}) {
48
+ console.log(chalk_1.default.bold.blue('\n🔐 Setting up Security Patterns\n'));
49
+ const sharedPath = path.join(basePath, 'src/shared/security');
50
+ if (!fs.existsSync(sharedPath)) {
51
+ fs.mkdirSync(sharedPath, { recursive: true });
52
+ }
53
+ fs.writeFileSync(path.join(sharedPath, 'encryption.service.ts'), generateEncryptionService());
54
+ console.log(chalk_1.default.green(` ✓ Created encryption service`));
55
+ fs.writeFileSync(path.join(sharedPath, 'rbac.service.ts'), generateRbacService());
56
+ console.log(chalk_1.default.green(` ✓ Created RBAC service`));
57
+ fs.writeFileSync(path.join(sharedPath, 'rbac.decorator.ts'), generateRbacDecorator());
58
+ console.log(chalk_1.default.green(` ✓ Created RBAC decorator`));
59
+ fs.writeFileSync(path.join(sharedPath, 'owasp.middleware.ts'), generateOwaspMiddleware());
60
+ console.log(chalk_1.default.green(` ✓ Created OWASP middleware`));
61
+ fs.writeFileSync(path.join(sharedPath, 'secret-vault.service.ts'), generateSecretVault());
62
+ console.log(chalk_1.default.green(` ✓ Created secret vault`));
63
+ fs.writeFileSync(path.join(sharedPath, 'input-sanitizer.ts'), generateInputSanitizer());
64
+ console.log(chalk_1.default.green(` ✓ Created input sanitizer`));
65
+ fs.writeFileSync(path.join(sharedPath, 'security.module.ts'), generateSecurityModule());
66
+ console.log(chalk_1.default.green(` ✓ Created security module`));
67
+ fs.writeFileSync(path.join(sharedPath, 'cors.config.ts'), generateCorsConfig());
68
+ console.log(chalk_1.default.green(` ✓ Created CORS configuration`));
69
+ fs.writeFileSync(path.join(sharedPath, 'cookie.config.ts'), generateCookieConfig());
70
+ console.log(chalk_1.default.green(` ✓ Created cookie security configuration`));
71
+ fs.writeFileSync(path.join(sharedPath, 'jwt.security.ts'), generateJwtSecurity());
72
+ console.log(chalk_1.default.green(` ✓ Created JWT security service`));
73
+ fs.writeFileSync(path.join(sharedPath, 'security-headers.config.ts'), generateSecurityHeadersConfig());
74
+ console.log(chalk_1.default.green(` ✓ Created security headers configuration`));
75
+ console.log(chalk_1.default.bold.green('\n✅ Security patterns ready!\n'));
76
+ }
77
+ function generateEncryptionService() {
78
+ return `/**
79
+ * Encryption Service
80
+ * AES-256-GCM authenticated encryption for data at rest
81
+ * OWASP A02:2021 compliant
82
+ */
83
+
84
+ import { Injectable, Logger } from '@nestjs/common';
85
+ import * as crypto from 'crypto';
86
+
87
+ export interface EncryptionOptions {
88
+ algorithm?: string;
89
+ keyLength?: number;
90
+ ivLength?: number;
91
+ }
92
+
93
+ // Minimum password/key requirements
94
+ const MIN_KEY_LENGTH = 16;
95
+ const MIN_PASSWORD_LENGTH = 8;
96
+ const MAX_PLAINTEXT_LENGTH = 10 * 1024 * 1024; // 10MB max
97
+
98
+ @Injectable()
99
+ export class EncryptionService {
100
+ private readonly logger = new Logger(EncryptionService.name);
101
+ private readonly algorithm = 'aes-256-gcm';
102
+ private readonly keyLength = 32;
103
+ private readonly ivLength = 16;
104
+ private readonly tagLength = 16;
105
+ private readonly saltLength = 32;
106
+
107
+ // PBKDF2 iterations - OWASP recommends at least 600,000 for SHA-256
108
+ private readonly pbkdf2Iterations = 600000;
109
+ private readonly passwordHashIterations = 310000;
110
+
111
+ /**
112
+ * Encrypt data with AES-256-GCM
113
+ * @throws Error if key is too short or plaintext too long
114
+ */
115
+ encrypt(plaintext: string, key: string): string {
116
+ // Input validation
117
+ if (!plaintext || typeof plaintext !== 'string') {
118
+ throw new Error('Plaintext must be a non-empty string');
119
+ }
120
+ if (!key || typeof key !== 'string' || key.length < MIN_KEY_LENGTH) {
121
+ throw new Error(\`Encryption key must be at least \${MIN_KEY_LENGTH} characters\`);
122
+ }
123
+ if (plaintext.length > MAX_PLAINTEXT_LENGTH) {
124
+ throw new Error(\`Plaintext exceeds maximum length of \${MAX_PLAINTEXT_LENGTH} bytes\`);
125
+ }
126
+
127
+ const iv = crypto.randomBytes(this.ivLength);
128
+ const salt = crypto.randomBytes(this.saltLength);
129
+ const derivedKey = this.deriveKey(key, salt);
130
+
131
+ const cipher = crypto.createCipheriv(this.algorithm, derivedKey, iv);
132
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
133
+ encrypted += cipher.final('hex');
134
+
135
+ const tag = cipher.getAuthTag();
136
+
137
+ // Format: version:salt:iv:tag:encrypted (version for future algorithm changes)
138
+ return [
139
+ 'v1',
140
+ salt.toString('hex'),
141
+ iv.toString('hex'),
142
+ tag.toString('hex'),
143
+ encrypted,
144
+ ].join(':');
145
+ }
146
+
147
+ /**
148
+ * Decrypt data
149
+ * @throws Error if decryption fails (tampered or wrong key)
150
+ */
151
+ decrypt(ciphertext: string, key: string): string {
152
+ if (!ciphertext || typeof ciphertext !== 'string') {
153
+ throw new Error('Ciphertext must be a non-empty string');
154
+ }
155
+ if (!key || typeof key !== 'string' || key.length < MIN_KEY_LENGTH) {
156
+ throw new Error(\`Decryption key must be at least \${MIN_KEY_LENGTH} characters\`);
157
+ }
158
+
159
+ const parts = ciphertext.split(':');
160
+
161
+ // Support versioned format
162
+ let saltHex: string, ivHex: string, tagHex: string, encrypted: string;
163
+
164
+ if (parts[0] === 'v1' && parts.length === 5) {
165
+ [, saltHex, ivHex, tagHex, encrypted] = parts;
166
+ } else if (parts.length === 4) {
167
+ // Legacy format without version
168
+ [saltHex, ivHex, tagHex, encrypted] = parts;
169
+ } else {
170
+ throw new Error('Invalid ciphertext format');
171
+ }
172
+
173
+ try {
174
+ const salt = Buffer.from(saltHex, 'hex');
175
+ const iv = Buffer.from(ivHex, 'hex');
176
+ const tag = Buffer.from(tagHex, 'hex');
177
+ const derivedKey = this.deriveKey(key, salt);
178
+
179
+ const decipher = crypto.createDecipheriv(this.algorithm, derivedKey, iv);
180
+ decipher.setAuthTag(tag);
181
+
182
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
183
+ decrypted += decipher.final('utf8');
184
+
185
+ return decrypted;
186
+ } catch (error) {
187
+ // Don't reveal specific error details
188
+ this.logger.warn('Decryption failed - possibly tampered data or wrong key');
189
+ throw new Error('Decryption failed');
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Hash password using PBKDF2 with high iteration count
195
+ * OWASP recommendation: 310,000 iterations for PBKDF2-HMAC-SHA256
196
+ */
197
+ async hashPassword(password: string): Promise<string> {
198
+ if (!password || typeof password !== 'string') {
199
+ throw new Error('Password must be a non-empty string');
200
+ }
201
+ if (password.length < MIN_PASSWORD_LENGTH) {
202
+ throw new Error(\`Password must be at least \${MIN_PASSWORD_LENGTH} characters\`);
203
+ }
204
+
205
+ const salt = crypto.randomBytes(this.saltLength);
206
+ const iterations = this.passwordHashIterations;
207
+
208
+ return new Promise((resolve, reject) => {
209
+ crypto.pbkdf2(password, salt, iterations, 64, 'sha512', (err, key) => {
210
+ if (err) reject(err);
211
+ // Include algorithm info for future migration
212
+ resolve(\`pbkdf2:sha512:\${iterations}:\${salt.toString('hex')}:\${key.toString('hex')}\`);
213
+ });
214
+ });
215
+ }
216
+
217
+ /**
218
+ * Verify password with timing-safe comparison
219
+ */
220
+ async verifyPassword(password: string, hash: string): Promise<boolean> {
221
+ if (!password || !hash) {
222
+ return false;
223
+ }
224
+
225
+ try {
226
+ const parts = hash.split(':');
227
+ let saltHex: string, keyHex: string, iterations: number;
228
+
229
+ // Support new format with algorithm prefix
230
+ if (parts[0] === 'pbkdf2' && parts.length === 5) {
231
+ [, , iterations, saltHex, keyHex] = [parts[0], parts[1], parseInt(parts[2], 10), parts[3], parts[4]] as [string, string, number, string, string];
232
+ } else if (parts.length === 3) {
233
+ // Legacy format
234
+ [saltHex, iterations, keyHex] = [parts[0], parseInt(parts[1], 10), parts[2]] as [string, number, string];
235
+ } else {
236
+ return false;
237
+ }
238
+
239
+ const salt = Buffer.from(saltHex, 'hex');
240
+ const storedKey = Buffer.from(keyHex, 'hex');
241
+
242
+ return new Promise((resolve, reject) => {
243
+ crypto.pbkdf2(password, salt, iterations, 64, 'sha512', (err, derivedKey) => {
244
+ if (err) {
245
+ reject(err);
246
+ return;
247
+ }
248
+ // Timing-safe comparison prevents timing attacks
249
+ try {
250
+ resolve(crypto.timingSafeEqual(storedKey, derivedKey));
251
+ } catch {
252
+ resolve(false);
253
+ }
254
+ });
255
+ });
256
+ } catch {
257
+ return false;
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Generate cryptographically secure random token
263
+ */
264
+ generateToken(length: number = 32): string {
265
+ if (length < 16) {
266
+ throw new Error('Token length must be at least 16 bytes');
267
+ }
268
+ return crypto.randomBytes(length).toString('hex');
269
+ }
270
+
271
+ /**
272
+ * Generate API key with checksum
273
+ */
274
+ generateApiKey(prefix: string = 'sk'): string {
275
+ const sanitizedPrefix = prefix.replace(/[^a-zA-Z0-9]/g, '').substring(0, 10);
276
+ const key = crypto.randomBytes(24).toString('base64url');
277
+ const checksum = this.hash(key).substring(0, 4);
278
+ return \`\${sanitizedPrefix}_\${key}_\${checksum}\`;
279
+ }
280
+
281
+ /**
282
+ * Verify API key checksum
283
+ */
284
+ verifyApiKeyChecksum(apiKey: string): boolean {
285
+ const parts = apiKey.split('_');
286
+ if (parts.length !== 3) return false;
287
+
288
+ const [, key, checksum] = parts;
289
+ const expectedChecksum = this.hash(key).substring(0, 4);
290
+
291
+ return crypto.timingSafeEqual(
292
+ Buffer.from(checksum),
293
+ Buffer.from(expectedChecksum)
294
+ );
295
+ }
296
+
297
+ /**
298
+ * Hash data (one-way) - defaults to SHA-256
299
+ */
300
+ hash(data: string, algorithm: string = 'sha256'): string {
301
+ const allowedAlgorithms = ['sha256', 'sha384', 'sha512', 'sha3-256', 'sha3-512'];
302
+ if (!allowedAlgorithms.includes(algorithm)) {
303
+ throw new Error(\`Algorithm must be one of: \${allowedAlgorithms.join(', ')}\`);
304
+ }
305
+ return crypto.createHash(algorithm).update(data).digest('hex');
306
+ }
307
+
308
+ /**
309
+ * HMAC signature using SHA-256
310
+ */
311
+ hmac(data: string, secret: string): string {
312
+ if (!secret || secret.length < MIN_KEY_LENGTH) {
313
+ throw new Error(\`HMAC secret must be at least \${MIN_KEY_LENGTH} characters\`);
314
+ }
315
+ return crypto.createHmac('sha256', secret).update(data).digest('hex');
316
+ }
317
+
318
+ /**
319
+ * Verify HMAC with timing-safe comparison
320
+ */
321
+ verifyHmac(data: string, signature: string, secret: string): boolean {
322
+ try {
323
+ const expected = this.hmac(data, secret);
324
+ // Ensure both are same length for timing-safe comparison
325
+ if (signature.length !== expected.length) {
326
+ return false;
327
+ }
328
+ return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
329
+ } catch {
330
+ return false;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Securely compare two strings (timing-safe)
336
+ */
337
+ secureCompare(a: string, b: string): boolean {
338
+ if (typeof a !== 'string' || typeof b !== 'string') {
339
+ return false;
340
+ }
341
+ if (a.length !== b.length) {
342
+ return false;
343
+ }
344
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
345
+ }
346
+
347
+ private deriveKey(password: string, salt: Buffer): Buffer {
348
+ return crypto.pbkdf2Sync(password, salt, this.pbkdf2Iterations, this.keyLength, 'sha256');
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Field-level encryption decorator
354
+ */
355
+ export function Encrypted(): PropertyDecorator {
356
+ return function (target: Object, propertyKey: string | symbol) {
357
+ Reflect.defineMetadata('encrypted', true, target, propertyKey);
358
+ };
359
+ }
360
+ `;
361
+ }
362
+ function generateRbacService() {
363
+ return `/**
364
+ * RBAC (Role-Based Access Control) Service
365
+ * Manages roles, permissions, and access policies
366
+ */
367
+
368
+ import { Injectable, Logger } from '@nestjs/common';
369
+
370
+ export interface Role {
371
+ name: string;
372
+ permissions: string[];
373
+ inherits?: string[];
374
+ }
375
+
376
+ export interface Permission {
377
+ resource: string;
378
+ action: string;
379
+ conditions?: PermissionCondition[];
380
+ }
381
+
382
+ export interface PermissionCondition {
383
+ field: string;
384
+ operator: 'equals' | 'in' | 'owns' | 'custom';
385
+ value?: any;
386
+ handler?: (user: any, resource: any) => boolean;
387
+ }
388
+
389
+ export interface AccessContext {
390
+ user: {
391
+ id: string;
392
+ roles: string[];
393
+ attributes?: Record<string, any>;
394
+ };
395
+ resource?: any;
396
+ action: string;
397
+ }
398
+
399
+ @Injectable()
400
+ export class RbacService {
401
+ private readonly logger = new Logger(RbacService.name);
402
+ private readonly roles = new Map<string, Role>();
403
+ private readonly permissions = new Map<string, Permission>();
404
+
405
+ /**
406
+ * Define a role
407
+ */
408
+ defineRole(role: Role): void {
409
+ this.roles.set(role.name, role);
410
+ }
411
+
412
+ /**
413
+ * Define a permission
414
+ */
415
+ definePermission(name: string, permission: Permission): void {
416
+ this.permissions.set(name, permission);
417
+ }
418
+
419
+ /**
420
+ * Check if user has permission
421
+ */
422
+ hasPermission(context: AccessContext): boolean {
423
+ const { user, action, resource } = context;
424
+
425
+ // Get all permissions for user's roles
426
+ const userPermissions = this.getUserPermissions(user.roles);
427
+
428
+ // Check each permission
429
+ for (const permName of userPermissions) {
430
+ const permission = this.permissions.get(permName);
431
+ if (!permission) continue;
432
+
433
+ if (permission.action !== action && permission.action !== '*') continue;
434
+
435
+ // Check conditions
436
+ if (permission.conditions && permission.conditions.length > 0) {
437
+ if (this.evaluateConditions(permission.conditions, user, resource)) {
438
+ return true;
439
+ }
440
+ } else {
441
+ return true;
442
+ }
443
+ }
444
+
445
+ return false;
446
+ }
447
+
448
+ /**
449
+ * Get all permissions for roles (including inherited)
450
+ */
451
+ getUserPermissions(roleNames: string[]): string[] {
452
+ const permissions = new Set<string>();
453
+ const processedRoles = new Set<string>();
454
+
455
+ const processRole = (roleName: string) => {
456
+ if (processedRoles.has(roleName)) return;
457
+ processedRoles.add(roleName);
458
+
459
+ const role = this.roles.get(roleName);
460
+ if (!role) return;
461
+
462
+ role.permissions.forEach(p => permissions.add(p));
463
+
464
+ if (role.inherits) {
465
+ role.inherits.forEach(processRole);
466
+ }
467
+ };
468
+
469
+ roleNames.forEach(processRole);
470
+ return Array.from(permissions);
471
+ }
472
+
473
+ /**
474
+ * Check if user can perform action on resource
475
+ */
476
+ can(user: { id: string; roles: string[] }, action: string, resource?: any): boolean {
477
+ return this.hasPermission({ user, action, resource });
478
+ }
479
+
480
+ /**
481
+ * Check if user owns resource
482
+ */
483
+ owns(user: { id: string }, resource: { userId?: string; ownerId?: string }): boolean {
484
+ return resource.userId === user.id || resource.ownerId === user.id;
485
+ }
486
+
487
+ private evaluateConditions(
488
+ conditions: PermissionCondition[],
489
+ user: any,
490
+ resource: any,
491
+ ): boolean {
492
+ return conditions.every(condition => {
493
+ switch (condition.operator) {
494
+ case 'equals':
495
+ return resource?.[condition.field] === condition.value;
496
+ case 'in':
497
+ return Array.isArray(condition.value) &&
498
+ condition.value.includes(resource?.[condition.field]);
499
+ case 'owns':
500
+ return this.owns(user, resource);
501
+ case 'custom':
502
+ return condition.handler?.(user, resource) ?? false;
503
+ default:
504
+ return false;
505
+ }
506
+ });
507
+ }
508
+
509
+ /**
510
+ * Get role by name
511
+ */
512
+ getRole(name: string): Role | undefined {
513
+ return this.roles.get(name);
514
+ }
515
+
516
+ /**
517
+ * List all roles
518
+ */
519
+ listRoles(): Role[] {
520
+ return Array.from(this.roles.values());
521
+ }
522
+ }
523
+
524
+ /**
525
+ * ABAC (Attribute-Based Access Control) Service
526
+ */
527
+ @Injectable()
528
+ export class AbacService {
529
+ private readonly policies: AbacPolicy[] = [];
530
+
531
+ /**
532
+ * Add policy
533
+ */
534
+ addPolicy(policy: AbacPolicy): void {
535
+ this.policies.push(policy);
536
+ }
537
+
538
+ /**
539
+ * Evaluate access
540
+ */
541
+ evaluate(context: AbacContext): boolean {
542
+ for (const policy of this.policies) {
543
+ if (policy.effect === 'deny' && this.matchesPolicy(policy, context)) {
544
+ return false;
545
+ }
546
+ }
547
+
548
+ for (const policy of this.policies) {
549
+ if (policy.effect === 'allow' && this.matchesPolicy(policy, context)) {
550
+ return true;
551
+ }
552
+ }
553
+
554
+ return false; // Default deny
555
+ }
556
+
557
+ private matchesPolicy(policy: AbacPolicy, context: AbacContext): boolean {
558
+ // Check subject conditions
559
+ if (policy.subject && !this.matchConditions(policy.subject, context.subject)) {
560
+ return false;
561
+ }
562
+
563
+ // Check resource conditions
564
+ if (policy.resource && !this.matchConditions(policy.resource, context.resource)) {
565
+ return false;
566
+ }
567
+
568
+ // Check action
569
+ if (policy.action && policy.action !== context.action && policy.action !== '*') {
570
+ return false;
571
+ }
572
+
573
+ // Check environment
574
+ if (policy.environment && !this.matchConditions(policy.environment, context.environment)) {
575
+ return false;
576
+ }
577
+
578
+ return true;
579
+ }
580
+
581
+ private matchConditions(
582
+ conditions: Record<string, any>,
583
+ values: Record<string, any>,
584
+ ): boolean {
585
+ for (const [key, expected] of Object.entries(conditions)) {
586
+ const actual = values[key];
587
+ if (Array.isArray(expected)) {
588
+ if (!expected.includes(actual)) return false;
589
+ } else if (expected !== actual) {
590
+ return false;
591
+ }
592
+ }
593
+ return true;
594
+ }
595
+ }
596
+
597
+ interface AbacPolicy {
598
+ name: string;
599
+ effect: 'allow' | 'deny';
600
+ subject?: Record<string, any>;
601
+ resource?: Record<string, any>;
602
+ action?: string;
603
+ environment?: Record<string, any>;
604
+ }
605
+
606
+ interface AbacContext {
607
+ subject: Record<string, any>;
608
+ resource: Record<string, any>;
609
+ action: string;
610
+ environment?: Record<string, any>;
611
+ }
612
+ `;
613
+ }
614
+ function generateRbacDecorator() {
615
+ return `/**
616
+ * RBAC Decorators
617
+ * Declarative access control on routes
618
+ */
619
+
620
+ import { SetMetadata, applyDecorators, UseGuards } from '@nestjs/common';
621
+ import {
622
+ Injectable,
623
+ CanActivate,
624
+ ExecutionContext,
625
+ ForbiddenException,
626
+ } from '@nestjs/common';
627
+ import { Reflector } from '@nestjs/core';
628
+ import { RbacService } from './rbac.service';
629
+
630
+ export const ROLES_KEY = 'roles';
631
+ export const PERMISSIONS_KEY = 'permissions';
632
+ export const RESOURCE_KEY = 'resource';
633
+ export const OWNERSHIP_KEY = 'ownership';
634
+
635
+ /**
636
+ * Require specific roles
637
+ */
638
+ export function Roles(...roles: string[]) {
639
+ return SetMetadata(ROLES_KEY, roles);
640
+ }
641
+
642
+ /**
643
+ * Require specific permissions
644
+ */
645
+ export function Permissions(...permissions: string[]) {
646
+ return SetMetadata(PERMISSIONS_KEY, permissions);
647
+ }
648
+
649
+ /**
650
+ * Require resource ownership
651
+ */
652
+ export function RequireOwnership(resourceParam: string = 'id') {
653
+ return applyDecorators(
654
+ SetMetadata(OWNERSHIP_KEY, resourceParam),
655
+ UseGuards(OwnershipGuard),
656
+ );
657
+ }
658
+
659
+ /**
660
+ * Public route (no auth required)
661
+ */
662
+ export function Public() {
663
+ return SetMetadata('isPublic', true);
664
+ }
665
+
666
+ /**
667
+ * Roles Guard
668
+ */
669
+ @Injectable()
670
+ export class RolesGuard implements CanActivate {
671
+ constructor(
672
+ private readonly reflector: Reflector,
673
+ private readonly rbacService: RbacService,
674
+ ) {}
675
+
676
+ canActivate(context: ExecutionContext): boolean {
677
+ const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
678
+ context.getHandler(),
679
+ context.getClass(),
680
+ ]);
681
+
682
+ if (!requiredRoles || requiredRoles.length === 0) {
683
+ return true;
684
+ }
685
+
686
+ const request = context.switchToHttp().getRequest();
687
+ const user = request.user;
688
+
689
+ if (!user || !user.roles) {
690
+ throw new ForbiddenException('No roles assigned');
691
+ }
692
+
693
+ const hasRole = requiredRoles.some(role => user.roles.includes(role));
694
+
695
+ if (!hasRole) {
696
+ throw new ForbiddenException('Insufficient role privileges');
697
+ }
698
+
699
+ return true;
700
+ }
701
+ }
702
+
703
+ /**
704
+ * Permissions Guard
705
+ */
706
+ @Injectable()
707
+ export class PermissionsGuard implements CanActivate {
708
+ constructor(
709
+ private readonly reflector: Reflector,
710
+ private readonly rbacService: RbacService,
711
+ ) {}
712
+
713
+ canActivate(context: ExecutionContext): boolean {
714
+ const requiredPermissions = this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [
715
+ context.getHandler(),
716
+ context.getClass(),
717
+ ]);
718
+
719
+ if (!requiredPermissions || requiredPermissions.length === 0) {
720
+ return true;
721
+ }
722
+
723
+ const request = context.switchToHttp().getRequest();
724
+ const user = request.user;
725
+
726
+ if (!user) {
727
+ throw new ForbiddenException('Not authenticated');
728
+ }
729
+
730
+ const userPermissions = this.rbacService.getUserPermissions(user.roles || []);
731
+
732
+ const hasAllPermissions = requiredPermissions.every(
733
+ perm => userPermissions.includes(perm) || userPermissions.includes('*'),
734
+ );
735
+
736
+ if (!hasAllPermissions) {
737
+ throw new ForbiddenException('Insufficient permissions');
738
+ }
739
+
740
+ return true;
741
+ }
742
+ }
743
+
744
+ /**
745
+ * Ownership Guard
746
+ */
747
+ @Injectable()
748
+ export class OwnershipGuard implements CanActivate {
749
+ constructor(private readonly reflector: Reflector) {}
750
+
751
+ async canActivate(context: ExecutionContext): Promise<boolean> {
752
+ const resourceParam = this.reflector.get<string>(OWNERSHIP_KEY, context.getHandler());
753
+
754
+ if (!resourceParam) {
755
+ return true;
756
+ }
757
+
758
+ const request = context.switchToHttp().getRequest();
759
+ const user = request.user;
760
+ const resourceId = request.params[resourceParam];
761
+
762
+ if (!user) {
763
+ throw new ForbiddenException('Not authenticated');
764
+ }
765
+
766
+ // This would need to fetch the resource and check ownership
767
+ // For now, we'll just check if the IDs match
768
+ if (resourceId !== user.id) {
769
+ throw new ForbiddenException('You do not own this resource');
770
+ }
771
+
772
+ return true;
773
+ }
774
+ }
775
+ `;
776
+ }
777
+ function generateOwaspMiddleware() {
778
+ return `/**
779
+ * OWASP Security Middleware
780
+ * Protection against common web vulnerabilities
781
+ */
782
+
783
+ import { Injectable, NestMiddleware, BadRequestException } from '@nestjs/common';
784
+ import { Request, Response, NextFunction } from 'express';
785
+
786
+ @Injectable()
787
+ export class OwaspMiddleware implements NestMiddleware {
788
+ use(req: Request, res: Response, next: NextFunction): void {
789
+ // Set security headers
790
+ this.setSecurityHeaders(res);
791
+
792
+ // Validate request
793
+ this.validateRequest(req);
794
+
795
+ next();
796
+ }
797
+
798
+ private setSecurityHeaders(res: Response): void {
799
+ // Prevent clickjacking
800
+ res.setHeader('X-Frame-Options', 'DENY');
801
+
802
+ // XSS protection
803
+ res.setHeader('X-XSS-Protection', '1; mode=block');
804
+
805
+ // Prevent MIME sniffing
806
+ res.setHeader('X-Content-Type-Options', 'nosniff');
807
+
808
+ // Content Security Policy
809
+ res.setHeader(
810
+ 'Content-Security-Policy',
811
+ "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'",
812
+ );
813
+
814
+ // HSTS
815
+ res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
816
+
817
+ // Referrer Policy
818
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
819
+
820
+ // Permissions Policy
821
+ res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
822
+ }
823
+
824
+ private validateRequest(req: Request): void {
825
+ // Check for common attack patterns
826
+ this.checkSqlInjection(req);
827
+ this.checkXss(req);
828
+ this.checkPathTraversal(req);
829
+ }
830
+
831
+ private checkSqlInjection(req: Request): void {
832
+ const sqlPatterns = [
833
+ /(\\'|\\")\\s*OR\\s+/i,
834
+ /UNION\\s+SELECT/i,
835
+ /DROP\\s+TABLE/i,
836
+ /INSERT\\s+INTO/i,
837
+ /DELETE\\s+FROM/i,
838
+ /UPDATE\\s+\\w+\\s+SET/i,
839
+ /--/,
840
+ /;\\s*$/,
841
+ ];
842
+
843
+ const checkValue = (value: any): void => {
844
+ if (typeof value === 'string') {
845
+ for (const pattern of sqlPatterns) {
846
+ if (pattern.test(value)) {
847
+ throw new BadRequestException('Potential SQL injection detected');
848
+ }
849
+ }
850
+ } else if (typeof value === 'object' && value !== null) {
851
+ Object.values(value).forEach(checkValue);
852
+ }
853
+ };
854
+
855
+ checkValue(req.query);
856
+ checkValue(req.body);
857
+ checkValue(req.params);
858
+ }
859
+
860
+ private checkXss(req: Request): void {
861
+ const xssPatterns = [
862
+ // Script tags and attributes
863
+ /<script\\b[^>]*>/i,
864
+ /<\\/script>/i,
865
+ // JavaScript protocol
866
+ /javascript:/i,
867
+ /vbscript:/i,
868
+ // Event handlers
869
+ /on\\w+\\s*=/i,
870
+ // Dangerous HTML elements
871
+ /<iframe/i,
872
+ /<object/i,
873
+ /<embed/i,
874
+ /<svg[^>]*onload/i,
875
+ /<img[^>]*onerror/i,
876
+ /<body[^>]*onload/i,
877
+ // Data URIs with HTML/Script
878
+ /data:\\s*text\\/html/i,
879
+ /data:\\s*application\\/javascript/i,
880
+ // CSS injection
881
+ /expression\\s*\\(/i,
882
+ /-moz-binding/i,
883
+ // HTML5 attack vectors
884
+ /<math/i,
885
+ /<video[^>]*on/i,
886
+ /<audio[^>]*on/i,
887
+ // Template injection
888
+ /\\{\\{.*\\}\\}/,
889
+ /\\$\\{.*\\}/,
890
+ ];
891
+
892
+ const checkValue = (value: any): void => {
893
+ if (typeof value === 'string') {
894
+ for (const pattern of xssPatterns) {
895
+ if (pattern.test(value)) {
896
+ throw new BadRequestException('Potential XSS attack detected');
897
+ }
898
+ }
899
+ } else if (typeof value === 'object' && value !== null) {
900
+ Object.values(value).forEach(checkValue);
901
+ }
902
+ };
903
+
904
+ checkValue(req.query);
905
+ checkValue(req.body);
906
+ }
907
+
908
+ private checkPathTraversal(req: Request): void {
909
+ const pathTraversalPatterns = [
910
+ /\\.\\.\\//, // ../
911
+ /\\.\\.\\\\/, // ..\\
912
+ /%2e%2e%2f/i, // encoded ../
913
+ /%2e%2e\\//i,
914
+ /%2e%2e%5c/i, // encoded ..\\
915
+ ];
916
+
917
+ const url = req.url + JSON.stringify(req.params);
918
+
919
+ for (const pattern of pathTraversalPatterns) {
920
+ if (pattern.test(url)) {
921
+ throw new BadRequestException('Potential path traversal attack detected');
922
+ }
923
+ }
924
+ }
925
+ }
926
+
927
+ /**
928
+ * CSRF Protection
929
+ */
930
+ @Injectable()
931
+ export class CsrfMiddleware implements NestMiddleware {
932
+ private readonly tokenStore = new Map<string, string>();
933
+
934
+ use(req: Request, res: Response, next: NextFunction): void {
935
+ // Skip for safe methods
936
+ if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
937
+ return next();
938
+ }
939
+
940
+ const token = req.headers['x-csrf-token'] as string;
941
+ const sessionId = req.session?.id || req.headers['x-session-id'] as string;
942
+
943
+ if (!token || !sessionId) {
944
+ throw new BadRequestException('CSRF token missing');
945
+ }
946
+
947
+ const storedToken = this.tokenStore.get(sessionId);
948
+ if (token !== storedToken) {
949
+ throw new BadRequestException('Invalid CSRF token');
950
+ }
951
+
952
+ next();
953
+ }
954
+
955
+ generateToken(sessionId: string): string {
956
+ const token = require('crypto').randomBytes(32).toString('hex');
957
+ this.tokenStore.set(sessionId, token);
958
+ return token;
959
+ }
960
+ }
961
+
962
+ /**
963
+ * Request size limiter
964
+ */
965
+ export function createSizeLimiter(maxSize: number) {
966
+ return (req: Request, res: Response, next: NextFunction) => {
967
+ const contentLength = parseInt(req.headers['content-length'] || '0', 10);
968
+ if (contentLength > maxSize) {
969
+ throw new BadRequestException(\`Request too large. Max size: \${maxSize} bytes\`);
970
+ }
971
+ next();
972
+ };
973
+ }
974
+ `;
975
+ }
976
+ function generateSecretVault() {
977
+ return `/**
978
+ * Secret Vault Service
979
+ * Secure secret management
980
+ */
981
+
982
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
983
+ import * as crypto from 'crypto';
984
+
985
+ export interface Secret {
986
+ key: string;
987
+ value: string;
988
+ version: number;
989
+ createdAt: Date;
990
+ expiresAt?: Date;
991
+ metadata?: Record<string, any>;
992
+ }
993
+
994
+ @Injectable()
995
+ export class SecretVaultService implements OnModuleInit {
996
+ private readonly logger = new Logger(SecretVaultService.name);
997
+ private readonly secrets = new Map<string, Secret[]>();
998
+ private masterKey: Buffer | null = null;
999
+
1000
+ async onModuleInit() {
1001
+ // Initialize master key from environment
1002
+ const masterKeyHex = process.env.VAULT_MASTER_KEY;
1003
+ if (masterKeyHex) {
1004
+ this.masterKey = Buffer.from(masterKeyHex, 'hex');
1005
+ this.logger.log('Secret vault initialized with master key');
1006
+ } else {
1007
+ this.logger.warn('No master key configured, secrets will be stored in plaintext');
1008
+ }
1009
+ }
1010
+
1011
+ /**
1012
+ * Store a secret
1013
+ */
1014
+ async setSecret(key: string, value: string, options?: { expiresIn?: number; metadata?: Record<string, any> }): Promise<Secret> {
1015
+ const versions = this.secrets.get(key) || [];
1016
+ const version = versions.length + 1;
1017
+
1018
+ const encryptedValue = this.masterKey ? this.encrypt(value) : value;
1019
+
1020
+ const secret: Secret = {
1021
+ key,
1022
+ value: encryptedValue,
1023
+ version,
1024
+ createdAt: new Date(),
1025
+ expiresAt: options?.expiresIn ? new Date(Date.now() + options.expiresIn) : undefined,
1026
+ metadata: options?.metadata,
1027
+ };
1028
+
1029
+ versions.push(secret);
1030
+ this.secrets.set(key, versions);
1031
+
1032
+ this.logger.log(\`Secret '\${key}' stored (version \${version})\`);
1033
+ return { ...secret, value: '[REDACTED]' };
1034
+ }
1035
+
1036
+ /**
1037
+ * Get a secret
1038
+ */
1039
+ async getSecret(key: string, version?: number): Promise<string | null> {
1040
+ const versions = this.secrets.get(key);
1041
+ if (!versions || versions.length === 0) {
1042
+ return null;
1043
+ }
1044
+
1045
+ const secret = version
1046
+ ? versions.find(s => s.version === version)
1047
+ : versions[versions.length - 1];
1048
+
1049
+ if (!secret) {
1050
+ return null;
1051
+ }
1052
+
1053
+ if (secret.expiresAt && new Date() > secret.expiresAt) {
1054
+ this.logger.warn(\`Secret '\${key}' has expired\`);
1055
+ return null;
1056
+ }
1057
+
1058
+ return this.masterKey ? this.decrypt(secret.value) : secret.value;
1059
+ }
1060
+
1061
+ /**
1062
+ * Delete a secret
1063
+ */
1064
+ async deleteSecret(key: string): Promise<boolean> {
1065
+ const deleted = this.secrets.delete(key);
1066
+ if (deleted) {
1067
+ this.logger.log(\`Secret '\${key}' deleted\`);
1068
+ }
1069
+ return deleted;
1070
+ }
1071
+
1072
+ /**
1073
+ * Rotate a secret
1074
+ */
1075
+ async rotateSecret(key: string, newValue: string): Promise<Secret> {
1076
+ return this.setSecret(key, newValue);
1077
+ }
1078
+
1079
+ /**
1080
+ * List secret keys
1081
+ */
1082
+ listKeys(): string[] {
1083
+ return Array.from(this.secrets.keys());
1084
+ }
1085
+
1086
+ /**
1087
+ * Get secret metadata
1088
+ */
1089
+ getSecretInfo(key: string): Omit<Secret, 'value'> | null {
1090
+ const versions = this.secrets.get(key);
1091
+ if (!versions || versions.length === 0) {
1092
+ return null;
1093
+ }
1094
+
1095
+ const latest = versions[versions.length - 1];
1096
+ return {
1097
+ key: latest.key,
1098
+ version: latest.version,
1099
+ createdAt: latest.createdAt,
1100
+ expiresAt: latest.expiresAt,
1101
+ metadata: latest.metadata,
1102
+ };
1103
+ }
1104
+
1105
+ private encrypt(plaintext: string): string {
1106
+ if (!this.masterKey) return plaintext;
1107
+
1108
+ const iv = crypto.randomBytes(16);
1109
+ const cipher = crypto.createCipheriv('aes-256-gcm', this.masterKey, iv);
1110
+
1111
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
1112
+ encrypted += cipher.final('hex');
1113
+
1114
+ const tag = cipher.getAuthTag();
1115
+
1116
+ return \`\${iv.toString('hex')}:\${tag.toString('hex')}:\${encrypted}\`;
1117
+ }
1118
+
1119
+ private decrypt(ciphertext: string): string {
1120
+ if (!this.masterKey) return ciphertext;
1121
+
1122
+ const [ivHex, tagHex, encrypted] = ciphertext.split(':');
1123
+ const iv = Buffer.from(ivHex, 'hex');
1124
+ const tag = Buffer.from(tagHex, 'hex');
1125
+
1126
+ const decipher = crypto.createDecipheriv('aes-256-gcm', this.masterKey, iv);
1127
+ decipher.setAuthTag(tag);
1128
+
1129
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
1130
+ decrypted += decipher.final('utf8');
1131
+
1132
+ return decrypted;
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * Environment secret loader
1138
+ */
1139
+ export function loadSecrets(vault: SecretVaultService): Record<string, string> {
1140
+ const secrets: Record<string, string> = {};
1141
+
1142
+ for (const key of vault.listKeys()) {
1143
+ // Sync version - for initialization only
1144
+ const value = (vault as any).secrets.get(key)?.[0]?.value;
1145
+ if (value) {
1146
+ secrets[key] = value;
1147
+ }
1148
+ }
1149
+
1150
+ return secrets;
1151
+ }
1152
+ `;
1153
+ }
1154
+ function generateInputSanitizer() {
1155
+ return `/**
1156
+ * Input Sanitizer
1157
+ * Clean and validate user input
1158
+ * OWASP A03:2021 compliant
1159
+ */
1160
+
1161
+ import { Injectable, Logger } from '@nestjs/common';
1162
+
1163
+ @Injectable()
1164
+ export class InputSanitizer {
1165
+ private readonly logger = new Logger(InputSanitizer.name);
1166
+
1167
+ /**
1168
+ * Sanitize string for HTML output - prevents XSS
1169
+ * Includes complete entity encoding for all dangerous characters
1170
+ */
1171
+ sanitizeHtml(input: string): string {
1172
+ if (!input || typeof input !== 'string') return '';
1173
+
1174
+ const map: Record<string, string> = {
1175
+ '&': '&amp;',
1176
+ '<': '&lt;',
1177
+ '>': '&gt;',
1178
+ '"': '&quot;',
1179
+ "'": '&#x27;',
1180
+ '/': '&#x2F;',
1181
+ '\`': '&#x60;', // Backtick for template literals
1182
+ '=': '&#x3D;', // Equals sign
1183
+ };
1184
+ return input.replace(/[&<>"'/\`=]/g, char => map[char] || char);
1185
+ }
1186
+
1187
+ /**
1188
+ * @deprecated NEVER use this for SQL injection prevention!
1189
+ * Always use parameterized queries/prepared statements instead.
1190
+ * This method only exists for legacy compatibility.
1191
+ */
1192
+ sanitizeSql(input: string): string {
1193
+ this.logger.warn('sanitizeSql is deprecated! Use parameterized queries instead.');
1194
+ // This is NOT safe - always use parameterized queries
1195
+ return input.replace(/['";\\\\]/g, '');
1196
+ }
1197
+
1198
+ /**
1199
+ * Sanitize for shell commands using WHITELIST approach
1200
+ * Only allows alphanumeric, hyphen, underscore, and dot
1201
+ * For complex shell operations, prefer using spawn with args array
1202
+ */
1203
+ sanitizeShell(input: string): string {
1204
+ if (!input || typeof input !== 'string') return '';
1205
+
1206
+ // Strict whitelist - only allow safe characters
1207
+ const sanitized = input.replace(/[^a-zA-Z0-9_\\-.]/g, '');
1208
+
1209
+ // Check for path traversal
1210
+ if (sanitized.includes('..')) {
1211
+ this.logger.warn(\`Shell sanitization blocked path traversal attempt: \${input}\`);
1212
+ return sanitized.replace(/\\.\\.+/g, '');
1213
+ }
1214
+
1215
+ return sanitized;
1216
+ }
1217
+
1218
+ /**
1219
+ * Sanitize filename - prevents path traversal and invalid chars
1220
+ */
1221
+ sanitizeFilename(input: string): string {
1222
+ if (!input || typeof input !== 'string') return '';
1223
+
1224
+ return input
1225
+ .replace(/\\.\\./g, '') // Remove path traversal
1226
+ .replace(/[/\\\\]/g, '') // Remove path separators
1227
+ .replace(/[?%*:|"<>\\x00-\\x1f]/g, '-') // Remove invalid chars
1228
+ .replace(/^[-_.]+|[-_.]+$/g, '') // Trim leading/trailing special chars
1229
+ .substring(0, 255); // Limit length
1230
+ }
1231
+
1232
+ /**
1233
+ * Sanitize URL - validates protocol and structure
1234
+ */
1235
+ sanitizeUrl(input: string): string {
1236
+ if (!input || typeof input !== 'string') return '';
1237
+
1238
+ try {
1239
+ const url = new URL(input);
1240
+
1241
+ // Only allow http/https protocols
1242
+ if (!['http:', 'https:'].includes(url.protocol)) {
1243
+ this.logger.warn(\`URL sanitization blocked invalid protocol: \${url.protocol}\`);
1244
+ return '';
1245
+ }
1246
+
1247
+ // Block javascript: and data: URIs (case-insensitive)
1248
+ if (/^(javascript|data|vbscript):/i.test(input)) {
1249
+ this.logger.warn(\`URL sanitization blocked dangerous scheme: \${input}\`);
1250
+ return '';
1251
+ }
1252
+
1253
+ return url.toString();
1254
+ } catch {
1255
+ return '';
1256
+ }
1257
+ }
1258
+
1259
+ /**
1260
+ * Strip all HTML tags - for plain text extraction
1261
+ */
1262
+ stripHtml(input: string): string {
1263
+ if (!input || typeof input !== 'string') return '';
1264
+
1265
+ // Remove script and style content entirely
1266
+ let result = input
1267
+ .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')
1268
+ .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '');
1269
+
1270
+ // Then strip remaining tags
1271
+ result = result.replace(/<[^>]*>/g, '');
1272
+
1273
+ // Decode entities
1274
+ const entities: Record<string, string> = {
1275
+ '&amp;': '&',
1276
+ '&lt;': '<',
1277
+ '&gt;': '>',
1278
+ '&quot;': '"',
1279
+ '&#x27;': "'",
1280
+ '&nbsp;': ' ',
1281
+ };
1282
+ for (const [entity, char] of Object.entries(entities)) {
1283
+ result = result.replace(new RegExp(entity, 'g'), char);
1284
+ }
1285
+
1286
+ return result;
1287
+ }
1288
+
1289
+ /**
1290
+ * Normalize whitespace
1291
+ */
1292
+ normalizeWhitespace(input: string): string {
1293
+ if (!input || typeof input !== 'string') return '';
1294
+ return input.replace(/[\\s\\u200B-\\u200D\\uFEFF]+/g, ' ').trim();
1295
+ }
1296
+
1297
+ /**
1298
+ * Sanitize JSON - prevents XSS in JSON values and prototype pollution
1299
+ */
1300
+ sanitizeJson(input: any, depth = 0): any {
1301
+ // Prevent infinite recursion
1302
+ if (depth > 10) {
1303
+ this.logger.warn('JSON sanitization max depth exceeded');
1304
+ return null;
1305
+ }
1306
+
1307
+ if (typeof input === 'string') {
1308
+ return this.sanitizeHtml(input);
1309
+ }
1310
+ if (Array.isArray(input)) {
1311
+ return input.map(item => this.sanitizeJson(item, depth + 1));
1312
+ }
1313
+ if (typeof input === 'object' && input !== null) {
1314
+ const result: Record<string, any> = {};
1315
+ for (const [key, value] of Object.entries(input)) {
1316
+ // Block prototype pollution attacks
1317
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
1318
+ this.logger.warn(\`JSON sanitization blocked prototype pollution key: \${key}\`);
1319
+ continue;
1320
+ }
1321
+ result[this.sanitizeHtml(key)] = this.sanitizeJson(value, depth + 1);
1322
+ }
1323
+ return result;
1324
+ }
1325
+ return input;
1326
+ }
1327
+
1328
+ /**
1329
+ * Validate and sanitize email
1330
+ */
1331
+ sanitizeEmail(input: string): string | null {
1332
+ if (!input || typeof input !== 'string') return null;
1333
+
1334
+ const email = input.toLowerCase().trim();
1335
+
1336
+ // RFC 5321 max length
1337
+ if (email.length > 254) return null;
1338
+
1339
+ // Basic email regex (more permissive to allow valid emails)
1340
+ const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
1341
+ return emailRegex.test(email) ? email : null;
1342
+ }
1343
+
1344
+ /**
1345
+ * Sanitize phone number
1346
+ */
1347
+ sanitizePhone(input: string): string {
1348
+ if (!input || typeof input !== 'string') return '';
1349
+ const sanitized = input.replace(/[^0-9+\\-() ]/g, '');
1350
+ // E.164 max length is 15 digits plus '+'
1351
+ return sanitized.substring(0, 20);
1352
+ }
1353
+
1354
+ /**
1355
+ * Validate field name for database queries
1356
+ * Prevents SQL injection via dynamic column names
1357
+ */
1358
+ validateFieldName(fieldName: string, allowedFields: string[]): string {
1359
+ if (!fieldName || typeof fieldName !== 'string') {
1360
+ throw new Error('Invalid field name');
1361
+ }
1362
+
1363
+ // Must be in allowed list
1364
+ if (!allowedFields.includes(fieldName)) {
1365
+ throw new Error(\`Field "\${fieldName}" not allowed. Allowed: \${allowedFields.join(', ')}\`);
1366
+ }
1367
+
1368
+ // Additional safety check - must match safe pattern
1369
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(fieldName)) {
1370
+ throw new Error(\`Field name format invalid: \${fieldName}\`);
1371
+ }
1372
+
1373
+ return fieldName;
1374
+ }
1375
+ }
1376
+
1377
+ /**
1378
+ * Sanitize decorator
1379
+ */
1380
+ export function Sanitize(type: 'html' | 'sql' | 'filename' | 'url'): PropertyDecorator {
1381
+ return function (target: Object, propertyKey: string | symbol) {
1382
+ Reflect.defineMetadata('sanitize', type, target, propertyKey);
1383
+ };
1384
+ }
1385
+ `;
1386
+ }
1387
+ function generateCorsConfig() {
1388
+ return `/**
1389
+ * Secure CORS Configuration
1390
+ * OWASP A05:2021 - Security Misconfiguration prevention
1391
+ */
1392
+
1393
+ import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
1394
+ import { Logger } from '@nestjs/common';
1395
+
1396
+ const logger = new Logger('CorsConfig');
1397
+
1398
+ /**
1399
+ * Create secure CORS configuration
1400
+ * - Strict origin validation (no wildcards in production)
1401
+ * - Explicit allowed methods
1402
+ * - Credentials handling with origin check
1403
+ */
1404
+ export function createSecureCorsConfig(options: {
1405
+ allowedOrigins: string[];
1406
+ isProduction?: boolean;
1407
+ }): CorsOptions {
1408
+ const { allowedOrigins, isProduction = process.env.NODE_ENV === 'production' } = options;
1409
+
1410
+ // Validate origins
1411
+ if (isProduction && allowedOrigins.includes('*')) {
1412
+ throw new Error('CORS: Wildcard origin (*) not allowed in production');
1413
+ }
1414
+
1415
+ // Normalize origins to prevent bypass (e.g., trailing slashes)
1416
+ const normalizedOrigins = allowedOrigins.map(origin => {
1417
+ if (origin === '*') return origin;
1418
+ return origin.replace(/\\/+$/, '').toLowerCase();
1419
+ });
1420
+
1421
+ return {
1422
+ origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
1423
+ // Allow requests with no origin (like mobile apps or Postman)
1424
+ // In production, you may want to be stricter
1425
+ if (!origin) {
1426
+ if (isProduction) {
1427
+ logger.warn('Request with no origin header - consider blocking in production');
1428
+ }
1429
+ callback(null, true);
1430
+ return;
1431
+ }
1432
+
1433
+ const normalizedOrigin = origin.replace(/\\/+$/, '').toLowerCase();
1434
+
1435
+ // Check against whitelist
1436
+ if (normalizedOrigins.includes('*') || normalizedOrigins.includes(normalizedOrigin)) {
1437
+ callback(null, true);
1438
+ return;
1439
+ }
1440
+
1441
+ // Check for subdomain matching (if pattern like *.example.com is allowed)
1442
+ for (const allowed of normalizedOrigins) {
1443
+ if (allowed.startsWith('*.')) {
1444
+ const domain = allowed.slice(2);
1445
+ if (normalizedOrigin.endsWith(domain)) {
1446
+ callback(null, true);
1447
+ return;
1448
+ }
1449
+ }
1450
+ }
1451
+
1452
+ logger.warn(\`CORS blocked origin: \${origin}\`);
1453
+ callback(new Error('Not allowed by CORS'));
1454
+ },
1455
+
1456
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
1457
+
1458
+ // Only expose safe headers
1459
+ allowedHeaders: [
1460
+ 'Content-Type',
1461
+ 'Authorization',
1462
+ 'X-Requested-With',
1463
+ 'Accept',
1464
+ 'Origin',
1465
+ 'X-CSRF-Token',
1466
+ ],
1467
+
1468
+ // Credentials require explicit origin (not *)
1469
+ credentials: true,
1470
+
1471
+ // Cache preflight for 24 hours
1472
+ maxAge: 86400,
1473
+
1474
+ // Limit exposed headers to prevent information leak
1475
+ exposedHeaders: ['X-Request-Id', 'X-RateLimit-Remaining'],
1476
+
1477
+ // Don't allow OPTIONS to continue to route handlers
1478
+ preflightContinue: false,
1479
+
1480
+ // Return 204 for OPTIONS
1481
+ optionsSuccessStatus: 204,
1482
+ };
1483
+ }
1484
+
1485
+ /**
1486
+ * Validate origin against allowed list
1487
+ * Use for manual CORS checks
1488
+ */
1489
+ export function isOriginAllowed(origin: string, allowedOrigins: string[]): boolean {
1490
+ if (!origin) return false;
1491
+
1492
+ const normalizedOrigin = origin.replace(/\\/+$/, '').toLowerCase();
1493
+
1494
+ for (const allowed of allowedOrigins) {
1495
+ const normalizedAllowed = allowed.replace(/\\/+$/, '').toLowerCase();
1496
+
1497
+ if (normalizedAllowed === '*') return true;
1498
+ if (normalizedAllowed === normalizedOrigin) return true;
1499
+
1500
+ // Subdomain matching
1501
+ if (normalizedAllowed.startsWith('*.')) {
1502
+ const domain = normalizedAllowed.slice(2);
1503
+ if (normalizedOrigin.endsWith('.' + domain) || normalizedOrigin === domain) {
1504
+ return true;
1505
+ }
1506
+ }
1507
+ }
1508
+
1509
+ return false;
1510
+ }
1511
+ `;
1512
+ }
1513
+ function generateCookieConfig() {
1514
+ return `/**
1515
+ * Secure Cookie Configuration
1516
+ * OWASP A07:2021 - Authentication best practices
1517
+ */
1518
+
1519
+ import { CookieOptions } from 'express';
1520
+ import { Logger } from '@nestjs/common';
1521
+
1522
+ const logger = new Logger('CookieConfig');
1523
+
1524
+ export interface SecureCookieOptions {
1525
+ name: string;
1526
+ isProduction?: boolean;
1527
+ domain?: string;
1528
+ maxAgeSeconds?: number;
1529
+ }
1530
+
1531
+ /**
1532
+ * Create secure cookie options
1533
+ * - HttpOnly to prevent XSS access
1534
+ * - Secure flag for HTTPS-only
1535
+ * - SameSite to prevent CSRF
1536
+ * - Proper path and domain scoping
1537
+ */
1538
+ export function createSecureCookieOptions(options: SecureCookieOptions): CookieOptions {
1539
+ const {
1540
+ isProduction = process.env.NODE_ENV === 'production',
1541
+ domain,
1542
+ maxAgeSeconds = 3600, // 1 hour default
1543
+ } = options;
1544
+
1545
+ return {
1546
+ // Prevent JavaScript access (XSS protection)
1547
+ httpOnly: true,
1548
+
1549
+ // Only send over HTTPS in production
1550
+ secure: isProduction,
1551
+
1552
+ // Strict SameSite prevents CSRF
1553
+ // Use 'lax' if you need cross-site GET requests (e.g., OAuth redirects)
1554
+ sameSite: 'strict',
1555
+
1556
+ // Scope to specific path
1557
+ path: '/',
1558
+
1559
+ // Domain scoping (omit to use current domain only)
1560
+ domain: domain,
1561
+
1562
+ // Set explicit expiry
1563
+ maxAge: maxAgeSeconds * 1000, // Express uses milliseconds
1564
+
1565
+ // Signed cookies for integrity (requires cookie-parser with secret)
1566
+ signed: true,
1567
+ };
1568
+ }
1569
+
1570
+ /**
1571
+ * Session cookie configuration
1572
+ */
1573
+ export function createSessionCookieOptions(options: {
1574
+ isProduction?: boolean;
1575
+ sessionMaxAgeHours?: number;
1576
+ }): CookieOptions {
1577
+ const {
1578
+ isProduction = process.env.NODE_ENV === 'production',
1579
+ sessionMaxAgeHours = 24,
1580
+ } = options;
1581
+
1582
+ return {
1583
+ httpOnly: true,
1584
+ secure: isProduction,
1585
+ sameSite: 'strict',
1586
+ path: '/',
1587
+ maxAge: sessionMaxAgeHours * 60 * 60 * 1000,
1588
+ signed: true,
1589
+ };
1590
+ }
1591
+
1592
+ /**
1593
+ * Authentication token cookie options
1594
+ * More restrictive for auth tokens
1595
+ */
1596
+ export function createAuthCookieOptions(options: {
1597
+ isProduction?: boolean;
1598
+ tokenMaxAgeMinutes?: number;
1599
+ domain?: string;
1600
+ }): CookieOptions {
1601
+ const {
1602
+ isProduction = process.env.NODE_ENV === 'production',
1603
+ tokenMaxAgeMinutes = 15, // Short-lived for security
1604
+ domain,
1605
+ } = options;
1606
+
1607
+ return {
1608
+ httpOnly: true,
1609
+ secure: isProduction,
1610
+ sameSite: 'strict',
1611
+ path: '/',
1612
+ domain,
1613
+ maxAge: tokenMaxAgeMinutes * 60 * 1000,
1614
+ signed: true,
1615
+ };
1616
+ }
1617
+
1618
+ /**
1619
+ * Refresh token cookie options
1620
+ * Longer lived but more restricted
1621
+ */
1622
+ export function createRefreshTokenCookieOptions(options: {
1623
+ isProduction?: boolean;
1624
+ maxAgeDays?: number;
1625
+ domain?: string;
1626
+ }): CookieOptions {
1627
+ const {
1628
+ isProduction = process.env.NODE_ENV === 'production',
1629
+ maxAgeDays = 7,
1630
+ domain,
1631
+ } = options;
1632
+
1633
+ return {
1634
+ httpOnly: true,
1635
+ secure: isProduction,
1636
+ sameSite: 'strict',
1637
+ // Restrict refresh token to specific path
1638
+ path: '/auth/refresh',
1639
+ domain,
1640
+ maxAge: maxAgeDays * 24 * 60 * 60 * 1000,
1641
+ signed: true,
1642
+ };
1643
+ }
1644
+
1645
+ /**
1646
+ * Clear cookie securely
1647
+ */
1648
+ export function clearCookieOptions(isProduction: boolean = process.env.NODE_ENV === 'production'): CookieOptions {
1649
+ return {
1650
+ httpOnly: true,
1651
+ secure: isProduction,
1652
+ sameSite: 'strict',
1653
+ path: '/',
1654
+ maxAge: 0,
1655
+ expires: new Date(0),
1656
+ };
1657
+ }
1658
+
1659
+ /**
1660
+ * Cookie name constants with prefix for clarity
1661
+ */
1662
+ export const CookieNames = {
1663
+ SESSION: '__Host-session', // __Host- prefix requires Secure and Path=/
1664
+ ACCESS_TOKEN: '__Host-access',
1665
+ REFRESH_TOKEN: '__Host-refresh',
1666
+ CSRF: '__Host-csrf',
1667
+ } as const;
1668
+
1669
+ /**
1670
+ * Validate cookie name follows security best practices
1671
+ * __Host- prefix ensures Secure, no Domain, Path=/
1672
+ * __Secure- prefix ensures Secure
1673
+ */
1674
+ export function validateCookieName(name: string, isProduction: boolean): void {
1675
+ if (isProduction) {
1676
+ if (name.startsWith('__Host-') || name.startsWith('__Secure-')) {
1677
+ return; // Valid secure prefix
1678
+ }
1679
+ logger.warn(\`Cookie "\${name}" should use __Host- or __Secure- prefix in production\`);
1680
+ }
1681
+ }
1682
+ `;
1683
+ }
1684
+ function generateJwtSecurity() {
1685
+ return `/**
1686
+ * JWT Security Service
1687
+ * OWASP A07:2021 - Identification and Authentication Failures prevention
1688
+ */
1689
+
1690
+ import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
1691
+ import * as crypto from 'crypto';
1692
+
1693
+ export interface JwtHeader {
1694
+ alg: string;
1695
+ typ: string;
1696
+ kid?: string;
1697
+ }
1698
+
1699
+ export interface JwtPayload {
1700
+ iss?: string; // Issuer
1701
+ sub?: string; // Subject
1702
+ aud?: string | string[]; // Audience
1703
+ exp?: number; // Expiration
1704
+ nbf?: number; // Not Before
1705
+ iat?: number; // Issued At
1706
+ jti?: string; // JWT ID (for revocation)
1707
+ [key: string]: any;
1708
+ }
1709
+
1710
+ export interface JwtValidationOptions {
1711
+ issuer?: string;
1712
+ audience?: string | string[];
1713
+ algorithms?: string[];
1714
+ clockTolerance?: number;
1715
+ maxAge?: number;
1716
+ }
1717
+
1718
+ // Secure algorithm whitelist - block 'none' and weak algorithms
1719
+ const ALLOWED_ALGORITHMS = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'];
1720
+ const WEAK_ALGORITHMS = ['none', 'HS1', 'RS1'];
1721
+
1722
+ @Injectable()
1723
+ export class JwtSecurityService {
1724
+ private readonly logger = new Logger(JwtSecurityService.name);
1725
+ private readonly revokedTokens = new Set<string>();
1726
+ private readonly usedJtis = new Map<string, number>(); // jti -> expiry timestamp
1727
+
1728
+ /**
1729
+ * Validate JWT structure and claims
1730
+ * Checks for common JWT attacks:
1731
+ * - Algorithm confusion (none attack, weak algorithms)
1732
+ * - Token replay (jti tracking)
1733
+ * - Expired/not-yet-valid tokens
1734
+ * - Issuer/audience mismatch
1735
+ */
1736
+ validateToken(token: string, options: JwtValidationOptions = {}): { header: JwtHeader; payload: JwtPayload } {
1737
+ if (!token || typeof token !== 'string') {
1738
+ throw new UnauthorizedException('Token is required');
1739
+ }
1740
+
1741
+ // Split and decode
1742
+ const parts = token.split('.');
1743
+ if (parts.length !== 3) {
1744
+ throw new UnauthorizedException('Invalid token format');
1745
+ }
1746
+
1747
+ let header: JwtHeader;
1748
+ let payload: JwtPayload;
1749
+
1750
+ try {
1751
+ header = JSON.parse(this.base64UrlDecode(parts[0]));
1752
+ payload = JSON.parse(this.base64UrlDecode(parts[1]));
1753
+ } catch {
1754
+ throw new UnauthorizedException('Invalid token encoding');
1755
+ }
1756
+
1757
+ // Validate algorithm
1758
+ this.validateAlgorithm(header.alg, options.algorithms);
1759
+
1760
+ // Validate claims
1761
+ this.validateClaims(payload, options);
1762
+
1763
+ // Check if token is revoked
1764
+ if (payload.jti && this.revokedTokens.has(payload.jti)) {
1765
+ throw new UnauthorizedException('Token has been revoked');
1766
+ }
1767
+
1768
+ // Check for replay (same jti used before expiry)
1769
+ if (payload.jti) {
1770
+ const existingExpiry = this.usedJtis.get(payload.jti);
1771
+ if (existingExpiry && Date.now() < existingExpiry) {
1772
+ // Potential replay - same jti, not expired
1773
+ this.logger.warn(\`Potential JWT replay detected: jti=\${payload.jti}\`);
1774
+ // Note: In some cases, legitimate retries may reuse tokens
1775
+ // Consider your use case before throwing here
1776
+ }
1777
+ if (payload.exp) {
1778
+ this.usedJtis.set(payload.jti, payload.exp * 1000);
1779
+ }
1780
+ }
1781
+
1782
+ return { header, payload };
1783
+ }
1784
+
1785
+ /**
1786
+ * Validate algorithm is allowed
1787
+ * Prevents algorithm confusion attacks
1788
+ */
1789
+ private validateAlgorithm(alg: string, allowedAlgorithms?: string[]): void {
1790
+ // Check against weak algorithms
1791
+ if (WEAK_ALGORITHMS.includes(alg.toLowerCase())) {
1792
+ this.logger.error(\`JWT with weak/dangerous algorithm detected: \${alg}\`);
1793
+ throw new UnauthorizedException('Invalid token algorithm');
1794
+ }
1795
+
1796
+ // Check against allowed list
1797
+ const allowed = allowedAlgorithms?.length ? allowedAlgorithms : ALLOWED_ALGORITHMS;
1798
+ if (!allowed.includes(alg)) {
1799
+ throw new UnauthorizedException('Token algorithm not allowed');
1800
+ }
1801
+ }
1802
+
1803
+ /**
1804
+ * Validate JWT claims
1805
+ */
1806
+ private validateClaims(payload: JwtPayload, options: JwtValidationOptions): void {
1807
+ const now = Math.floor(Date.now() / 1000);
1808
+ const clockTolerance = options.clockTolerance || 30; // 30 seconds tolerance
1809
+
1810
+ // Check expiration
1811
+ if (payload.exp !== undefined) {
1812
+ if (now > payload.exp + clockTolerance) {
1813
+ throw new UnauthorizedException('Token has expired');
1814
+ }
1815
+ } else {
1816
+ // Tokens without expiration are risky
1817
+ this.logger.warn('JWT without expiration claim');
1818
+ if (options.maxAge) {
1819
+ throw new UnauthorizedException('Token missing required exp claim');
1820
+ }
1821
+ }
1822
+
1823
+ // Check not before
1824
+ if (payload.nbf !== undefined) {
1825
+ if (now < payload.nbf - clockTolerance) {
1826
+ throw new UnauthorizedException('Token not yet valid');
1827
+ }
1828
+ }
1829
+
1830
+ // Check issued at (with max age)
1831
+ if (options.maxAge && payload.iat !== undefined) {
1832
+ if (now - payload.iat > options.maxAge) {
1833
+ throw new UnauthorizedException('Token exceeds maximum age');
1834
+ }
1835
+ }
1836
+
1837
+ // Check issuer
1838
+ if (options.issuer && payload.iss !== options.issuer) {
1839
+ throw new UnauthorizedException('Invalid token issuer');
1840
+ }
1841
+
1842
+ // Check audience
1843
+ if (options.audience) {
1844
+ const expectedAudiences = Array.isArray(options.audience) ? options.audience : [options.audience];
1845
+ const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
1846
+
1847
+ const hasValidAudience = expectedAudiences.some(aud => tokenAudiences.includes(aud));
1848
+ if (!hasValidAudience) {
1849
+ throw new UnauthorizedException('Invalid token audience');
1850
+ }
1851
+ }
1852
+ }
1853
+
1854
+ /**
1855
+ * Revoke a token by jti
1856
+ */
1857
+ revokeToken(jti: string): void {
1858
+ this.revokedTokens.add(jti);
1859
+ this.logger.log(\`Token revoked: jti=\${jti}\`);
1860
+ }
1861
+
1862
+ /**
1863
+ * Generate a secure, unique JWT ID
1864
+ */
1865
+ generateJti(): string {
1866
+ return crypto.randomUUID();
1867
+ }
1868
+
1869
+ /**
1870
+ * Clean up expired jti tracking
1871
+ * Call periodically to prevent memory bloat
1872
+ */
1873
+ cleanupExpiredJtis(): void {
1874
+ const now = Date.now();
1875
+ let cleaned = 0;
1876
+
1877
+ for (const [jti, expiry] of this.usedJtis.entries()) {
1878
+ if (now > expiry) {
1879
+ this.usedJtis.delete(jti);
1880
+ this.revokedTokens.delete(jti);
1881
+ cleaned++;
1882
+ }
1883
+ }
1884
+
1885
+ if (cleaned > 0) {
1886
+ this.logger.debug(\`Cleaned up \${cleaned} expired JWT IDs\`);
1887
+ }
1888
+ }
1889
+
1890
+ /**
1891
+ * Create secure token payload with required claims
1892
+ */
1893
+ createSecurePayload(options: {
1894
+ subject: string;
1895
+ issuer: string;
1896
+ audience: string | string[];
1897
+ expiresInSeconds?: number;
1898
+ data?: Record<string, any>;
1899
+ }): JwtPayload {
1900
+ const now = Math.floor(Date.now() / 1000);
1901
+ const expiresIn = options.expiresInSeconds || 900; // 15 minutes default
1902
+
1903
+ return {
1904
+ sub: options.subject,
1905
+ iss: options.issuer,
1906
+ aud: options.audience,
1907
+ iat: now,
1908
+ nbf: now,
1909
+ exp: now + expiresIn,
1910
+ jti: this.generateJti(),
1911
+ ...options.data,
1912
+ };
1913
+ }
1914
+
1915
+ private base64UrlDecode(str: string): string {
1916
+ // Add padding if needed
1917
+ let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
1918
+ while (base64.length % 4) {
1919
+ base64 += '=';
1920
+ }
1921
+ return Buffer.from(base64, 'base64').toString('utf-8');
1922
+ }
1923
+ }
1924
+
1925
+ /**
1926
+ * Secure JWT configuration recommendations
1927
+ */
1928
+ export const JwtSecurityRecommendations = {
1929
+ // Use RS256 or ES256 in production (asymmetric)
1930
+ RECOMMENDED_ALGORITHM: 'RS256',
1931
+
1932
+ // Short token lifetime (15 min for access, longer for refresh)
1933
+ ACCESS_TOKEN_LIFETIME: 15 * 60, // 15 minutes
1934
+ REFRESH_TOKEN_LIFETIME: 7 * 24 * 60 * 60, // 7 days
1935
+
1936
+ // Always include these claims
1937
+ REQUIRED_CLAIMS: ['iss', 'sub', 'aud', 'exp', 'iat', 'jti'],
1938
+
1939
+ // Key rotation recommendation
1940
+ KEY_ROTATION_DAYS: 90,
1941
+ };
1942
+ `;
1943
+ }
1944
+ function generateSecurityHeadersConfig() {
1945
+ return `/**
1946
+ * Comprehensive Security Headers Configuration
1947
+ * OWASP A05:2021 - Security Misconfiguration prevention
1948
+ */
1949
+
1950
+ import { Response } from 'express';
1951
+
1952
+ export interface SecurityHeadersOptions {
1953
+ isProduction?: boolean;
1954
+ contentSecurityPolicy?: ContentSecurityPolicyOptions;
1955
+ permissionsPolicy?: PermissionsPolicyOptions;
1956
+ reportUri?: string;
1957
+ }
1958
+
1959
+ export interface ContentSecurityPolicyOptions {
1960
+ defaultSrc?: string[];
1961
+ scriptSrc?: string[];
1962
+ styleSrc?: string[];
1963
+ imgSrc?: string[];
1964
+ fontSrc?: string[];
1965
+ connectSrc?: string[];
1966
+ frameSrc?: string[];
1967
+ objectSrc?: string[];
1968
+ mediaSrc?: string[];
1969
+ workerSrc?: string[];
1970
+ frameAncestors?: string[];
1971
+ formAction?: string[];
1972
+ baseUri?: string[];
1973
+ upgradeInsecureRequests?: boolean;
1974
+ blockAllMixedContent?: boolean;
1975
+ reportUri?: string;
1976
+ }
1977
+
1978
+ export interface PermissionsPolicyOptions {
1979
+ accelerometer?: string;
1980
+ camera?: string;
1981
+ geolocation?: string;
1982
+ gyroscope?: string;
1983
+ magnetometer?: string;
1984
+ microphone?: string;
1985
+ payment?: string;
1986
+ usb?: string;
1987
+ fullscreen?: string;
1988
+ [key: string]: string | undefined;
1989
+ }
1990
+
1991
+ /**
1992
+ * Set all security headers on a response
1993
+ */
1994
+ export function setSecurityHeaders(res: Response, options: SecurityHeadersOptions = {}): void {
1995
+ const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
1996
+
1997
+ // Clickjacking protection
1998
+ res.setHeader('X-Frame-Options', 'DENY');
1999
+
2000
+ // XSS protection (legacy but still useful)
2001
+ res.setHeader('X-XSS-Protection', '1; mode=block');
2002
+
2003
+ // Prevent MIME type sniffing
2004
+ res.setHeader('X-Content-Type-Options', 'nosniff');
2005
+
2006
+ // HTTPS strict transport
2007
+ if (isProduction) {
2008
+ // 2 years, include subdomains, allow preloading
2009
+ res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload');
2010
+ }
2011
+
2012
+ // Referrer policy - strict for security, relaxed for internal analytics
2013
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
2014
+
2015
+ // Content Security Policy
2016
+ const csp = buildContentSecurityPolicy(options.contentSecurityPolicy);
2017
+ if (isProduction) {
2018
+ res.setHeader('Content-Security-Policy', csp);
2019
+ } else {
2020
+ // Report-only in development for debugging
2021
+ res.setHeader('Content-Security-Policy-Report-Only', csp);
2022
+ }
2023
+
2024
+ // Permissions Policy (formerly Feature-Policy)
2025
+ res.setHeader('Permissions-Policy', buildPermissionsPolicy(options.permissionsPolicy));
2026
+
2027
+ // Cross-Origin policies
2028
+ res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
2029
+ res.setHeader('Cross-Origin-Resource-Policy', 'same-origin');
2030
+ res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
2031
+
2032
+ // Prevent Adobe products from cross-domain requests
2033
+ res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
2034
+
2035
+ // Prevent caching of sensitive data
2036
+ res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
2037
+ res.setHeader('Pragma', 'no-cache');
2038
+ res.setHeader('Expires', '0');
2039
+ res.setHeader('Surrogate-Control', 'no-store');
2040
+
2041
+ // Clear site data on logout (set this header on logout endpoint)
2042
+ // res.setHeader('Clear-Site-Data', '"cache", "cookies", "storage"');
2043
+ }
2044
+
2045
+ /**
2046
+ * Build Content Security Policy header value
2047
+ */
2048
+ function buildContentSecurityPolicy(options: ContentSecurityPolicyOptions = {}): string {
2049
+ const directives: string[] = [];
2050
+
2051
+ const addDirective = (name: string, values: string[] | undefined, defaultValue: string[]) => {
2052
+ const finalValues = values ?? defaultValue;
2053
+ if (finalValues.length > 0) {
2054
+ directives.push(\`\${name} \${finalValues.join(' ')}\`);
2055
+ }
2056
+ };
2057
+
2058
+ // Restrictive defaults
2059
+ addDirective('default-src', options.defaultSrc, ["'self'"]);
2060
+ addDirective('script-src', options.scriptSrc, ["'self'"]);
2061
+ addDirective('style-src', options.styleSrc, ["'self'"]);
2062
+ addDirective('img-src', options.imgSrc, ["'self'", 'data:', 'https:']);
2063
+ addDirective('font-src', options.fontSrc, ["'self'"]);
2064
+ addDirective('connect-src', options.connectSrc, ["'self'"]);
2065
+ addDirective('frame-src', options.frameSrc, ["'none'"]);
2066
+ addDirective('object-src', options.objectSrc, ["'none'"]);
2067
+ addDirective('media-src', options.mediaSrc, ["'self'"]);
2068
+ addDirective('worker-src', options.workerSrc, ["'self'"]);
2069
+ addDirective('frame-ancestors', options.frameAncestors, ["'none'"]);
2070
+ addDirective('form-action', options.formAction, ["'self'"]);
2071
+ addDirective('base-uri', options.baseUri, ["'self'"]);
2072
+
2073
+ if (options.upgradeInsecureRequests !== false) {
2074
+ directives.push('upgrade-insecure-requests');
2075
+ }
2076
+
2077
+ if (options.blockAllMixedContent !== false) {
2078
+ directives.push('block-all-mixed-content');
2079
+ }
2080
+
2081
+ if (options.reportUri) {
2082
+ directives.push(\`report-uri \${options.reportUri}\`);
2083
+ }
2084
+
2085
+ return directives.join('; ');
2086
+ }
2087
+
2088
+ /**
2089
+ * Build Permissions Policy header value
2090
+ */
2091
+ function buildPermissionsPolicy(options: PermissionsPolicyOptions = {}): string {
2092
+ const defaultPolicy: PermissionsPolicyOptions = {
2093
+ accelerometer: '()',
2094
+ camera: '()',
2095
+ geolocation: '()',
2096
+ gyroscope: '()',
2097
+ magnetometer: '()',
2098
+ microphone: '()',
2099
+ payment: '()',
2100
+ usb: '()',
2101
+ fullscreen: '(self)',
2102
+ ...options,
2103
+ };
2104
+
2105
+ return Object.entries(defaultPolicy)
2106
+ .filter(([_, value]) => value !== undefined)
2107
+ .map(([key, value]) => \`\${key}=\${value}\`)
2108
+ .join(', ');
2109
+ }
2110
+
2111
+ /**
2112
+ * Security headers for API responses (less restrictive CSP)
2113
+ */
2114
+ export function setApiSecurityHeaders(res: Response, options: SecurityHeadersOptions = {}): void {
2115
+ const isProduction = options.isProduction ?? process.env.NODE_ENV === 'production';
2116
+
2117
+ res.setHeader('X-Content-Type-Options', 'nosniff');
2118
+ res.setHeader('X-Frame-Options', 'DENY');
2119
+
2120
+ if (isProduction) {
2121
+ res.setHeader('Strict-Transport-Security', 'max-age=63072000; includeSubDomains');
2122
+ }
2123
+
2124
+ // API-specific: prevent caching by default
2125
+ res.setHeader('Cache-Control', 'no-store');
2126
+ res.setHeader('Pragma', 'no-cache');
2127
+
2128
+ // Prevent embedding
2129
+ res.setHeader('X-Permitted-Cross-Domain-Policies', 'none');
2130
+ }
2131
+
2132
+ /**
2133
+ * Headers to set on logout for session cleanup
2134
+ */
2135
+ export function setLogoutHeaders(res: Response): void {
2136
+ // Clear all site data
2137
+ res.setHeader('Clear-Site-Data', '"cache", "cookies", "storage"');
2138
+ }
2139
+
2140
+ /**
2141
+ * Security headers middleware factory
2142
+ */
2143
+ export function securityHeadersMiddleware(options: SecurityHeadersOptions = {}) {
2144
+ return (req: any, res: Response, next: () => void) => {
2145
+ setSecurityHeaders(res, options);
2146
+ next();
2147
+ };
2148
+ }
2149
+
2150
+ /**
2151
+ * API security headers middleware factory
2152
+ */
2153
+ export function apiSecurityHeadersMiddleware(options: SecurityHeadersOptions = {}) {
2154
+ return (req: any, res: Response, next: () => void) => {
2155
+ setApiSecurityHeaders(res, options);
2156
+ next();
2157
+ };
2158
+ }
2159
+ `;
2160
+ }
2161
+ function generateSecurityModule() {
2162
+ return `import { Module, Global, DynamicModule, MiddlewareConsumer, NestModule } from '@nestjs/common';
2163
+ import { APP_GUARD } from '@nestjs/core';
2164
+ import { EncryptionService } from './encryption.service';
2165
+ import { RbacService, AbacService } from './rbac.service';
2166
+ import { RolesGuard, PermissionsGuard } from './rbac.decorator';
2167
+ import { OwaspMiddleware, CsrfMiddleware } from './owasp.middleware';
2168
+ import { SecretVaultService } from './secret-vault.service';
2169
+ import { InputSanitizer } from './input-sanitizer';
2170
+ import { JwtSecurityService } from './jwt.security';
2171
+ import { securityHeadersMiddleware } from './security-headers.config';
2172
+
2173
+ export interface SecurityModuleOptions {
2174
+ enableRbac?: boolean;
2175
+ enableOwasp?: boolean;
2176
+ enableCsrf?: boolean;
2177
+ enableSecurityHeaders?: boolean;
2178
+ }
2179
+
2180
+ @Global()
2181
+ @Module({})
2182
+ export class SecurityModule implements NestModule {
2183
+ static options: SecurityModuleOptions = {};
2184
+
2185
+ static forRoot(options: SecurityModuleOptions = {}): DynamicModule {
2186
+ this.options = options;
2187
+
2188
+ const providers: any[] = [
2189
+ EncryptionService,
2190
+ RbacService,
2191
+ AbacService,
2192
+ SecretVaultService,
2193
+ InputSanitizer,
2194
+ JwtSecurityService,
2195
+ CsrfMiddleware,
2196
+ ];
2197
+
2198
+ if (options.enableRbac !== false) {
2199
+ providers.push(
2200
+ { provide: APP_GUARD, useClass: RolesGuard },
2201
+ { provide: APP_GUARD, useClass: PermissionsGuard },
2202
+ );
2203
+ }
2204
+
2205
+ return {
2206
+ module: SecurityModule,
2207
+ providers,
2208
+ exports: [
2209
+ EncryptionService,
2210
+ RbacService,
2211
+ AbacService,
2212
+ SecretVaultService,
2213
+ InputSanitizer,
2214
+ JwtSecurityService,
2215
+ ],
2216
+ };
2217
+ }
2218
+
2219
+ configure(consumer: MiddlewareConsumer) {
2220
+ if (SecurityModule.options.enableOwasp !== false) {
2221
+ consumer.apply(OwaspMiddleware).forRoutes('*');
2222
+ }
2223
+ if (SecurityModule.options.enableCsrf) {
2224
+ consumer.apply(CsrfMiddleware).forRoutes('*');
2225
+ }
2226
+ if (SecurityModule.options.enableSecurityHeaders !== false) {
2227
+ consumer.apply(securityHeadersMiddleware()).forRoutes('*');
2228
+ }
2229
+ }
2230
+ }
2231
+ `;
2232
+ }
2233
+ //# sourceMappingURL=security-patterns.js.map