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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Input Validation and Sanitization System
|
|
3
|
+
* Provides security-focused validation for all MCP tool inputs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
// Common validation patterns
|
|
9
|
+
const URL_PATTERN = /^https?:\/\/[^\s<>'"{}|\\^`\[\]]+$/;
|
|
10
|
+
const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
11
|
+
const SLUG_PATTERN = /^[a-z0-9-]+$/;
|
|
12
|
+
const SCRIPT_PATTERN = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
|
|
13
|
+
const SQL_INJECTION_PATTERN = /('|(\\')|(;)|(\\x00)|(\\n)|(\\r)|(\\x1a)|(\\x22)|(\\x27)|(\\x5c)|(\\x60))/i;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Security validation schemas
|
|
17
|
+
*/
|
|
18
|
+
export const SecuritySchemas = {
|
|
19
|
+
// Safe string with XSS protection
|
|
20
|
+
safeString: z.string()
|
|
21
|
+
.max(10000, 'String too long')
|
|
22
|
+
.refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed')
|
|
23
|
+
.refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
|
|
24
|
+
.refine(val => !val.includes('data:'), 'Data URLs not allowed')
|
|
25
|
+
.refine(val => !val.includes('onerror='), 'Event handlers not allowed')
|
|
26
|
+
.refine(val => !val.includes('onload='), 'Event handlers not allowed')
|
|
27
|
+
.refine(val => !val.includes('onfocus='), 'Event handlers not allowed'),
|
|
28
|
+
|
|
29
|
+
// HTML content with basic sanitization
|
|
30
|
+
htmlContent: z.string()
|
|
31
|
+
.max(100000, 'Content too long')
|
|
32
|
+
.refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed')
|
|
33
|
+
.refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
|
|
34
|
+
.refine(val => !val.includes('on[a-z]+='), 'Event handlers not allowed'),
|
|
35
|
+
|
|
36
|
+
// URL validation
|
|
37
|
+
url: z.string()
|
|
38
|
+
.url('Invalid URL format')
|
|
39
|
+
.regex(URL_PATTERN, 'URL contains invalid characters')
|
|
40
|
+
.refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed')
|
|
41
|
+
.refine(val => !val.includes('data:'), 'Data URLs not allowed'),
|
|
42
|
+
|
|
43
|
+
// Email validation
|
|
44
|
+
email: z.string()
|
|
45
|
+
.email('Invalid email format')
|
|
46
|
+
.regex(EMAIL_PATTERN, 'Email contains invalid characters')
|
|
47
|
+
.max(254, 'Email too long'),
|
|
48
|
+
|
|
49
|
+
// Slug validation (for URLs, usernames, etc.)
|
|
50
|
+
slug: z.string()
|
|
51
|
+
.min(1, 'Slug cannot be empty')
|
|
52
|
+
.max(100, 'Slug too long')
|
|
53
|
+
.regex(SLUG_PATTERN, 'Slug can only contain lowercase letters, numbers, and hyphens'),
|
|
54
|
+
|
|
55
|
+
// WordPress post/page content
|
|
56
|
+
wpContent: z.string()
|
|
57
|
+
.max(1000000, 'Content too long')
|
|
58
|
+
.refine(val => !SCRIPT_PATTERN.test(val), 'Script tags not allowed in content')
|
|
59
|
+
.refine(val => !val.includes('javascript:'), 'JavaScript URLs not allowed'),
|
|
60
|
+
|
|
61
|
+
// Site ID validation
|
|
62
|
+
siteId: z.string()
|
|
63
|
+
.min(1, 'Site ID cannot be empty')
|
|
64
|
+
.max(50, 'Site ID too long')
|
|
65
|
+
.regex(/^[a-zA-Z0-9\-_]+$/, 'Site ID can only contain letters, numbers, hyphens, and underscores'),
|
|
66
|
+
|
|
67
|
+
// WordPress ID (numeric)
|
|
68
|
+
wpId: z.number()
|
|
69
|
+
.int('ID must be an integer')
|
|
70
|
+
.positive('ID must be positive')
|
|
71
|
+
.max(999999999, 'ID too large'),
|
|
72
|
+
|
|
73
|
+
// Search query with SQL injection protection
|
|
74
|
+
searchQuery: z.string()
|
|
75
|
+
.max(500, 'Search query too long')
|
|
76
|
+
.refine(val => !SQL_INJECTION_PATTERN.test(val), 'Invalid characters in search query')
|
|
77
|
+
.refine(val => !val.includes('--'), 'SQL comments not allowed')
|
|
78
|
+
.refine(val => !val.includes('/*'), 'SQL comments not allowed'),
|
|
79
|
+
|
|
80
|
+
// File path validation
|
|
81
|
+
filePath: z.string()
|
|
82
|
+
.max(500, 'File path too long')
|
|
83
|
+
.refine(val => !val.includes('..'), 'Path traversal not allowed')
|
|
84
|
+
.refine(val => !val.includes('<'), 'Invalid characters in path')
|
|
85
|
+
.refine(val => !val.includes('>'), 'Invalid characters in path'),
|
|
86
|
+
|
|
87
|
+
// Password (for display/logging - never log actual passwords)
|
|
88
|
+
passwordMask: z.string()
|
|
89
|
+
.transform(() => '[REDACTED]'),
|
|
90
|
+
|
|
91
|
+
// WordPress application password format
|
|
92
|
+
appPassword: z.string()
|
|
93
|
+
.regex(/^[a-zA-Z0-9\s]{24}$/, 'Invalid application password format')
|
|
94
|
+
.transform(val => val.replace(/\s/g, ' ')) // Normalize spaces
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Input sanitization functions
|
|
99
|
+
*/
|
|
100
|
+
export class InputSanitizer {
|
|
101
|
+
/**
|
|
102
|
+
* Sanitize HTML content by removing dangerous elements
|
|
103
|
+
*/
|
|
104
|
+
static sanitizeHtml(input: string): string {
|
|
105
|
+
return input
|
|
106
|
+
.replace(SCRIPT_PATTERN, '') // Remove script tags
|
|
107
|
+
.replace(/javascript:/gi, '') // Remove javascript: URLs
|
|
108
|
+
.replace(/data:/gi, '') // Remove data: URLs
|
|
109
|
+
.replace(/on[a-z]+\s*=/gi, '') // Remove event handlers
|
|
110
|
+
.replace(/<iframe[^>]*>/gi, '') // Remove iframes
|
|
111
|
+
.replace(/<object[^>]*>/gi, '') // Remove objects
|
|
112
|
+
.replace(/<embed[^>]*>/gi, ''); // Remove embeds
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sanitize search queries to prevent SQL injection
|
|
117
|
+
*/
|
|
118
|
+
static sanitizeSearchQuery(query: string): string {
|
|
119
|
+
return query
|
|
120
|
+
.replace(/['"\\;]/g, '') // Remove quotes and backslashes
|
|
121
|
+
.replace(/--/g, '') // Remove SQL comments
|
|
122
|
+
.replace(/\/\*/g, '') // Remove SQL comments
|
|
123
|
+
.replace(/\*/g, '') // Remove wildcards
|
|
124
|
+
.trim()
|
|
125
|
+
.substring(0, 500); // Limit length
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sanitize file paths to prevent directory traversal
|
|
130
|
+
*/
|
|
131
|
+
static sanitizeFilePath(path: string): string {
|
|
132
|
+
return path
|
|
133
|
+
.replace(/\.\./g, '') // Remove directory traversal
|
|
134
|
+
.replace(/[<>]/g, '') // Remove angle brackets
|
|
135
|
+
.replace(/[|&;$`\\]/g, '') // Remove shell metacharacters
|
|
136
|
+
.trim();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Encode output for safe display
|
|
141
|
+
*/
|
|
142
|
+
static encodeOutput(input: string): string {
|
|
143
|
+
return input
|
|
144
|
+
.replace(/&/g, '&')
|
|
145
|
+
.replace(/</g, '<')
|
|
146
|
+
.replace(/>/g, '>')
|
|
147
|
+
.replace(/"/g, '"')
|
|
148
|
+
.replace(/'/g, ''');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Security validation decorator for tool methods
|
|
154
|
+
*/
|
|
155
|
+
export function validateSecurity(schema: z.ZodSchema) {
|
|
156
|
+
return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
|
|
157
|
+
const method = descriptor.value;
|
|
158
|
+
|
|
159
|
+
descriptor.value = async function (...args: any[]) {
|
|
160
|
+
try {
|
|
161
|
+
// Validate input parameters
|
|
162
|
+
const params = args[0] || {};
|
|
163
|
+
const validatedParams = schema.parse(params);
|
|
164
|
+
|
|
165
|
+
// Log security validation (without sensitive data)
|
|
166
|
+
console.log(`Security validation passed for ${propertyName}`, {
|
|
167
|
+
timestamp: new Date().toISOString(),
|
|
168
|
+
method: propertyName,
|
|
169
|
+
paramCount: Object.keys(validatedParams).length
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Call original method with validated params
|
|
173
|
+
return await method.call(this, validatedParams, ...args.slice(1));
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Log security validation failure
|
|
176
|
+
console.error(`Security validation failed for ${propertyName}`, {
|
|
177
|
+
timestamp: new Date().toISOString(),
|
|
178
|
+
method: propertyName,
|
|
179
|
+
error: error instanceof z.ZodError ? error.errors : (error instanceof Error ? error.message : String(error))
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
throw new SecurityValidationError(
|
|
183
|
+
`Security validation failed for ${propertyName}`,
|
|
184
|
+
error instanceof z.ZodError ? error.errors : [{ message: error instanceof Error ? error.message : String(error) }]
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return descriptor;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Custom security validation error
|
|
195
|
+
*/
|
|
196
|
+
export class SecurityValidationError extends Error {
|
|
197
|
+
public readonly errors: any[];
|
|
198
|
+
|
|
199
|
+
constructor(message: string, errors: any[] = []) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.name = 'SecurityValidationError';
|
|
202
|
+
this.errors = errors;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Tool-specific validation schemas
|
|
208
|
+
*/
|
|
209
|
+
export const ToolSchemas = {
|
|
210
|
+
// Post creation/update
|
|
211
|
+
postData: z.object({
|
|
212
|
+
site: SecuritySchemas.siteId.optional(),
|
|
213
|
+
title: SecuritySchemas.safeString.optional(),
|
|
214
|
+
content: SecuritySchemas.wpContent.optional(),
|
|
215
|
+
excerpt: SecuritySchemas.safeString.optional(),
|
|
216
|
+
status: z.enum(['publish', 'draft', 'private', 'pending']).optional(),
|
|
217
|
+
slug: SecuritySchemas.slug.optional(),
|
|
218
|
+
categories: z.array(SecuritySchemas.wpId).optional(),
|
|
219
|
+
tags: z.array(SecuritySchemas.wpId).optional()
|
|
220
|
+
}),
|
|
221
|
+
|
|
222
|
+
// User creation/update
|
|
223
|
+
userData: z.object({
|
|
224
|
+
site: SecuritySchemas.siteId.optional(),
|
|
225
|
+
username: SecuritySchemas.slug,
|
|
226
|
+
email: SecuritySchemas.email,
|
|
227
|
+
password: SecuritySchemas.safeString.optional(),
|
|
228
|
+
roles: z.array(z.string()).optional(),
|
|
229
|
+
firstName: SecuritySchemas.safeString.optional(),
|
|
230
|
+
lastName: SecuritySchemas.safeString.optional()
|
|
231
|
+
}),
|
|
232
|
+
|
|
233
|
+
// Search parameters
|
|
234
|
+
searchParams: z.object({
|
|
235
|
+
site: SecuritySchemas.siteId.optional(),
|
|
236
|
+
query: SecuritySchemas.searchQuery,
|
|
237
|
+
type: z.enum(['post', 'page', 'any']).optional(),
|
|
238
|
+
limit: z.number().int().min(1).max(100).optional()
|
|
239
|
+
}),
|
|
240
|
+
|
|
241
|
+
// Media upload
|
|
242
|
+
mediaUpload: z.object({
|
|
243
|
+
site: SecuritySchemas.siteId.optional(),
|
|
244
|
+
filename: SecuritySchemas.filePath,
|
|
245
|
+
title: SecuritySchemas.safeString.optional(),
|
|
246
|
+
caption: SecuritySchemas.safeString.optional(),
|
|
247
|
+
description: SecuritySchemas.safeString.optional()
|
|
248
|
+
}),
|
|
249
|
+
|
|
250
|
+
// Site settings
|
|
251
|
+
siteSettings: z.object({
|
|
252
|
+
site: SecuritySchemas.siteId.optional(),
|
|
253
|
+
title: SecuritySchemas.safeString.optional(),
|
|
254
|
+
description: SecuritySchemas.safeString.optional(),
|
|
255
|
+
url: SecuritySchemas.url.optional(),
|
|
256
|
+
adminEmail: SecuritySchemas.email.optional()
|
|
257
|
+
}),
|
|
258
|
+
|
|
259
|
+
// Generic list parameters
|
|
260
|
+
listParams: z.object({
|
|
261
|
+
site: SecuritySchemas.siteId.optional(),
|
|
262
|
+
page: z.number().int().min(1).max(1000).optional(),
|
|
263
|
+
perPage: z.number().int().min(1).max(100).optional(),
|
|
264
|
+
search: SecuritySchemas.searchQuery.optional(),
|
|
265
|
+
orderBy: z.string().max(50).optional(),
|
|
266
|
+
order: z.enum(['asc', 'desc']).optional()
|
|
267
|
+
}),
|
|
268
|
+
|
|
269
|
+
// ID-based operations
|
|
270
|
+
idParams: z.object({
|
|
271
|
+
site: SecuritySchemas.siteId.optional(),
|
|
272
|
+
id: SecuritySchemas.wpId
|
|
273
|
+
})
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Rate limiting and DoS protection
|
|
278
|
+
*/
|
|
279
|
+
export class SecurityLimiter {
|
|
280
|
+
private static requestCounts = new Map<string, { count: number; resetTime: number }>();
|
|
281
|
+
private static readonly RATE_LIMIT = 1000; // requests per window
|
|
282
|
+
private static readonly WINDOW_MS = 60 * 1000; // 1 minute
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if request is within rate limits
|
|
286
|
+
*/
|
|
287
|
+
static checkRateLimit(identifier: string): boolean {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
const key = identifier;
|
|
290
|
+
const current = this.requestCounts.get(key);
|
|
291
|
+
|
|
292
|
+
if (!current || now > current.resetTime) {
|
|
293
|
+
this.requestCounts.set(key, { count: 1, resetTime: now + this.WINDOW_MS });
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (current.count >= this.RATE_LIMIT) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
current.count++;
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clean up expired rate limit entries
|
|
307
|
+
*/
|
|
308
|
+
static cleanup(): void {
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
for (const [key, data] of this.requestCounts.entries()) {
|
|
311
|
+
if (now > data.resetTime) {
|
|
312
|
+
this.requestCounts.delete(key);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Start cleanup interval
|
|
319
|
+
setInterval(() => SecurityLimiter.cleanup(), 60000); // Clean up every minute
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security configuration and constants for MCP WordPress
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
|
|
7
|
+
export const SecurityConfig = {
|
|
8
|
+
// Rate limiting
|
|
9
|
+
rateLimiting: {
|
|
10
|
+
default: {
|
|
11
|
+
windowMs: 60 * 1000, // 1 minute
|
|
12
|
+
maxRequests: 60
|
|
13
|
+
},
|
|
14
|
+
authentication: {
|
|
15
|
+
windowMs: 5 * 60 * 1000, // 5 minutes
|
|
16
|
+
maxAttempts: 5
|
|
17
|
+
},
|
|
18
|
+
upload: {
|
|
19
|
+
windowMs: 60 * 1000, // 1 minute
|
|
20
|
+
maxRequests: 10
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// File upload restrictions
|
|
25
|
+
fileUpload: {
|
|
26
|
+
maxSizeMB: 10,
|
|
27
|
+
allowedMimeTypes: [
|
|
28
|
+
'image/jpeg',
|
|
29
|
+
'image/png',
|
|
30
|
+
'image/gif',
|
|
31
|
+
'image/webp',
|
|
32
|
+
'image/svg+xml',
|
|
33
|
+
'application/pdf',
|
|
34
|
+
'application/msword',
|
|
35
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
36
|
+
'application/vnd.ms-excel',
|
|
37
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
38
|
+
'text/plain',
|
|
39
|
+
'text/csv'
|
|
40
|
+
],
|
|
41
|
+
// Dangerous file extensions to block
|
|
42
|
+
blockedExtensions: [
|
|
43
|
+
'.exe',
|
|
44
|
+
'.bat',
|
|
45
|
+
'.cmd',
|
|
46
|
+
'.com',
|
|
47
|
+
'.pif',
|
|
48
|
+
'.scr',
|
|
49
|
+
'.vbs',
|
|
50
|
+
'.js',
|
|
51
|
+
'.jar',
|
|
52
|
+
'.zip',
|
|
53
|
+
'.rar',
|
|
54
|
+
'.tar',
|
|
55
|
+
'.php',
|
|
56
|
+
'.php3',
|
|
57
|
+
'.php4',
|
|
58
|
+
'.php5',
|
|
59
|
+
'.phtml',
|
|
60
|
+
'.sh',
|
|
61
|
+
'.bash',
|
|
62
|
+
'.zsh',
|
|
63
|
+
'.fish',
|
|
64
|
+
'.ps1',
|
|
65
|
+
'.psm1'
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
// Input validation
|
|
70
|
+
validation: {
|
|
71
|
+
maxStringLength: 1000,
|
|
72
|
+
maxTitleLength: 200,
|
|
73
|
+
maxContentLength: 50000,
|
|
74
|
+
maxExcerptLength: 500,
|
|
75
|
+
maxUrlLength: 2048,
|
|
76
|
+
maxUsernameLength: 60,
|
|
77
|
+
minUsernameLength: 3,
|
|
78
|
+
maxPasswordLength: 128,
|
|
79
|
+
minPasswordLength: 8
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// Request timeouts (milliseconds)
|
|
83
|
+
timeouts: {
|
|
84
|
+
default: 30000, // 30 seconds
|
|
85
|
+
upload: 600000, // 10 minutes
|
|
86
|
+
auth: 10000 // 10 seconds
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// Security headers
|
|
90
|
+
headers: {
|
|
91
|
+
'X-Content-Type-Options': 'nosniff',
|
|
92
|
+
'X-Frame-Options': 'DENY',
|
|
93
|
+
'X-XSS-Protection': '1; mode=block',
|
|
94
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
95
|
+
'Content-Security-Policy': 'default-src \'self\''
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Error messages (generic to avoid information disclosure)
|
|
99
|
+
errorMessages: {
|
|
100
|
+
authentication: 'Authentication failed. Please check your credentials.',
|
|
101
|
+
authorization: 'You do not have permission to perform this action.',
|
|
102
|
+
validation: 'Invalid input provided.',
|
|
103
|
+
rateLimit: 'Too many requests. Please try again later.',
|
|
104
|
+
serverError: 'An error occurred processing your request.',
|
|
105
|
+
notFound: 'The requested resource was not found.'
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Logging configuration
|
|
109
|
+
logging: {
|
|
110
|
+
// Fields to exclude from logs
|
|
111
|
+
excludeFields: [
|
|
112
|
+
'password',
|
|
113
|
+
'appPassword',
|
|
114
|
+
'app_password',
|
|
115
|
+
'token',
|
|
116
|
+
'secret',
|
|
117
|
+
'authorization',
|
|
118
|
+
'cookie',
|
|
119
|
+
'session',
|
|
120
|
+
'key',
|
|
121
|
+
'apiKey',
|
|
122
|
+
'api_key'
|
|
123
|
+
],
|
|
124
|
+
// Patterns to redact in log messages
|
|
125
|
+
redactPatterns: [
|
|
126
|
+
/password["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
127
|
+
/token["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
128
|
+
/secret["\s:=]+["']?([^"'\s]+)["']?/gi,
|
|
129
|
+
/key["\s:=]+["']?([^"'\s]+)["']?/gi
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// Cache configuration
|
|
134
|
+
cache: {
|
|
135
|
+
// Default cache settings
|
|
136
|
+
enabled: true,
|
|
137
|
+
maxSize: 1000, // Maximum number of cached entries
|
|
138
|
+
defaultTTL: 15 * 60 * 1000, // 15 minutes default TTL
|
|
139
|
+
enableLRU: true,
|
|
140
|
+
enableStats: true,
|
|
141
|
+
|
|
142
|
+
// TTL presets by data type (milliseconds)
|
|
143
|
+
ttlPresets: {
|
|
144
|
+
static: 4 * 60 * 60 * 1000, // 4 hours - site settings, user roles
|
|
145
|
+
semiStatic: 2 * 60 * 60 * 1000, // 2 hours - categories, tags, user profiles
|
|
146
|
+
dynamic: 15 * 60 * 1000, // 15 minutes - posts, pages, comments
|
|
147
|
+
session: 30 * 60 * 1000, // 30 minutes - authentication, current user
|
|
148
|
+
realtime: 60 * 1000 // 1 minute - real-time data
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// Cache-Control headers by data type
|
|
152
|
+
cacheHeaders: {
|
|
153
|
+
static: 'public, max-age=14400', // 4 hours
|
|
154
|
+
semiStatic: 'public, max-age=7200', // 2 hours
|
|
155
|
+
dynamic: 'public, max-age=900', // 15 minutes
|
|
156
|
+
session: 'private, max-age=1800', // 30 minutes
|
|
157
|
+
realtime: 'public, max-age=60' // 1 minute
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Invalidation settings
|
|
161
|
+
invalidation: {
|
|
162
|
+
enabled: true,
|
|
163
|
+
batchSize: 100, // Max events to process in one batch
|
|
164
|
+
queueTimeout: 5000, // Max time to wait before processing queue (ms)
|
|
165
|
+
enableCascading: true // Allow cascading invalidations
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// Memory management
|
|
169
|
+
cleanup: {
|
|
170
|
+
interval: 60 * 1000, // Cleanup interval in milliseconds (1 minute)
|
|
171
|
+
maxMemoryMB: 50, // Maximum memory usage for cache
|
|
172
|
+
evictionThreshold: 0.8 // Start evicting when 80% full
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Security utility functions
|
|
179
|
+
*/
|
|
180
|
+
export class SecurityUtils {
|
|
181
|
+
/**
|
|
182
|
+
* Redact sensitive information from objects
|
|
183
|
+
*/
|
|
184
|
+
static redactSensitiveData(obj: any): any {
|
|
185
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
186
|
+
return obj;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const redacted = Array.isArray(obj) ? [...obj] : { ...obj };
|
|
190
|
+
|
|
191
|
+
for (const key in redacted) {
|
|
192
|
+
if (
|
|
193
|
+
SecurityConfig.logging.excludeFields.some((field) =>
|
|
194
|
+
key.toLowerCase().includes(field.toLowerCase())
|
|
195
|
+
)
|
|
196
|
+
) {
|
|
197
|
+
redacted[key] = '[REDACTED]';
|
|
198
|
+
} else if (typeof redacted[key] === 'object') {
|
|
199
|
+
redacted[key] = SecurityUtils.redactSensitiveData(redacted[key]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return redacted;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Redact sensitive patterns from strings
|
|
208
|
+
*/
|
|
209
|
+
static redactString(str: string): string {
|
|
210
|
+
let redacted = str;
|
|
211
|
+
for (const pattern of SecurityConfig.logging.redactPatterns) {
|
|
212
|
+
redacted = redacted.replace(pattern, (match, value) => {
|
|
213
|
+
return match.replace(value, '[REDACTED]');
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return redacted;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Generate secure random strings
|
|
221
|
+
*/
|
|
222
|
+
static generateSecureToken(length: number = 32): string {
|
|
223
|
+
const chars =
|
|
224
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
225
|
+
const array = new Uint8Array(length);
|
|
226
|
+
|
|
227
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
228
|
+
crypto.getRandomValues(array);
|
|
229
|
+
} else {
|
|
230
|
+
// Fallback for Node.js
|
|
231
|
+
const buffer = randomBytes(length);
|
|
232
|
+
array.set(buffer);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return Array.from(array, (byte) => chars[byte % chars.length]).join('');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check if a file extension is allowed
|
|
240
|
+
*/
|
|
241
|
+
static isFileExtensionAllowed(filename: string): boolean {
|
|
242
|
+
const ext = path.extname(filename).toLowerCase();
|
|
243
|
+
return !SecurityConfig.fileUpload.blockedExtensions.includes(ext);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Sanitize log output
|
|
248
|
+
*/
|
|
249
|
+
static sanitizeForLog(data: any): any {
|
|
250
|
+
if (typeof data === 'string') {
|
|
251
|
+
return SecurityUtils.redactString(data);
|
|
252
|
+
}
|
|
253
|
+
if (typeof data === 'object') {
|
|
254
|
+
return SecurityUtils.redactSensitiveData(data);
|
|
255
|
+
}
|
|
256
|
+
return data;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Secure error handler that prevents information disclosure
|
|
262
|
+
*/
|
|
263
|
+
export function createSecureError(
|
|
264
|
+
error: any,
|
|
265
|
+
fallbackMessage: string = SecurityConfig.errorMessages.serverError
|
|
266
|
+
): Error {
|
|
267
|
+
// Log the actual error for debugging (with sanitization)
|
|
268
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
269
|
+
console.error('Secure Error:', SecurityUtils.sanitizeForLog(error));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Return generic error to prevent information disclosure
|
|
273
|
+
const secureError = new Error(fallbackMessage);
|
|
274
|
+
|
|
275
|
+
// Preserve error code if it's safe
|
|
276
|
+
if (error && typeof error.code === 'string' && !error.code.includes('_')) {
|
|
277
|
+
(secureError as any).code = error.code;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return secureError;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Import path for file extension checking
|
|
284
|
+
import * as path from 'path';
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Environment-specific security settings
|
|
288
|
+
*/
|
|
289
|
+
export function getEnvironmentSecurity(): {
|
|
290
|
+
strictMode: boolean;
|
|
291
|
+
verboseErrors: boolean;
|
|
292
|
+
enforceHttps: boolean;
|
|
293
|
+
} {
|
|
294
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
strictMode: isProduction,
|
|
298
|
+
verboseErrors: !isProduction,
|
|
299
|
+
enforceHttps: isProduction
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { WordPressClient } from '../client/api.js';
|
|
2
|
+
import { getErrorMessage } from '../utils/error.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Service for testing WordPress client connections
|
|
6
|
+
* Handles connection validation and health checks
|
|
7
|
+
*/
|
|
8
|
+
export class ConnectionTester {
|
|
9
|
+
/**
|
|
10
|
+
* Test connections to all configured WordPress sites
|
|
11
|
+
*/
|
|
12
|
+
public static async testClientConnections(
|
|
13
|
+
wordpressClients: Map<string, WordPressClient>
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
console.error('INFO: Testing connections to all configured WordPress sites...');
|
|
16
|
+
|
|
17
|
+
const connectionPromises = Array.from(wordpressClients.entries()).map(
|
|
18
|
+
async ([siteId, client]) => {
|
|
19
|
+
try {
|
|
20
|
+
await client.ping();
|
|
21
|
+
console.error(`SUCCESS: Connection to site '${siteId}' successful.`);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error(`ERROR: Failed to connect to site '${siteId}': ${getErrorMessage(error)}`);
|
|
24
|
+
|
|
25
|
+
if (ConnectionTester.isAuthenticationError(error)) {
|
|
26
|
+
console.error(`Authentication may have failed for site '${siteId}'. Please check credentials.`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
await Promise.all(connectionPromises);
|
|
33
|
+
console.error('INFO: Connection tests complete.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if error is authentication-related
|
|
38
|
+
*/
|
|
39
|
+
private static isAuthenticationError(error: any): boolean {
|
|
40
|
+
if (error?.response?.status && [401, 403].includes(error.response.status)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return error?.code === 'WORDPRESS_AUTH_ERROR';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Perform health check for a specific client
|
|
48
|
+
*/
|
|
49
|
+
public static async healthCheck(client: WordPressClient): Promise<boolean> {
|
|
50
|
+
try {
|
|
51
|
+
await client.ping();
|
|
52
|
+
return true;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(`Health check failed: ${getErrorMessage(error)}`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Perform health checks for all clients
|
|
61
|
+
*/
|
|
62
|
+
public static async healthCheckAll(
|
|
63
|
+
wordpressClients: Map<string, WordPressClient>
|
|
64
|
+
): Promise<Map<string, boolean>> {
|
|
65
|
+
const results = new Map<string, boolean>();
|
|
66
|
+
|
|
67
|
+
for (const [siteId, client] of wordpressClients.entries()) {
|
|
68
|
+
const isHealthy = await ConnectionTester.healthCheck(client);
|
|
69
|
+
results.set(siteId, isHealthy);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
}
|