servcraft 0.1.0 → 0.1.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 (216) hide show
  1. package/.claude/settings.local.json +29 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/README.md +1070 -1
  9. package/dist/cli/index.cjs +2026 -2168
  10. package/dist/cli/index.cjs.map +1 -1
  11. package/dist/cli/index.js +2026 -2168
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/index.cjs +595 -616
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +114 -52
  16. package/dist/index.d.ts +114 -52
  17. package/dist/index.js +595 -616
  18. package/dist/index.js.map +1 -1
  19. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  20. package/docs/DATABASE_MULTI_ORM.md +399 -0
  21. package/docs/PHASE1_BREAKDOWN.md +346 -0
  22. package/docs/PROGRESS.md +550 -0
  23. package/docs/modules/ANALYTICS.md +226 -0
  24. package/docs/modules/API-VERSIONING.md +252 -0
  25. package/docs/modules/AUDIT.md +192 -0
  26. package/docs/modules/AUTH.md +431 -0
  27. package/docs/modules/CACHE.md +346 -0
  28. package/docs/modules/EMAIL.md +254 -0
  29. package/docs/modules/FEATURE-FLAG.md +291 -0
  30. package/docs/modules/I18N.md +294 -0
  31. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  32. package/docs/modules/MFA.md +266 -0
  33. package/docs/modules/NOTIFICATION.md +311 -0
  34. package/docs/modules/OAUTH.md +237 -0
  35. package/docs/modules/PAYMENT.md +804 -0
  36. package/docs/modules/QUEUE.md +540 -0
  37. package/docs/modules/RATE-LIMIT.md +339 -0
  38. package/docs/modules/SEARCH.md +288 -0
  39. package/docs/modules/SECURITY.md +327 -0
  40. package/docs/modules/SESSION.md +382 -0
  41. package/docs/modules/SWAGGER.md +305 -0
  42. package/docs/modules/UPLOAD.md +296 -0
  43. package/docs/modules/USER.md +505 -0
  44. package/docs/modules/VALIDATION.md +294 -0
  45. package/docs/modules/WEBHOOK.md +270 -0
  46. package/docs/modules/WEBSOCKET.md +691 -0
  47. package/package.json +53 -38
  48. package/prisma/schema.prisma +395 -1
  49. package/src/cli/commands/add-module.ts +520 -87
  50. package/src/cli/commands/db.ts +3 -4
  51. package/src/cli/commands/docs.ts +256 -6
  52. package/src/cli/commands/generate.ts +12 -19
  53. package/src/cli/commands/init.ts +384 -214
  54. package/src/cli/index.ts +0 -4
  55. package/src/cli/templates/repository.ts +6 -1
  56. package/src/cli/templates/routes.ts +6 -21
  57. package/src/cli/utils/docs-generator.ts +6 -7
  58. package/src/cli/utils/env-manager.ts +717 -0
  59. package/src/cli/utils/field-parser.ts +16 -7
  60. package/src/cli/utils/interactive-prompt.ts +223 -0
  61. package/src/cli/utils/template-manager.ts +346 -0
  62. package/src/config/database.config.ts +183 -0
  63. package/src/config/env.ts +0 -10
  64. package/src/config/index.ts +0 -14
  65. package/src/core/server.ts +1 -1
  66. package/src/database/adapters/mongoose.adapter.ts +132 -0
  67. package/src/database/adapters/prisma.adapter.ts +118 -0
  68. package/src/database/connection.ts +190 -0
  69. package/src/database/interfaces/database.interface.ts +85 -0
  70. package/src/database/interfaces/index.ts +7 -0
  71. package/src/database/interfaces/repository.interface.ts +129 -0
  72. package/src/database/models/mongoose/index.ts +7 -0
  73. package/src/database/models/mongoose/payment.schema.ts +347 -0
  74. package/src/database/models/mongoose/user.schema.ts +154 -0
  75. package/src/database/prisma.ts +1 -4
  76. package/src/database/redis.ts +101 -0
  77. package/src/database/repositories/mongoose/index.ts +7 -0
  78. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  79. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  80. package/src/database/seed.ts +6 -1
  81. package/src/index.ts +9 -20
  82. package/src/middleware/security.ts +2 -6
  83. package/src/modules/analytics/analytics.routes.ts +80 -0
  84. package/src/modules/analytics/analytics.service.ts +364 -0
  85. package/src/modules/analytics/index.ts +18 -0
  86. package/src/modules/analytics/types.ts +180 -0
  87. package/src/modules/api-versioning/index.ts +15 -0
  88. package/src/modules/api-versioning/types.ts +86 -0
  89. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  90. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  91. package/src/modules/api-versioning/versioning.service.ts +189 -0
  92. package/src/modules/audit/audit.repository.ts +206 -0
  93. package/src/modules/audit/audit.service.ts +27 -59
  94. package/src/modules/auth/auth.controller.ts +2 -2
  95. package/src/modules/auth/auth.middleware.ts +3 -9
  96. package/src/modules/auth/auth.routes.ts +10 -107
  97. package/src/modules/auth/auth.service.ts +126 -23
  98. package/src/modules/auth/index.ts +3 -4
  99. package/src/modules/cache/cache.service.ts +367 -0
  100. package/src/modules/cache/index.ts +10 -0
  101. package/src/modules/cache/types.ts +44 -0
  102. package/src/modules/email/email.service.ts +3 -10
  103. package/src/modules/email/templates.ts +2 -8
  104. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  105. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  106. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  107. package/src/modules/feature-flag/index.ts +20 -0
  108. package/src/modules/feature-flag/types.ts +192 -0
  109. package/src/modules/i18n/i18n.middleware.ts +186 -0
  110. package/src/modules/i18n/i18n.routes.ts +191 -0
  111. package/src/modules/i18n/i18n.service.ts +456 -0
  112. package/src/modules/i18n/index.ts +18 -0
  113. package/src/modules/i18n/types.ts +118 -0
  114. package/src/modules/media-processing/index.ts +17 -0
  115. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  116. package/src/modules/media-processing/media-processing.service.ts +245 -0
  117. package/src/modules/media-processing/types.ts +156 -0
  118. package/src/modules/mfa/index.ts +20 -0
  119. package/src/modules/mfa/mfa.repository.ts +206 -0
  120. package/src/modules/mfa/mfa.routes.ts +595 -0
  121. package/src/modules/mfa/mfa.service.ts +572 -0
  122. package/src/modules/mfa/totp.ts +150 -0
  123. package/src/modules/mfa/types.ts +57 -0
  124. package/src/modules/notification/index.ts +20 -0
  125. package/src/modules/notification/notification.repository.ts +356 -0
  126. package/src/modules/notification/notification.service.ts +483 -0
  127. package/src/modules/notification/types.ts +119 -0
  128. package/src/modules/oauth/index.ts +20 -0
  129. package/src/modules/oauth/oauth.repository.ts +219 -0
  130. package/src/modules/oauth/oauth.routes.ts +446 -0
  131. package/src/modules/oauth/oauth.service.ts +293 -0
  132. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  133. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  134. package/src/modules/oauth/providers/github.provider.ts +248 -0
  135. package/src/modules/oauth/providers/google.provider.ts +189 -0
  136. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  137. package/src/modules/oauth/types.ts +94 -0
  138. package/src/modules/payment/index.ts +19 -0
  139. package/src/modules/payment/payment.repository.ts +733 -0
  140. package/src/modules/payment/payment.routes.ts +390 -0
  141. package/src/modules/payment/payment.service.ts +354 -0
  142. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  143. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  144. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  145. package/src/modules/payment/types.ts +140 -0
  146. package/src/modules/queue/cron.ts +438 -0
  147. package/src/modules/queue/index.ts +87 -0
  148. package/src/modules/queue/queue.routes.ts +600 -0
  149. package/src/modules/queue/queue.service.ts +842 -0
  150. package/src/modules/queue/types.ts +222 -0
  151. package/src/modules/queue/workers.ts +366 -0
  152. package/src/modules/rate-limit/index.ts +59 -0
  153. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  154. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  155. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  156. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  157. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  158. package/src/modules/rate-limit/types.ts +153 -0
  159. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  160. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  161. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  162. package/src/modules/search/index.ts +21 -0
  163. package/src/modules/search/search.service.ts +234 -0
  164. package/src/modules/search/types.ts +214 -0
  165. package/src/modules/security/index.ts +40 -0
  166. package/src/modules/security/sanitize.ts +223 -0
  167. package/src/modules/security/security-audit.service.ts +388 -0
  168. package/src/modules/security/security.middleware.ts +398 -0
  169. package/src/modules/session/index.ts +3 -0
  170. package/src/modules/session/session.repository.ts +159 -0
  171. package/src/modules/session/session.service.ts +340 -0
  172. package/src/modules/session/types.ts +38 -0
  173. package/src/modules/swagger/index.ts +7 -1
  174. package/src/modules/swagger/schema-builder.ts +16 -4
  175. package/src/modules/swagger/swagger.service.ts +9 -10
  176. package/src/modules/swagger/types.ts +0 -2
  177. package/src/modules/upload/index.ts +14 -0
  178. package/src/modules/upload/types.ts +83 -0
  179. package/src/modules/upload/upload.repository.ts +199 -0
  180. package/src/modules/upload/upload.routes.ts +311 -0
  181. package/src/modules/upload/upload.service.ts +448 -0
  182. package/src/modules/user/index.ts +3 -3
  183. package/src/modules/user/user.controller.ts +15 -9
  184. package/src/modules/user/user.repository.ts +237 -113
  185. package/src/modules/user/user.routes.ts +39 -164
  186. package/src/modules/user/user.service.ts +4 -3
  187. package/src/modules/validation/validator.ts +12 -17
  188. package/src/modules/webhook/index.ts +91 -0
  189. package/src/modules/webhook/retry.ts +196 -0
  190. package/src/modules/webhook/signature.ts +135 -0
  191. package/src/modules/webhook/types.ts +181 -0
  192. package/src/modules/webhook/webhook.repository.ts +358 -0
  193. package/src/modules/webhook/webhook.routes.ts +442 -0
  194. package/src/modules/webhook/webhook.service.ts +457 -0
  195. package/src/modules/websocket/features.ts +504 -0
  196. package/src/modules/websocket/index.ts +106 -0
  197. package/src/modules/websocket/middlewares.ts +298 -0
  198. package/src/modules/websocket/types.ts +181 -0
  199. package/src/modules/websocket/websocket.service.ts +692 -0
  200. package/src/utils/errors.ts +7 -0
  201. package/src/utils/pagination.ts +4 -1
  202. package/tests/helpers/db-check.ts +79 -0
  203. package/tests/integration/auth-redis.test.ts +94 -0
  204. package/tests/integration/cache-redis.test.ts +387 -0
  205. package/tests/integration/mongoose-repositories.test.ts +410 -0
  206. package/tests/integration/payment-prisma.test.ts +637 -0
  207. package/tests/integration/queue-bullmq.test.ts +417 -0
  208. package/tests/integration/user-prisma.test.ts +441 -0
  209. package/tests/integration/websocket-socketio.test.ts +552 -0
  210. package/tests/setup.ts +11 -9
  211. package/vitest.config.ts +3 -8
  212. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  213. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  216. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,214 @@
1
+ export type SearchEngineType = 'elasticsearch' | 'meilisearch' | 'memory';
2
+
3
+ export interface SearchConfig {
4
+ /** Search engine to use */
5
+ engine?: SearchEngineType;
6
+ /** Elasticsearch config */
7
+ elasticsearch?: {
8
+ node: string;
9
+ auth?: {
10
+ username: string;
11
+ password: string;
12
+ };
13
+ apiKey?: string;
14
+ };
15
+ /** Meilisearch config */
16
+ meilisearch?: {
17
+ host: string;
18
+ apiKey?: string;
19
+ };
20
+ /** Default index settings */
21
+ defaultSettings?: IndexSettings;
22
+ }
23
+
24
+ export interface IndexSettings {
25
+ /** Searchable attributes */
26
+ searchableAttributes?: string[];
27
+ /** Attributes to display in results */
28
+ displayedAttributes?: string[];
29
+ /** Filterable attributes */
30
+ filterableAttributes?: string[];
31
+ /** Sortable attributes */
32
+ sortableAttributes?: string[];
33
+ /** Ranking rules */
34
+ rankingRules?: string[];
35
+ /** Stop words */
36
+ stopWords?: string[];
37
+ /** Synonyms */
38
+ synonyms?: Record<string, string[]>;
39
+ /** Number of replicas */
40
+ numberOfReplicas?: number;
41
+ /** Number of shards */
42
+ numberOfShards?: number;
43
+ }
44
+
45
+ export interface SearchDocument {
46
+ /** Document unique ID */
47
+ id: string;
48
+ /** Document data */
49
+ [key: string]: unknown;
50
+ }
51
+
52
+ export interface SearchQuery {
53
+ /** Search query string */
54
+ query: string;
55
+ /** Filters to apply */
56
+ filters?: SearchFilter[];
57
+ /** Fields to search in */
58
+ fields?: string[];
59
+ /** Limit results */
60
+ limit?: number;
61
+ /** Offset for pagination */
62
+ offset?: number;
63
+ /** Sort options */
64
+ sort?: SearchSort[];
65
+ /** Facets to compute */
66
+ facets?: string[];
67
+ /** Highlight matches */
68
+ highlight?: boolean;
69
+ /** Fuzzy search threshold (0-1) */
70
+ fuzziness?: number;
71
+ }
72
+
73
+ export interface SearchFilter {
74
+ /** Field to filter on */
75
+ field: string;
76
+ /** Filter operator */
77
+ operator: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'nin' | 'contains' | 'exists';
78
+ /** Filter value */
79
+ value: unknown;
80
+ }
81
+
82
+ export interface SearchSort {
83
+ /** Field to sort by */
84
+ field: string;
85
+ /** Sort direction */
86
+ direction: 'asc' | 'desc';
87
+ }
88
+
89
+ export interface SearchResult<T = SearchDocument> {
90
+ /** Matched documents */
91
+ hits: SearchHit<T>[];
92
+ /** Total number of matches */
93
+ total: number;
94
+ /** Query processing time (ms) */
95
+ processingTime: number;
96
+ /** Current page */
97
+ page?: number;
98
+ /** Total pages */
99
+ totalPages?: number;
100
+ /** Facet results */
101
+ facets?: Record<string, FacetResult>;
102
+ }
103
+
104
+ export interface SearchHit<T = SearchDocument> {
105
+ /** Document */
106
+ document: T;
107
+ /** Relevance score */
108
+ score?: number;
109
+ /** Highlighted fields */
110
+ highlights?: Record<string, string[]>;
111
+ /** Matched terms */
112
+ matchedTerms?: string[];
113
+ }
114
+
115
+ export interface FacetResult {
116
+ /** Facet values and counts */
117
+ values: Array<{
118
+ value: string;
119
+ count: number;
120
+ }>;
121
+ /** Total unique values */
122
+ total: number;
123
+ }
124
+
125
+ export interface AutocompleteResult {
126
+ /** Suggestions */
127
+ suggestions: string[];
128
+ /** Documents matching suggestions */
129
+ documents?: SearchDocument[];
130
+ /** Processing time (ms) */
131
+ processingTime: number;
132
+ }
133
+
134
+ export interface IndexStats {
135
+ /** Index name */
136
+ name: string;
137
+ /** Total documents */
138
+ documentCount: number;
139
+ /** Index size in bytes */
140
+ size: number;
141
+ /** Is indexing in progress */
142
+ isIndexing: boolean;
143
+ /** Last update timestamp */
144
+ lastUpdate?: Date;
145
+ /** Health status */
146
+ health?: 'green' | 'yellow' | 'red';
147
+ }
148
+
149
+ export interface BulkIndexOperation {
150
+ /** Operation type */
151
+ operation: 'index' | 'update' | 'delete';
152
+ /** Document ID */
153
+ id: string;
154
+ /** Document data (for index/update) */
155
+ document?: SearchDocument;
156
+ }
157
+
158
+ export interface BulkIndexResult {
159
+ /** Successful operations */
160
+ success: number;
161
+ /** Failed operations */
162
+ failed: number;
163
+ /** Error details */
164
+ errors?: Array<{
165
+ id: string;
166
+ error: string;
167
+ }>;
168
+ /** Processing time (ms) */
169
+ processingTime: number;
170
+ }
171
+
172
+ export interface SearchSuggestion {
173
+ /** Suggestion text */
174
+ text: string;
175
+ /** Score/relevance */
176
+ score: number;
177
+ /** Highlighted version */
178
+ highlighted?: string;
179
+ }
180
+
181
+ export interface SearchEngine {
182
+ /** Index a document */
183
+ index(indexName: string, id: string, document: SearchDocument): Promise<void>;
184
+
185
+ /** Bulk index documents */
186
+ bulkIndex(indexName: string, operations: BulkIndexOperation[]): Promise<BulkIndexResult>;
187
+
188
+ /** Search documents */
189
+ search<T = SearchDocument>(indexName: string, query: SearchQuery): Promise<SearchResult<T>>;
190
+
191
+ /** Delete document */
192
+ delete(indexName: string, id: string): Promise<void>;
193
+
194
+ /** Update document */
195
+ update(indexName: string, id: string, document: Partial<SearchDocument>): Promise<void>;
196
+
197
+ /** Get document by ID */
198
+ get(indexName: string, id: string): Promise<SearchDocument | null>;
199
+
200
+ /** Create index */
201
+ createIndex(indexName: string, settings?: IndexSettings): Promise<void>;
202
+
203
+ /** Delete index */
204
+ deleteIndex(indexName: string): Promise<void>;
205
+
206
+ /** Update index settings */
207
+ updateSettings(indexName: string, settings: IndexSettings): Promise<void>;
208
+
209
+ /** Get index stats */
210
+ getStats(indexName: string): Promise<IndexStats>;
211
+
212
+ /** Autocomplete */
213
+ autocomplete(indexName: string, query: string, limit?: number): Promise<AutocompleteResult>;
214
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Security Module
3
+ * Comprehensive security features for the application
4
+ */
5
+
6
+ // Sanitization
7
+ export {
8
+ escapeHtml,
9
+ stripDangerousHtml,
10
+ sanitizeString,
11
+ sanitizeObject,
12
+ sanitizeSqlPatterns,
13
+ sanitizeUrl,
14
+ sanitizeFilename,
15
+ sanitizeForJson,
16
+ containsDangerousContent,
17
+ type SanitizeOptions,
18
+ type SanitizeMiddlewareOptions,
19
+ } from './sanitize.js';
20
+
21
+ // Middleware
22
+ export {
23
+ sanitizeInput,
24
+ csrfProtection,
25
+ generateCsrfToken,
26
+ hppProtection,
27
+ securityHeaders,
28
+ requestSizeLimit,
29
+ suspiciousActivityDetection,
30
+ registerSecurityMiddlewares,
31
+ } from './security.middleware.js';
32
+
33
+ // Security Audit
34
+ export {
35
+ SecurityAuditService,
36
+ getSecurityAuditService,
37
+ type SecurityEvent,
38
+ type SecurityEventType,
39
+ type SecurityEventInput,
40
+ } from './security-audit.service.js';
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Input Sanitization Module
3
+ * Prevents XSS attacks by sanitizing user input
4
+ */
5
+
6
+ // HTML entities to escape
7
+ const HTML_ENTITIES: Record<string, string> = {
8
+ '&': '&amp;',
9
+ '<': '&lt;',
10
+ '>': '&gt;',
11
+ '"': '&quot;',
12
+ "'": '&#x27;',
13
+ '/': '&#x2F;',
14
+ '`': '&#x60;',
15
+ '=': '&#x3D;',
16
+ };
17
+
18
+ // Regex patterns for dangerous content
19
+ const SCRIPT_PATTERN = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
20
+ const EVENT_HANDLER_PATTERN = /\s*on\w+\s*=\s*(['"])[^'"]*\1/gi;
21
+ const JAVASCRIPT_URL_PATTERN = /javascript\s*:/gi;
22
+ const DATA_URL_PATTERN = /data\s*:/gi;
23
+ const EXPRESSION_PATTERN = /expression\s*\(/gi;
24
+ const VBSCRIPT_PATTERN = /vbscript\s*:/gi;
25
+
26
+ /**
27
+ * Escape HTML special characters
28
+ */
29
+ export function escapeHtml(str: string): string {
30
+ if (typeof str !== 'string') return str;
31
+ return str.replace(/[&<>"'`=/]/g, (char) => HTML_ENTITIES[char] || char);
32
+ }
33
+
34
+ /**
35
+ * Remove dangerous HTML tags and attributes
36
+ */
37
+ export function stripDangerousHtml(str: string): string {
38
+ if (typeof str !== 'string') return str;
39
+
40
+ return str
41
+ .replace(SCRIPT_PATTERN, '')
42
+ .replace(EVENT_HANDLER_PATTERN, '')
43
+ .replace(JAVASCRIPT_URL_PATTERN, '')
44
+ .replace(DATA_URL_PATTERN, '')
45
+ .replace(EXPRESSION_PATTERN, '')
46
+ .replace(VBSCRIPT_PATTERN, '');
47
+ }
48
+
49
+ /**
50
+ * Sanitize a string for safe output
51
+ */
52
+ export function sanitizeString(str: string, options: SanitizeOptions = {}): string {
53
+ if (typeof str !== 'string') return str;
54
+
55
+ const { escapeHtmlChars = true, stripScripts = true, trim = true, maxLength } = options;
56
+
57
+ let result = str;
58
+
59
+ if (trim) {
60
+ result = result.trim();
61
+ }
62
+
63
+ if (stripScripts) {
64
+ result = stripDangerousHtml(result);
65
+ }
66
+
67
+ if (escapeHtmlChars) {
68
+ result = escapeHtml(result);
69
+ }
70
+
71
+ if (maxLength && result.length > maxLength) {
72
+ result = result.substring(0, maxLength);
73
+ }
74
+
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * Sanitize an object recursively
80
+ */
81
+ export function sanitizeObject<T extends Record<string, unknown>>(
82
+ obj: T,
83
+ options: SanitizeOptions = {}
84
+ ): T {
85
+ if (obj === null || typeof obj !== 'object') {
86
+ return obj;
87
+ }
88
+
89
+ if (Array.isArray(obj)) {
90
+ return obj.map((item) => {
91
+ if (typeof item === 'string') {
92
+ return sanitizeString(item, options);
93
+ }
94
+ if (typeof item === 'object' && item !== null) {
95
+ return sanitizeObject(item as Record<string, unknown>, options);
96
+ }
97
+ return item;
98
+ }) as unknown as T;
99
+ }
100
+
101
+ const sanitized: Record<string, unknown> = {};
102
+
103
+ for (const [key, value] of Object.entries(obj)) {
104
+ // Sanitize the key as well (prevent prototype pollution)
105
+ const safeKey = sanitizeString(key, { escapeHtmlChars: false, stripScripts: true });
106
+
107
+ // Skip dangerous keys
108
+ if (safeKey === '__proto__' || safeKey === 'constructor' || safeKey === 'prototype') {
109
+ continue;
110
+ }
111
+
112
+ if (typeof value === 'string') {
113
+ sanitized[safeKey] = sanitizeString(value, options);
114
+ } else if (typeof value === 'object' && value !== null) {
115
+ sanitized[safeKey] = sanitizeObject(value as Record<string, unknown>, options);
116
+ } else {
117
+ sanitized[safeKey] = value;
118
+ }
119
+ }
120
+
121
+ return sanitized as T;
122
+ }
123
+
124
+ /**
125
+ * Sanitize SQL-like patterns (additional protection layer)
126
+ * Note: Prisma already handles SQL injection, this is defense-in-depth
127
+ */
128
+ export function sanitizeSqlPatterns(str: string): string {
129
+ if (typeof str !== 'string') return str;
130
+
131
+ return str
132
+ .replace(/--/g, '')
133
+ .replace(/;/g, '')
134
+ .replace(/\/\*/g, '')
135
+ .replace(/\*\//g, '')
136
+ .replace(/xp_/gi, '')
137
+ .replace(/union\s+select/gi, '')
138
+ .replace(/insert\s+into/gi, '')
139
+ .replace(/drop\s+table/gi, '')
140
+ .replace(/delete\s+from/gi, '');
141
+ }
142
+
143
+ /**
144
+ * Sanitize for safe URL usage
145
+ */
146
+ export function sanitizeUrl(url: string): string {
147
+ if (typeof url !== 'string') return '';
148
+
149
+ const sanitized = url.trim().toLowerCase();
150
+
151
+ // Block dangerous protocols
152
+ if (
153
+ sanitized.startsWith('javascript:') ||
154
+ sanitized.startsWith('data:') ||
155
+ sanitized.startsWith('vbscript:')
156
+ ) {
157
+ return '';
158
+ }
159
+
160
+ return url;
161
+ }
162
+
163
+ /**
164
+ * Sanitize filename for safe storage
165
+ */
166
+ export function sanitizeFilename(filename: string): string {
167
+ if (typeof filename !== 'string') return '';
168
+
169
+ return filename
170
+ .replace(/[^a-zA-Z0-9._-]/g, '_') // Replace unsafe chars with underscore
171
+ .replace(/\.{2,}/g, '.') // Remove multiple dots
172
+ .replace(/^\.+|\.+$/g, '') // Remove leading/trailing dots
173
+ .substring(0, 255); // Limit length
174
+ }
175
+
176
+ /**
177
+ * Sanitize for JSON output (prevent JSON injection)
178
+ */
179
+ export function sanitizeForJson(str: string): string {
180
+ if (typeof str !== 'string') return str;
181
+
182
+ return str
183
+ .replace(/\\/g, '\\\\')
184
+ .replace(/"/g, '\\"')
185
+ .replace(/\n/g, '\\n')
186
+ .replace(/\r/g, '\\r')
187
+ .replace(/\t/g, '\\t');
188
+ }
189
+
190
+ /**
191
+ * Check if a string contains potentially dangerous content
192
+ */
193
+ export function containsDangerousContent(str: string): boolean {
194
+ if (typeof str !== 'string') return false;
195
+
196
+ return (
197
+ SCRIPT_PATTERN.test(str) ||
198
+ EVENT_HANDLER_PATTERN.test(str) ||
199
+ JAVASCRIPT_URL_PATTERN.test(str) ||
200
+ /<iframe/i.test(str) ||
201
+ /<object/i.test(str) ||
202
+ /<embed/i.test(str) ||
203
+ /<form/i.test(str)
204
+ );
205
+ }
206
+
207
+ export interface SanitizeOptions {
208
+ /** Escape HTML characters (default: true) */
209
+ escapeHtmlChars?: boolean;
210
+ /** Strip script tags and event handlers (default: true) */
211
+ stripScripts?: boolean;
212
+ /** Trim whitespace (default: true) */
213
+ trim?: boolean;
214
+ /** Maximum length of string */
215
+ maxLength?: number;
216
+ }
217
+
218
+ export interface SanitizeMiddlewareOptions extends SanitizeOptions {
219
+ /** Fields to skip sanitization */
220
+ skipFields?: string[];
221
+ /** Only sanitize these fields */
222
+ onlyFields?: string[];
223
+ }