@umituz/web-design-system 1.0.4 → 1.1.0
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.
- package/README.md +181 -0
- package/package.json +11 -3
- package/src/index.ts +3 -0
- package/src/infrastructure/error/ErrorBoundary.tsx +258 -0
- package/src/infrastructure/error/ErrorDisplay.tsx +352 -0
- package/src/infrastructure/error/SuspenseWrapper.tsx +129 -0
- package/src/infrastructure/error/index.ts +5 -0
- package/src/infrastructure/performance/index.ts +6 -0
- package/src/infrastructure/performance/useLazyLoading.ts +342 -0
- package/src/infrastructure/performance/useMemoryOptimization.ts +293 -0
- package/src/infrastructure/performance/usePerformanceMonitor.ts +158 -0
- package/src/infrastructure/security/index.ts +10 -0
- package/src/infrastructure/security/security-config.ts +171 -0
- package/src/infrastructure/security/useFormValidation.ts +216 -0
- package/src/infrastructure/security/validation.ts +242 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
export interface ValidationResult {
|
|
2
|
+
isValid: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ValidationConfig {
|
|
7
|
+
allowHtml?: boolean;
|
|
8
|
+
stripTags?: boolean;
|
|
9
|
+
allowedTags?: string[];
|
|
10
|
+
allowedAttributes?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Input validation with XSS protection
|
|
15
|
+
* Basic sanitization without external dependencies
|
|
16
|
+
*/
|
|
17
|
+
export const validateInput = (
|
|
18
|
+
input: string,
|
|
19
|
+
maxLength: number = 1000,
|
|
20
|
+
minLength: number = 0,
|
|
21
|
+
config?: ValidationConfig
|
|
22
|
+
): ValidationResult => {
|
|
23
|
+
const trimmed = input.trim();
|
|
24
|
+
|
|
25
|
+
// Required field validation
|
|
26
|
+
if (minLength > 0 && !trimmed) {
|
|
27
|
+
return { isValid: false, error: "This field is required" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Length validation
|
|
31
|
+
if (trimmed.length > maxLength) {
|
|
32
|
+
return { isValid: false, error: `Must be less than ${maxLength} characters` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (trimmed.length < minLength) {
|
|
36
|
+
return { isValid: false, error: `Must be at least ${minLength} characters` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// HTML tag check for non-HTML content
|
|
40
|
+
if (!config?.allowHtml) {
|
|
41
|
+
if (/<[^>]*>/.test(trimmed)) {
|
|
42
|
+
return { isValid: false, error: "HTML tags are not allowed" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check for suspicious patterns
|
|
46
|
+
const suspiciousPatterns = [
|
|
47
|
+
/javascript:/gi,
|
|
48
|
+
/vbscript:/gi,
|
|
49
|
+
/data:text\/html/gi,
|
|
50
|
+
/data:application\/javascript/gi,
|
|
51
|
+
/on\w+\s*=/gi,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const pattern of suspiciousPatterns) {
|
|
55
|
+
if (pattern.test(trimmed)) {
|
|
56
|
+
return { isValid: false, error: "Potentially unsafe content detected" };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { isValid: true };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Email validation with basic security checks
|
|
66
|
+
*/
|
|
67
|
+
export const validateEmail = (email: string): ValidationResult => {
|
|
68
|
+
const trimmed = email.trim();
|
|
69
|
+
|
|
70
|
+
if (!trimmed) {
|
|
71
|
+
return { isValid: false, error: "Email is required" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for HTML/script content
|
|
75
|
+
if (/<[^>]*>/.test(trimmed)) {
|
|
76
|
+
return { isValid: false, error: "Invalid characters in email address" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Email regex
|
|
80
|
+
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
81
|
+
|
|
82
|
+
if (!emailRegex.test(trimmed)) {
|
|
83
|
+
return { isValid: false, error: "Please enter a valid email address" };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for suspicious patterns
|
|
87
|
+
const suspiciousPatterns = [
|
|
88
|
+
/javascript:/gi,
|
|
89
|
+
/data:/gi,
|
|
90
|
+
/<[^>]*>/gi
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const pattern of suspiciousPatterns) {
|
|
94
|
+
if (pattern.test(trimmed)) {
|
|
95
|
+
return { isValid: false, error: "Invalid email format" };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { isValid: true };
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* URL validation with security checks
|
|
104
|
+
*/
|
|
105
|
+
export const validateUrl = (url: string): ValidationResult => {
|
|
106
|
+
const trimmed = url.trim();
|
|
107
|
+
|
|
108
|
+
if (!trimmed) {
|
|
109
|
+
return { isValid: false, error: "URL is required" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check for HTML/script content
|
|
113
|
+
if (/<[^>]*>/.test(trimmed)) {
|
|
114
|
+
return { isValid: false, error: "Invalid characters in URL" };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const urlObj = new URL(trimmed);
|
|
119
|
+
|
|
120
|
+
// Only allow safe protocols
|
|
121
|
+
const allowedProtocols = ['http:', 'https:', 'ftp:', 'ftps:'];
|
|
122
|
+
if (!allowedProtocols.includes(urlObj.protocol)) {
|
|
123
|
+
return { isValid: false, error: "Only HTTP, HTTPS, and FTP URLs are allowed" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { isValid: true };
|
|
127
|
+
} catch {
|
|
128
|
+
return { isValid: false, error: "Please enter a valid URL" };
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Required field validation
|
|
134
|
+
*/
|
|
135
|
+
export const validateRequired = (value: string): ValidationResult => {
|
|
136
|
+
const trimmed = value.trim();
|
|
137
|
+
|
|
138
|
+
if (!trimmed) {
|
|
139
|
+
return { isValid: false, error: "This field is required" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { isValid: true };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Basic input sanitization
|
|
147
|
+
* Removes potentially dangerous content
|
|
148
|
+
*/
|
|
149
|
+
export const sanitizeInput = (
|
|
150
|
+
input: string,
|
|
151
|
+
config?: ValidationConfig
|
|
152
|
+
): string => {
|
|
153
|
+
if (!input) return '';
|
|
154
|
+
|
|
155
|
+
let sanitized = input.trim();
|
|
156
|
+
|
|
157
|
+
// Remove dangerous content for non-HTML input
|
|
158
|
+
if (!config?.allowHtml) {
|
|
159
|
+
sanitized = sanitized
|
|
160
|
+
.replace(/javascript:/gi, '')
|
|
161
|
+
.replace(/vbscript:/gi, '')
|
|
162
|
+
.replace(/data:text\/html/gi, '')
|
|
163
|
+
.replace(/data:application\/javascript/gi, '')
|
|
164
|
+
.replace(/on\w+\s*=/gi, '')
|
|
165
|
+
.replace(/<[^>]*>/g, '');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Limit length
|
|
169
|
+
return sanitized.slice(0, config?.allowHtml ? 10000 : 1000);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate and sanitize file names
|
|
174
|
+
* Prevents directory traversal and other file-based attacks
|
|
175
|
+
*/
|
|
176
|
+
export const validateFileName = (fileName: string): ValidationResult => {
|
|
177
|
+
const trimmed = fileName.trim();
|
|
178
|
+
|
|
179
|
+
if (!trimmed) {
|
|
180
|
+
return { isValid: false, error: "File name is required" };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check for dangerous patterns
|
|
184
|
+
const dangerousPatterns = [
|
|
185
|
+
/\.\./g, // Directory traversal
|
|
186
|
+
/[<>:"|?*]/g, // Invalid file name characters
|
|
187
|
+
/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i, // Windows reserved names
|
|
188
|
+
/^\./g, // Hidden files starting with dot
|
|
189
|
+
/\.$/, // Files ending with dot
|
|
190
|
+
/\s+$/ // Trailing whitespace
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
for (const pattern of dangerousPatterns) {
|
|
194
|
+
if (pattern.test(trimmed)) {
|
|
195
|
+
return { isValid: false, error: "Invalid file name format" };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Length check
|
|
200
|
+
if (trimmed.length > 255) {
|
|
201
|
+
return { isValid: false, error: "File name too long (max 255 characters)" };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { isValid: true };
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Content Security Policy helper
|
|
209
|
+
* Validates that content meets basic CSP requirements
|
|
210
|
+
*/
|
|
211
|
+
export const validateCSPCompliance = (content: string): ValidationResult => {
|
|
212
|
+
const violations = [];
|
|
213
|
+
|
|
214
|
+
// Check for inline scripts
|
|
215
|
+
if (/<script[^>]*>/.test(content)) {
|
|
216
|
+
violations.push("Inline scripts not allowed");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Check for inline styles
|
|
220
|
+
if (/style\s*=/.test(content)) {
|
|
221
|
+
violations.push("Inline styles not allowed");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check for javascript: URLs
|
|
225
|
+
if (/javascript:/gi.test(content)) {
|
|
226
|
+
violations.push("JavaScript URLs not allowed");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check for data: URLs with dangerous content
|
|
230
|
+
if (/data:(?:text\/html|application\/javascript)/gi.test(content)) {
|
|
231
|
+
violations.push("Dangerous data URLs not allowed");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (violations.length > 0) {
|
|
235
|
+
return {
|
|
236
|
+
isValid: false,
|
|
237
|
+
error: `Content Security Policy violations: ${violations.join(', ')}`
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return { isValid: true };
|
|
242
|
+
};
|