mcp-wordpress 1.1.7 → 1.2.2
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 +388 -66
- package/dist/cache/CacheInvalidation.d.ts +118 -0
- package/dist/cache/CacheInvalidation.d.ts.map +1 -0
- package/dist/cache/CacheInvalidation.js +349 -0
- package/dist/cache/CacheInvalidation.js.map +1 -0
- package/dist/cache/CacheManager.d.ts +143 -0
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +308 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/cache/HttpCacheWrapper.d.ts +121 -0
- package/dist/cache/HttpCacheWrapper.d.ts.map +1 -0
- package/dist/cache/HttpCacheWrapper.js +280 -0
- package/dist/cache/HttpCacheWrapper.js.map +1 -0
- package/dist/cache/__tests__/CacheInvalidation.test.d.ts +5 -0
- package/dist/cache/__tests__/CacheInvalidation.test.d.ts.map +1 -0
- package/dist/cache/__tests__/CacheInvalidation.test.js +236 -0
- package/dist/cache/__tests__/CacheInvalidation.test.js.map +1 -0
- package/dist/cache/__tests__/CacheManager.test.d.ts +5 -0
- package/dist/cache/__tests__/CacheManager.test.d.ts.map +1 -0
- package/dist/cache/__tests__/CacheManager.test.js +233 -0
- package/dist/cache/__tests__/CacheManager.test.js.map +1 -0
- package/dist/cache/__tests__/CachedWordPressClient.test.d.ts +5 -0
- package/dist/cache/__tests__/CachedWordPressClient.test.d.ts.map +1 -0
- package/dist/cache/__tests__/CachedWordPressClient.test.js +228 -0
- package/dist/cache/__tests__/CachedWordPressClient.test.js.map +1 -0
- package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts +5 -0
- package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts.map +1 -0
- package/dist/cache/__tests__/HttpCacheWrapper.test.js +296 -0
- package/dist/cache/__tests__/HttpCacheWrapper.test.js.map +1 -0
- package/dist/cache/index.d.ts +12 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +9 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/client/CachedWordPressClient.d.ts +160 -0
- package/dist/client/CachedWordPressClient.d.ts.map +1 -0
- package/dist/client/CachedWordPressClient.js +338 -0
- package/dist/client/CachedWordPressClient.js.map +1 -0
- package/dist/client/WordPressClient.d.ts +81 -0
- package/dist/client/WordPressClient.d.ts.map +1 -0
- package/dist/client/WordPressClient.js +354 -0
- package/dist/client/WordPressClient.js.map +1 -0
- package/dist/config/ConfigurationSchema.d.ts +281 -0
- package/dist/config/ConfigurationSchema.d.ts.map +1 -0
- package/dist/config/ConfigurationSchema.js +205 -0
- package/dist/config/ConfigurationSchema.js.map +1 -0
- package/dist/config/ServerConfiguration.d.ts +38 -0
- package/dist/config/ServerConfiguration.d.ts.map +1 -0
- package/dist/config/ServerConfiguration.js +158 -0
- package/dist/config/ServerConfiguration.js.map +1 -0
- package/dist/docs/DocumentationGenerator.d.ts +184 -0
- package/dist/docs/DocumentationGenerator.d.ts.map +1 -0
- package/dist/docs/DocumentationGenerator.js +735 -0
- package/dist/docs/DocumentationGenerator.js.map +1 -0
- package/dist/docs/MarkdownFormatter.d.ts +84 -0
- package/dist/docs/MarkdownFormatter.d.ts.map +1 -0
- package/dist/docs/MarkdownFormatter.js +448 -0
- package/dist/docs/MarkdownFormatter.js.map +1 -0
- package/dist/docs/index.d.ts +8 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +7 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -212
- package/dist/index.js.map +1 -1
- package/dist/performance/AnomalyDetector.d.ts +63 -0
- package/dist/performance/AnomalyDetector.d.ts.map +1 -0
- package/dist/performance/AnomalyDetector.js +222 -0
- package/dist/performance/AnomalyDetector.js.map +1 -0
- package/dist/performance/BenchmarkAnalyzer.d.ts +67 -0
- package/dist/performance/BenchmarkAnalyzer.d.ts.map +1 -0
- package/dist/performance/BenchmarkAnalyzer.js +301 -0
- package/dist/performance/BenchmarkAnalyzer.js.map +1 -0
- package/dist/performance/MetricsCollector.d.ts +139 -0
- package/dist/performance/MetricsCollector.d.ts.map +1 -0
- package/dist/performance/MetricsCollector.js +320 -0
- package/dist/performance/MetricsCollector.js.map +1 -0
- package/dist/performance/PerformanceAnalytics.d.ts +162 -0
- package/dist/performance/PerformanceAnalytics.d.ts.map +1 -0
- package/dist/performance/PerformanceAnalytics.js +554 -0
- package/dist/performance/PerformanceAnalytics.js.map +1 -0
- package/dist/performance/PerformanceMonitor.d.ts +202 -0
- package/dist/performance/PerformanceMonitor.d.ts.map +1 -0
- package/dist/performance/PerformanceMonitor.js +478 -0
- package/dist/performance/PerformanceMonitor.js.map +1 -0
- package/dist/performance/TrendAnalyzer.d.ts +69 -0
- package/dist/performance/TrendAnalyzer.d.ts.map +1 -0
- package/dist/performance/TrendAnalyzer.js +203 -0
- package/dist/performance/TrendAnalyzer.js.map +1 -0
- package/dist/performance/index.d.ts +11 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +8 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/security/InputValidator.d.ts +215 -0
- package/dist/security/InputValidator.d.ts.map +1 -0
- package/dist/security/InputValidator.js +278 -0
- package/dist/security/InputValidator.js.map +1 -0
- package/dist/security/SecurityConfig.d.ts +129 -0
- package/dist/security/SecurityConfig.d.ts.map +1 -0
- package/dist/security/SecurityConfig.js +262 -0
- package/dist/security/SecurityConfig.js.map +1 -0
- package/dist/server/ConnectionTester.d.ts +24 -0
- package/dist/server/ConnectionTester.d.ts.map +1 -0
- package/dist/server/ConnectionTester.js +61 -0
- package/dist/server/ConnectionTester.js.map +1 -0
- package/dist/server/ToolRegistry.d.ts +46 -0
- package/dist/server/ToolRegistry.d.ts.map +1 -0
- package/dist/server/ToolRegistry.js +148 -0
- package/dist/server/ToolRegistry.js.map +1 -0
- package/dist/tools/BaseToolClass.d.ts +76 -0
- package/dist/tools/BaseToolClass.d.ts.map +1 -0
- package/dist/tools/BaseToolClass.js +104 -0
- package/dist/tools/BaseToolClass.js.map +1 -0
- package/dist/tools/BaseToolManager.d.ts +26 -0
- package/dist/tools/BaseToolManager.d.ts.map +1 -0
- package/dist/tools/BaseToolManager.js +56 -0
- package/dist/tools/BaseToolManager.js.map +1 -0
- package/dist/tools/base.d.ts +37 -0
- package/dist/tools/base.d.ts.map +1 -0
- package/dist/tools/base.js +60 -0
- package/dist/tools/base.js.map +1 -0
- package/dist/tools/cache.d.ts +260 -0
- package/dist/tools/cache.d.ts.map +1 -0
- package/dist/tools/cache.js +237 -0
- package/dist/tools/cache.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/performance.d.ts +63 -0
- package/dist/tools/performance.d.ts.map +1 -0
- package/dist/tools/performance.js +865 -0
- package/dist/tools/performance.js.map +1 -0
- package/dist/types/client.d.ts +1 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/client.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts +4 -0
- package/dist/utils/toolWrapper.d.ts.map +1 -1
- package/dist/utils/toolWrapper.js +11 -0
- package/dist/utils/toolWrapper.js.map +1 -1
- package/dist/utils/validation.d.ts +68 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +185 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/CACHING.md +340 -0
- package/docs/DOCKER.md +451 -0
- package/docs/PERFORMANCE_MONITORING.md +471 -0
- package/docs/SECURITY_TESTING.md +393 -0
- package/docs/api/README.md +200 -0
- package/docs/api/categories/auth.md +40 -0
- package/docs/api/categories/cache.md +41 -0
- package/docs/api/categories/comment.md +44 -0
- package/docs/api/categories/media.md +43 -0
- package/docs/api/categories/page.md +43 -0
- package/docs/api/categories/performance.md +44 -0
- package/docs/api/categories/post.md +43 -0
- package/docs/api/categories/site.md +43 -0
- package/docs/api/categories/taxonomy.md +47 -0
- package/docs/api/categories/user.md +43 -0
- package/docs/api/openapi.json +3305 -0
- package/docs/api/summary.json +12 -0
- package/docs/api/tools/wp_approve_comment.md +98 -0
- package/docs/api/tools/wp_cache_clear.md +120 -0
- package/docs/api/tools/wp_cache_info.md +119 -0
- package/docs/api/tools/wp_cache_stats.md +119 -0
- package/docs/api/tools/wp_cache_warm.md +119 -0
- package/docs/api/tools/wp_create_application_password.md +102 -0
- package/docs/api/tools/wp_create_category.md +102 -0
- package/docs/api/tools/wp_create_comment.md +128 -0
- package/docs/api/tools/wp_create_page.md +135 -0
- package/docs/api/tools/wp_create_post.md +147 -0
- package/docs/api/tools/wp_create_tag.md +101 -0
- package/docs/api/tools/wp_create_user.md +135 -0
- package/docs/api/tools/wp_delete_application_password.md +101 -0
- package/docs/api/tools/wp_delete_category.md +100 -0
- package/docs/api/tools/wp_delete_comment.md +101 -0
- package/docs/api/tools/wp_delete_media.md +108 -0
- package/docs/api/tools/wp_delete_page.md +108 -0
- package/docs/api/tools/wp_delete_post.md +117 -0
- package/docs/api/tools/wp_delete_tag.md +100 -0
- package/docs/api/tools/wp_delete_user.md +108 -0
- package/docs/api/tools/wp_get_application_passwords.md +103 -0
- package/docs/api/tools/wp_get_auth_status.md +101 -0
- package/docs/api/tools/wp_get_category.md +103 -0
- package/docs/api/tools/wp_get_comment.md +103 -0
- package/docs/api/tools/wp_get_current_user.md +101 -0
- package/docs/api/tools/wp_get_media.md +103 -0
- package/docs/api/tools/wp_get_page.md +103 -0
- package/docs/api/tools/wp_get_page_revisions.md +103 -0
- package/docs/api/tools/wp_get_post.md +112 -0
- package/docs/api/tools/wp_get_post_revisions.md +103 -0
- package/docs/api/tools/wp_get_site_settings.md +108 -0
- package/docs/api/tools/wp_get_tag.md +103 -0
- package/docs/api/tools/wp_get_user.md +103 -0
- package/docs/api/tools/wp_list_categories.md +111 -0
- package/docs/api/tools/wp_list_comments.md +111 -0
- package/docs/api/tools/wp_list_media.md +145 -0
- package/docs/api/tools/wp_list_pages.md +145 -0
- package/docs/api/tools/wp_list_posts.md +156 -0
- package/docs/api/tools/wp_list_tags.md +110 -0
- package/docs/api/tools/wp_list_users.md +111 -0
- package/docs/api/tools/wp_performance_alerts.md +162 -0
- package/docs/api/tools/wp_performance_benchmark.md +160 -0
- package/docs/api/tools/wp_performance_export.md +162 -0
- package/docs/api/tools/wp_performance_history.md +161 -0
- package/docs/api/tools/wp_performance_optimize.md +162 -0
- package/docs/api/tools/wp_performance_stats.md +160 -0
- package/docs/api/tools/wp_search_site.md +99 -0
- package/docs/api/tools/wp_spam_comment.md +98 -0
- package/docs/api/tools/wp_switch_auth_method.md +122 -0
- package/docs/api/tools/wp_test_auth.md +96 -0
- package/docs/api/tools/wp_update_category.md +102 -0
- package/docs/api/tools/wp_update_comment.md +127 -0
- package/docs/api/tools/wp_update_media.md +129 -0
- package/docs/api/tools/wp_update_page.md +135 -0
- package/docs/api/tools/wp_update_post.md +144 -0
- package/docs/api/tools/wp_update_site_settings.md +127 -0
- package/docs/api/tools/wp_update_tag.md +102 -0
- package/docs/api/tools/wp_update_user.md +134 -0
- package/docs/api/tools/wp_upload_media.md +131 -0
- package/docs/api/types/WordPressPost.md +39 -0
- package/docs/contract-testing.md +183 -0
- package/docs/developer/NPM_AUTH_SETUP.md +3 -3
- package/docs/wordpress-rest-api-authentication-troubleshooting.md +218 -0
- package/package.json +84 -64
- package/src/cache/CacheInvalidation.ts +421 -0
- package/src/cache/CacheManager.ts +391 -0
- package/src/cache/HttpCacheWrapper.ts +372 -0
- package/src/cache/__tests__/CacheInvalidation.test.ts +299 -0
- package/src/cache/__tests__/CacheManager.test.ts +300 -0
- package/src/cache/__tests__/CachedWordPressClient.test.ts +304 -0
- package/src/cache/__tests__/HttpCacheWrapper.test.ts +359 -0
- package/src/cache/index.ts +26 -0
- package/src/client/CachedWordPressClient.ts +442 -0
- package/src/config/ConfigurationSchema.ts +246 -0
- package/src/config/ServerConfiguration.ts +215 -0
- package/src/docs/DocumentationGenerator.ts +952 -0
- package/src/docs/MarkdownFormatter.ts +494 -0
- package/src/docs/index.ts +21 -0
- package/src/index.ts +14 -274
- package/src/performance/MetricsCollector.ts +447 -0
- package/src/performance/PerformanceAnalytics.ts +762 -0
- package/src/performance/PerformanceMonitor.ts +649 -0
- package/src/performance/index.ts +28 -0
- package/src/security/InputValidator.ts +319 -0
- package/src/security/SecurityConfig.ts +301 -0
- package/src/server/ConnectionTester.ts +74 -0
- package/src/server/ToolRegistry.ts +194 -0
- package/src/tools/BaseToolManager.ts +66 -0
- package/src/tools/cache.ts +259 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/performance.ts +948 -0
- package/src/types/client.ts +1 -0
- package/src/utils/toolWrapper.ts +11 -0
- package/src/utils/validation.ts +259 -0
package/src/types/client.ts
CHANGED
package/src/utils/toolWrapper.ts
CHANGED
|
@@ -103,3 +103,14 @@ export function formatErrorResponse(operation: string, error: any): never {
|
|
|
103
103
|
const message = getErrorMessage(error);
|
|
104
104
|
throw new Error(`${operation}: ${message}`);
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Simple tool wrapper for standalone async functions
|
|
109
|
+
*/
|
|
110
|
+
export async function toolWrapper<T>(fn: () => Promise<T>): Promise<T> {
|
|
111
|
+
try {
|
|
112
|
+
return await fn();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(getErrorMessage(error));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { WordPressAPIError } from '../types/client.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Security-focused validation utilities for MCP WordPress
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validates and sanitizes numeric IDs
|
|
10
|
+
*/
|
|
11
|
+
export function validateId(id: any, fieldName: string = 'id'): number {
|
|
12
|
+
const numId = parseInt(String(id), 10);
|
|
13
|
+
if (isNaN(numId) || numId <= 0) {
|
|
14
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be a positive number`, 400, 'INVALID_PARAMETER');
|
|
15
|
+
}
|
|
16
|
+
return numId;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validates string length within bounds
|
|
21
|
+
*/
|
|
22
|
+
export function validateString(
|
|
23
|
+
value: any,
|
|
24
|
+
fieldName: string,
|
|
25
|
+
minLength: number = 1,
|
|
26
|
+
maxLength: number = 1000
|
|
27
|
+
): string {
|
|
28
|
+
if (typeof value !== 'string') {
|
|
29
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be a string`, 400, 'INVALID_PARAMETER');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (trimmed.length < minLength || trimmed.length > maxLength) {
|
|
34
|
+
throw new WordPressAPIError(
|
|
35
|
+
`Invalid ${fieldName}: length must be between ${minLength} and ${maxLength} characters`,
|
|
36
|
+
400,
|
|
37
|
+
'INVALID_PARAMETER'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validates and sanitizes file paths to prevent directory traversal
|
|
46
|
+
*/
|
|
47
|
+
export function validateFilePath(userPath: string, allowedBasePath: string): string {
|
|
48
|
+
// Normalize the path to remove ../ and other dangerous patterns
|
|
49
|
+
const normalizedPath = path.normalize(userPath);
|
|
50
|
+
const resolvedPath = path.resolve(allowedBasePath, normalizedPath);
|
|
51
|
+
|
|
52
|
+
// Ensure the resolved path is within the allowed directory
|
|
53
|
+
if (!resolvedPath.startsWith(path.resolve(allowedBasePath))) {
|
|
54
|
+
throw new WordPressAPIError('Invalid file path: access denied', 403, 'PATH_TRAVERSAL_ATTEMPT');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return resolvedPath;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validates WordPress post status values
|
|
62
|
+
*/
|
|
63
|
+
export function validatePostStatus(status: string): string {
|
|
64
|
+
const validStatuses = ['publish', 'draft', 'pending', 'private', 'future', 'auto-draft', 'trash'];
|
|
65
|
+
if (!validStatuses.includes(status)) {
|
|
66
|
+
throw new WordPressAPIError(
|
|
67
|
+
`Invalid status: must be one of ${validStatuses.join(', ')}`,
|
|
68
|
+
400,
|
|
69
|
+
'INVALID_PARAMETER'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return status;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validates and sanitizes URLs
|
|
77
|
+
*/
|
|
78
|
+
export function validateUrl(url: string, fieldName: string = 'url'): string {
|
|
79
|
+
try {
|
|
80
|
+
const urlObj = new URL(url);
|
|
81
|
+
// Only allow http and https protocols
|
|
82
|
+
if (!['http:', 'https:'].includes(urlObj.protocol)) {
|
|
83
|
+
throw new Error('Invalid protocol');
|
|
84
|
+
}
|
|
85
|
+
return urlObj.toString();
|
|
86
|
+
} catch {
|
|
87
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be a valid URL`, 400, 'INVALID_PARAMETER');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates file size
|
|
93
|
+
*/
|
|
94
|
+
export function validateFileSize(sizeInBytes: number, maxSizeInMB: number = 10): void {
|
|
95
|
+
const maxSizeInBytes = maxSizeInMB * 1024 * 1024;
|
|
96
|
+
if (sizeInBytes > maxSizeInBytes) {
|
|
97
|
+
throw new WordPressAPIError(
|
|
98
|
+
`File size exceeds maximum allowed size of ${maxSizeInMB}MB`,
|
|
99
|
+
413,
|
|
100
|
+
'FILE_TOO_LARGE'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates MIME types for file uploads
|
|
107
|
+
*/
|
|
108
|
+
export function validateMimeType(mimeType: string, allowedTypes: string[]): void {
|
|
109
|
+
if (!allowedTypes.includes(mimeType)) {
|
|
110
|
+
throw new WordPressAPIError(
|
|
111
|
+
`Invalid file type: ${mimeType}. Allowed types: ${allowedTypes.join(', ')}`,
|
|
112
|
+
415,
|
|
113
|
+
'UNSUPPORTED_MEDIA_TYPE'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sanitizes HTML content to prevent XSS
|
|
120
|
+
* Note: This is a basic implementation. For production use,
|
|
121
|
+
* consider using a library like DOMPurify
|
|
122
|
+
*/
|
|
123
|
+
export function sanitizeHtml(html: string): string {
|
|
124
|
+
// Remove script tags and their content
|
|
125
|
+
let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
126
|
+
|
|
127
|
+
// Remove event handlers
|
|
128
|
+
sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '');
|
|
129
|
+
|
|
130
|
+
// Remove javascript: protocol
|
|
131
|
+
sanitized = sanitized.replace(/javascript:/gi, '');
|
|
132
|
+
|
|
133
|
+
// Remove data: protocol (can be used for XSS)
|
|
134
|
+
sanitized = sanitized.replace(/data:text\/html/gi, '');
|
|
135
|
+
|
|
136
|
+
return sanitized;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validates array input
|
|
141
|
+
*/
|
|
142
|
+
export function validateArray<T>(
|
|
143
|
+
value: any,
|
|
144
|
+
fieldName: string,
|
|
145
|
+
minItems: number = 0,
|
|
146
|
+
maxItems: number = 100
|
|
147
|
+
): T[] {
|
|
148
|
+
if (!Array.isArray(value)) {
|
|
149
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be an array`, 400, 'INVALID_PARAMETER');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (value.length < minItems || value.length > maxItems) {
|
|
153
|
+
throw new WordPressAPIError(
|
|
154
|
+
`Invalid ${fieldName}: array must contain between ${minItems} and ${maxItems} items`,
|
|
155
|
+
400,
|
|
156
|
+
'INVALID_PARAMETER'
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Validates email addresses
|
|
165
|
+
*/
|
|
166
|
+
export function validateEmail(email: string): string {
|
|
167
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
168
|
+
if (!emailRegex.test(email)) {
|
|
169
|
+
throw new WordPressAPIError('Invalid email address format', 400, 'INVALID_PARAMETER');
|
|
170
|
+
}
|
|
171
|
+
return email.toLowerCase();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validates username format
|
|
176
|
+
*/
|
|
177
|
+
export function validateUsername(username: string): string {
|
|
178
|
+
// WordPress username rules: alphanumeric, space, underscore, hyphen, period, @ symbol
|
|
179
|
+
const usernameRegex = /^[a-zA-Z0-9 _.\-@]+$/;
|
|
180
|
+
if (!usernameRegex.test(username)) {
|
|
181
|
+
throw new WordPressAPIError(
|
|
182
|
+
'Invalid username: can only contain letters, numbers, spaces, and _.-@ symbols',
|
|
183
|
+
400,
|
|
184
|
+
'INVALID_PARAMETER'
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (username.length < 3 || username.length > 60) {
|
|
189
|
+
throw new WordPressAPIError(
|
|
190
|
+
'Invalid username: must be between 3 and 60 characters',
|
|
191
|
+
400,
|
|
192
|
+
'INVALID_PARAMETER'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return username;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Rate limiting tracker (simple in-memory implementation)
|
|
201
|
+
* For production, use Redis or similar
|
|
202
|
+
*/
|
|
203
|
+
class RateLimiter {
|
|
204
|
+
private attempts: Map<string, { count: number; resetTime: number }> = new Map();
|
|
205
|
+
|
|
206
|
+
constructor(
|
|
207
|
+
private maxAttempts: number = 5,
|
|
208
|
+
private windowMs: number = 60000 // 1 minute
|
|
209
|
+
) {}
|
|
210
|
+
|
|
211
|
+
check(identifier: string): void {
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
const record = this.attempts.get(identifier);
|
|
214
|
+
|
|
215
|
+
if (!record || record.resetTime < now) {
|
|
216
|
+
this.attempts.set(identifier, { count: 1, resetTime: now + this.windowMs });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (record.count >= this.maxAttempts) {
|
|
221
|
+
const waitTime = Math.ceil((record.resetTime - now) / 1000);
|
|
222
|
+
throw new WordPressAPIError(
|
|
223
|
+
`Rate limit exceeded. Please wait ${waitTime} seconds before trying again.`,
|
|
224
|
+
429,
|
|
225
|
+
'RATE_LIMIT_EXCEEDED'
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
record.count++;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
reset(identifier: string): void {
|
|
233
|
+
this.attempts.delete(identifier);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Export a default rate limiter for authentication attempts
|
|
238
|
+
export const authRateLimiter = new RateLimiter(5, 300000); // 5 attempts per 5 minutes
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Validates and sanitizes search queries
|
|
242
|
+
*/
|
|
243
|
+
export function validateSearchQuery(query: string): string {
|
|
244
|
+
// Remove potentially dangerous characters while preserving search functionality
|
|
245
|
+
let sanitized = query.trim();
|
|
246
|
+
|
|
247
|
+
// Limit length to prevent DoS
|
|
248
|
+
if (sanitized.length > 200) {
|
|
249
|
+
sanitized = sanitized.substring(0, 200);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Remove SQL-like patterns (basic protection)
|
|
253
|
+
sanitized = sanitized.replace(/(\b(union|select|insert|update|delete|drop|create)\b)/gi, '');
|
|
254
|
+
|
|
255
|
+
// Remove special characters that might be used for injection
|
|
256
|
+
sanitized = sanitized.replace(/[<>'"`;\\]/g, '');
|
|
257
|
+
|
|
258
|
+
return sanitized;
|
|
259
|
+
}
|