mcp-wordpress 2.4.2 → 2.5.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 +114 -48
- package/dist/ajv-patch.js +34 -0
- package/dist/cache/CacheInvalidation.d.ts +3 -1
- package/dist/cache/CacheInvalidation.d.ts.map +1 -1
- package/dist/cache/CacheInvalidation.js +10 -4
- package/dist/cache/CacheInvalidation.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +3 -2
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +11 -3
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/HttpCacheWrapper.d.ts +7 -6
- package/dist/cache/HttpCacheWrapper.d.ts.map +1 -1
- package/dist/cache/HttpCacheWrapper.js +8 -5
- package/dist/cache/HttpCacheWrapper.js.map +1 -1
- package/dist/cache/__tests__/HttpCacheWrapper.test.js +6 -5
- package/dist/cache/__tests__/HttpCacheWrapper.test.js.map +1 -1
- package/dist/cache/index.d.ts +3 -3
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/CachedWordPressClient.d.ts +23 -9
- package/dist/client/CachedWordPressClient.d.ts.map +1 -1
- package/dist/client/CachedWordPressClient.js +4 -1
- package/dist/client/CachedWordPressClient.js.map +1 -1
- package/dist/client/MockWordPressClient.d.ts +2 -1
- package/dist/client/MockWordPressClient.d.ts.map +1 -1
- package/dist/client/MockWordPressClient.js +3 -1
- package/dist/client/MockWordPressClient.js.map +1 -1
- package/dist/client/api.d.ts +17 -13
- package/dist/client/api.d.ts.map +1 -1
- package/dist/client/api.js +135 -30
- package/dist/client/api.js.map +1 -1
- package/dist/client/auth.d.ts.map +1 -1
- package/dist/client/auth.js +2 -3
- package/dist/client/auth.js.map +1 -1
- package/dist/client/managers/AuthenticationManager.d.ts +55 -2
- package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
- package/dist/client/managers/AuthenticationManager.js +269 -71
- package/dist/client/managers/AuthenticationManager.js.map +1 -1
- package/dist/client/managers/BaseManager.d.ts +3 -3
- package/dist/client/managers/BaseManager.d.ts.map +1 -1
- package/dist/client/managers/BaseManager.js +11 -5
- package/dist/client/managers/BaseManager.js.map +1 -1
- package/dist/client/managers/RequestManager.d.ts +2 -2
- package/dist/client/managers/RequestManager.d.ts.map +1 -1
- package/dist/client/managers/RequestManager.js +25 -12
- package/dist/client/managers/RequestManager.js.map +1 -1
- package/dist/config/Config.d.ts +155 -0
- package/dist/config/Config.d.ts.map +1 -0
- package/dist/config/Config.js +215 -0
- package/dist/config/Config.js.map +1 -0
- package/dist/config/ConfigurationSchema.d.ts +21 -21
- package/dist/config/ConfigurationSchema.d.ts.map +1 -1
- package/dist/config/ConfigurationSchema.js +19 -2
- package/dist/config/ConfigurationSchema.js.map +1 -1
- package/dist/config/ServerConfiguration.d.ts +2 -1
- package/dist/config/ServerConfiguration.d.ts.map +1 -1
- package/dist/config/ServerConfiguration.js +50 -41
- package/dist/config/ServerConfiguration.js.map +1 -1
- package/dist/docs/DocumentationGenerator.d.ts +9 -8
- package/dist/docs/DocumentationGenerator.d.ts.map +1 -1
- package/dist/docs/DocumentationGenerator.js +10 -7
- package/dist/docs/DocumentationGenerator.js.map +1 -1
- package/dist/docs/MarkdownFormatter.d.ts.map +1 -1
- package/dist/docs/MarkdownFormatter.js +3 -2
- package/dist/docs/MarkdownFormatter.js.map +1 -1
- package/dist/dxt-entry.cjs +81 -0
- package/dist/dxt-entry.js +15 -14
- package/dist/dxt-entry.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -21
- package/dist/index.js.map +1 -1
- package/dist/performance/MetricsCollector.d.ts +13 -7
- package/dist/performance/MetricsCollector.d.ts.map +1 -1
- package/dist/performance/MetricsCollector.js +69 -27
- package/dist/performance/MetricsCollector.js.map +1 -1
- package/dist/performance/PerformanceAnalytics.d.ts +8 -2
- package/dist/performance/PerformanceAnalytics.d.ts.map +1 -1
- package/dist/performance/PerformanceAnalytics.js +17 -47
- package/dist/performance/PerformanceAnalytics.js.map +1 -1
- package/dist/performance/PerformanceMonitor.d.ts +2 -1
- package/dist/performance/PerformanceMonitor.d.ts.map +1 -1
- package/dist/performance/PerformanceMonitor.js +12 -13
- package/dist/performance/PerformanceMonitor.js.map +1 -1
- package/dist/performance/index.d.ts +2 -2
- package/dist/performance/index.d.ts.map +1 -1
- package/dist/security/AISecurityScanner.d.ts +1 -0
- package/dist/security/AISecurityScanner.d.ts.map +1 -1
- package/dist/security/AISecurityScanner.js +22 -12
- package/dist/security/AISecurityScanner.js.map +1 -1
- package/dist/security/AutomatedRemediation.d.ts +4 -3
- package/dist/security/AutomatedRemediation.d.ts.map +1 -1
- package/dist/security/AutomatedRemediation.js +46 -15
- package/dist/security/AutomatedRemediation.js.map +1 -1
- package/dist/security/InputValidator.d.ts +13 -9
- package/dist/security/InputValidator.d.ts.map +1 -1
- package/dist/security/InputValidator.js +4 -2
- package/dist/security/InputValidator.js.map +1 -1
- package/dist/security/SecurityCIPipeline.d.ts +1 -1
- package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
- package/dist/security/SecurityCIPipeline.js +38 -29
- package/dist/security/SecurityCIPipeline.js.map +1 -1
- package/dist/security/SecurityConfig.d.ts +3 -3
- package/dist/security/SecurityConfig.d.ts.map +1 -1
- package/dist/security/SecurityConfig.js +13 -9
- package/dist/security/SecurityConfig.js.map +1 -1
- package/dist/security/SecurityConfigManager.d.ts +2 -2
- package/dist/security/SecurityConfigManager.d.ts.map +1 -1
- package/dist/security/SecurityConfigManager.js +20 -15
- package/dist/security/SecurityConfigManager.js.map +1 -1
- package/dist/security/SecurityMonitoring.d.ts +2 -2
- package/dist/security/SecurityMonitoring.d.ts.map +1 -1
- package/dist/security/SecurityMonitoring.js +19 -17
- package/dist/security/SecurityMonitoring.js.map +1 -1
- package/dist/security/SecurityReviewer.d.ts.map +1 -1
- package/dist/security/SecurityReviewer.js +10 -7
- package/dist/security/SecurityReviewer.js.map +1 -1
- package/dist/security/index.d.ts +24 -23
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +52 -23
- package/dist/security/index.js.map +1 -1
- package/dist/server/ConnectionTester.d.ts +12 -4
- package/dist/server/ConnectionTester.d.ts.map +1 -1
- package/dist/server/ConnectionTester.js +96 -22
- package/dist/server/ConnectionTester.js.map +1 -1
- package/dist/server/ToolRegistry.d.ts +2 -2
- package/dist/server/ToolRegistry.d.ts.map +1 -1
- package/dist/server/ToolRegistry.js +10 -5
- package/dist/server/ToolRegistry.js.map +1 -1
- package/dist/tools/BaseToolManager.d.ts +47 -11
- package/dist/tools/BaseToolManager.d.ts.map +1 -1
- package/dist/tools/BaseToolManager.js +168 -29
- package/dist/tools/BaseToolManager.js.map +1 -1
- package/dist/tools/auth.d.ts +16 -10
- package/dist/tools/auth.d.ts.map +1 -1
- package/dist/tools/auth.js +3 -2
- package/dist/tools/auth.js.map +1 -1
- package/dist/tools/cache.d.ts +30 -30
- package/dist/tools/cache.d.ts.map +1 -1
- package/dist/tools/cache.js +1 -6
- package/dist/tools/cache.js.map +1 -1
- package/dist/tools/comments.d.ts +20 -20
- package/dist/tools/comments.d.ts.map +1 -1
- package/dist/tools/comments.js +16 -9
- package/dist/tools/comments.js.map +1 -1
- package/dist/tools/media.d.ts +18 -16
- package/dist/tools/media.d.ts.map +1 -1
- package/dist/tools/media.js +16 -15
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/pages.d.ts +19 -17
- package/dist/tools/pages.d.ts.map +1 -1
- package/dist/tools/pages.js +16 -12
- package/dist/tools/pages.js.map +1 -1
- package/dist/tools/performance.d.ts +11 -1
- package/dist/tools/performance.d.ts.map +1 -1
- package/dist/tools/performance.js +67 -34
- package/dist/tools/performance.js.map +1 -1
- package/dist/tools/posts/PostHandlers.d.ts +46 -0
- package/dist/tools/posts/PostHandlers.d.ts.map +1 -0
- package/dist/tools/posts/PostHandlers.js +400 -0
- package/dist/tools/posts/PostHandlers.js.map +1 -0
- package/dist/tools/posts/PostToolDefinitions.d.ts +37 -0
- package/dist/tools/posts/PostToolDefinitions.d.ts.map +1 -0
- package/dist/tools/posts/PostToolDefinitions.js +236 -0
- package/dist/tools/posts/PostToolDefinitions.js.map +1 -0
- package/dist/tools/posts/index.d.ts +138 -0
- package/dist/tools/posts/index.d.ts.map +1 -0
- package/dist/tools/posts/index.js +163 -0
- package/dist/tools/posts/index.js.map +1 -0
- package/dist/tools/posts.d.ts +10 -246
- package/dist/tools/posts.d.ts.map +1 -1
- package/dist/tools/posts.js +11 -723
- package/dist/tools/posts.js.map +1 -1
- package/dist/tools/site.d.ts +19 -18
- package/dist/tools/site.d.ts.map +1 -1
- package/dist/tools/site.js +14 -10
- package/dist/tools/site.js.map +1 -1
- package/dist/tools/taxonomies.d.ts +23 -24
- package/dist/tools/taxonomies.d.ts.map +1 -1
- package/dist/tools/taxonomies.js +24 -18
- package/dist/tools/taxonomies.js.map +1 -1
- package/dist/tools/users.d.ts +20 -15
- package/dist/tools/users.d.ts.map +1 -1
- package/dist/tools/users.js +12 -8
- package/dist/tools/users.js.map +1 -1
- package/dist/types/client.d.ts +48 -41
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/client.js +30 -5
- package/dist/types/client.js.map +1 -1
- package/dist/types/enhanced.d.ts +237 -0
- package/dist/types/enhanced.d.ts.map +1 -0
- package/dist/types/enhanced.js +49 -0
- package/dist/types/enhanced.js.map +1 -0
- package/dist/types/index.d.ts +15 -12
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/mcp.d.ts +12 -12
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/types/requests.d.ts +322 -0
- package/dist/types/requests.d.ts.map +1 -0
- package/dist/types/requests.js +8 -0
- package/dist/types/requests.js.map +1 -0
- package/dist/types/tools.d.ts +506 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +8 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/types/wordpress.d.ts +43 -15
- package/dist/types/wordpress.d.ts.map +1 -1
- package/dist/types/wordpress.js +8 -1
- package/dist/types/wordpress.js.map +1 -1
- package/dist/utils/debug.d.ts +19 -11
- package/dist/utils/debug.d.ts.map +1 -1
- package/dist/utils/debug.js +46 -10
- package/dist/utils/debug.js.map +1 -1
- package/dist/utils/enhancedError.d.ts +8 -8
- package/dist/utils/enhancedError.d.ts.map +1 -1
- package/dist/utils/enhancedError.js.map +1 -1
- package/dist/utils/error.d.ts +2 -4
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +42 -5
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/logger.d.ts +106 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +280 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/streaming.d.ts +9 -9
- package/dist/utils/streaming.d.ts.map +1 -1
- package/dist/utils/streaming.js +71 -52
- package/dist/utils/streaming.js.map +1 -1
- package/dist/utils/toolWrapper.d.ts +9 -7
- package/dist/utils/toolWrapper.d.ts.map +1 -1
- package/dist/utils/toolWrapper.js.map +1 -1
- package/dist/utils/validation/core.d.ts +21 -0
- package/dist/utils/validation/core.d.ts.map +1 -0
- package/dist/utils/validation/core.js +71 -0
- package/dist/utils/validation/core.js.map +1 -0
- package/dist/utils/validation/index.d.ts +25 -0
- package/dist/utils/validation/index.d.ts.map +1 -0
- package/dist/utils/validation/index.js +29 -0
- package/dist/utils/validation/index.js.map +1 -0
- package/dist/utils/validation/network.d.ts +19 -0
- package/dist/utils/validation/network.d.ts.map +1 -0
- package/dist/utils/validation/network.js +93 -0
- package/dist/utils/validation/network.js.map +1 -0
- package/dist/utils/validation/rateLimit.d.ts +21 -0
- package/dist/utils/validation/rateLimit.d.ts.map +1 -0
- package/dist/utils/validation/rateLimit.js +43 -0
- package/dist/utils/validation/rateLimit.js.map +1 -0
- package/dist/utils/validation/security.d.ts +29 -0
- package/dist/utils/validation/security.d.ts.map +1 -0
- package/dist/utils/validation/security.js +327 -0
- package/dist/utils/validation/security.js.map +1 -0
- package/dist/utils/validation/wordpress.d.ts +31 -0
- package/dist/utils/validation/wordpress.d.ts.map +1 -0
- package/dist/utils/validation/wordpress.js +146 -0
- package/dist/utils/validation/wordpress.js.map +1 -0
- package/dist/utils/validation.d.ts +13 -82
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +25 -343
- package/dist/utils/validation.js.map +1 -1
- package/docs/BADGE_UPDATES.md +132 -0
- package/docs/CI_CD_IMPROVEMENTS.md +191 -0
- package/docs/INCREMENTAL_COVERAGE.md +183 -0
- package/docs/api/README.md +3 -1
- package/docs/api/openapi.json +5 -1
- package/docs/api/summary.json +1 -1
- package/docs/api/tools/wp_create_post.md +12 -14
- package/docs/examples/claude-desktop-config.md +1 -1
- package/docs/examples/docker-production.md +100 -93
- package/docs/examples/multi-site-setup.md +5 -4
- package/docs/examples/single-site-setup.md +3 -4
- package/docs/examples/use-case-workflows.md +4 -5
- package/docs/integrations/claude-desktop.md +31 -31
- package/docs/integrations/cline.md +4 -4
- package/docs/integrations/vs-code.md +9 -8
- package/docs/user-guides/SMITHERY_SETUP.md +10 -10
- package/package.json +44 -25
- package/src/cache/CacheInvalidation.ts +12 -5
- package/src/cache/CacheManager.ts +18 -15
- package/src/cache/HttpCacheWrapper.ts +30 -59
- package/src/cache/__tests__/HttpCacheWrapper.test.ts +6 -5
- package/src/cache/index.ts +3 -14
- package/src/client/CachedWordPressClient.ts +32 -30
- package/src/client/MockWordPressClient.ts +4 -2
- package/src/client/api.ts +186 -64
- package/src/client/auth.ts +15 -40
- package/src/client/managers/AuthenticationManager.ts +337 -77
- package/src/client/managers/BaseManager.ts +18 -30
- package/src/client/managers/RequestManager.ts +39 -44
- package/src/config/Config.ts +308 -0
- package/src/config/ConfigurationSchema.ts +23 -2
- package/src/config/ServerConfiguration.ts +51 -47
- package/src/docs/DocumentationGenerator.ts +50 -39
- package/src/docs/MarkdownFormatter.ts +19 -29
- package/src/dxt-entry.cjs +26 -16
- package/src/dxt-entry.ts +17 -27
- package/src/index.ts +42 -28
- package/src/performance/MetricsCollector.ts +108 -86
- package/src/performance/PerformanceAnalytics.ts +69 -164
- package/src/performance/PerformanceMonitor.ts +32 -47
- package/src/performance/index.ts +2 -10
- package/src/security/AISecurityScanner.ts +22 -12
- package/src/security/AutomatedRemediation.ts +49 -18
- package/src/security/InputValidator.ts +9 -6
- package/src/security/SecurityCIPipeline.ts +53 -37
- package/src/security/SecurityConfig.ts +22 -22
- package/src/security/SecurityConfigManager.ts +23 -19
- package/src/security/SecurityMonitoring.ts +24 -21
- package/src/security/SecurityReviewer.ts +10 -7
- package/src/security/index.ts +64 -29
- package/src/server/ConnectionTester.ts +120 -31
- package/src/server/ToolRegistry.ts +31 -21
- package/src/tools/BaseToolManager.ts +286 -33
- package/src/tools/auth.ts +20 -8
- package/src/tools/cache.ts +5 -15
- package/src/tools/comments.ts +34 -48
- package/src/tools/media.ts +41 -53
- package/src/tools/pages.ts +32 -54
- package/src/tools/performance.ts +141 -176
- package/src/tools/posts/PostHandlers.ts +474 -0
- package/src/tools/posts/PostToolDefinitions.ts +250 -0
- package/src/tools/posts/index.ts +192 -0
- package/src/tools/posts.ts +24 -780
- package/src/tools/site.ts +34 -19
- package/src/tools/taxonomies.ts +41 -57
- package/src/tools/users.ts +28 -16
- package/src/types/client.ts +114 -138
- package/src/types/enhanced.ts +318 -0
- package/src/types/index.ts +51 -30
- package/src/types/mcp.ts +20 -42
- package/src/types/requests.ts +378 -0
- package/src/types/tools.ts +608 -0
- package/src/types/wordpress.ts +56 -34
- package/src/utils/debug.ts +77 -59
- package/src/utils/enhancedError.ts +8 -8
- package/src/utils/error.ts +53 -31
- package/src/utils/logger.ts +351 -0
- package/src/utils/streaming.ts +86 -68
- package/src/utils/toolWrapper.ts +10 -12
- package/src/utils/validation/core.ts +108 -0
- package/src/utils/validation/index.ts +36 -0
- package/src/utils/validation/network.ts +132 -0
- package/src/utils/validation/rateLimit.ts +54 -0
- package/src/utils/validation/security.ts +361 -0
- package/src/utils/validation/wordpress.ts +180 -0
- package/src/utils/validation.ts +47 -470
package/src/utils/streaming.ts
CHANGED
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// Node.js streaming imports removed - not currently used but available for future enhancement
|
|
7
|
+
import { sanitizeHtml } from "./validation/security.js";
|
|
7
8
|
|
|
8
9
|
export interface StreamingOptions {
|
|
9
10
|
batchSize?: number;
|
|
10
11
|
delay?: number;
|
|
11
|
-
transformItem?: (item:
|
|
12
|
-
filterItem?: (item:
|
|
12
|
+
transformItem?: (item: unknown) => unknown;
|
|
13
|
+
filterItem?: (item: unknown) => boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface StreamingResult<T> {
|
|
@@ -26,7 +27,7 @@ export interface StreamingResult<T> {
|
|
|
26
27
|
export class DataStreamer<T> {
|
|
27
28
|
private batchSize: number;
|
|
28
29
|
private delay: number;
|
|
29
|
-
private transformItem: ((item: T) =>
|
|
30
|
+
private transformItem: ((item: T) => unknown) | undefined;
|
|
30
31
|
private filterItem: ((item: T) => boolean) | undefined;
|
|
31
32
|
|
|
32
33
|
constructor(options: StreamingOptions = {}) {
|
|
@@ -57,14 +58,14 @@ export class DataStreamer<T> {
|
|
|
57
58
|
|
|
58
59
|
// Apply transformation if provided
|
|
59
60
|
const transformedBatch = this.transformItem
|
|
60
|
-
? processedBatch.map((item:
|
|
61
|
+
? processedBatch.map((item: unknown) => this.transformItem!(item as T))
|
|
61
62
|
: processedBatch;
|
|
62
63
|
|
|
63
64
|
processed += batch.length;
|
|
64
65
|
const hasMore = processed < total;
|
|
65
66
|
|
|
66
67
|
yield {
|
|
67
|
-
data: transformedBatch,
|
|
68
|
+
data: transformedBatch as U[],
|
|
68
69
|
hasMore,
|
|
69
70
|
cursor: hasMore ? String(i + this.batchSize) : undefined,
|
|
70
71
|
total,
|
|
@@ -103,13 +104,13 @@ export class DataStreamer<T> {
|
|
|
103
104
|
|
|
104
105
|
// Apply transformation if provided
|
|
105
106
|
const transformedData = this.transformItem
|
|
106
|
-
? processedData.map((item:
|
|
107
|
+
? processedData.map((item: unknown) => this.transformItem!(item as T))
|
|
107
108
|
: processedData;
|
|
108
109
|
|
|
109
110
|
totalProcessed += result.data.length;
|
|
110
111
|
|
|
111
112
|
yield {
|
|
112
|
-
data: transformedData,
|
|
113
|
+
data: transformedData as U[],
|
|
113
114
|
hasMore: result.hasMore,
|
|
114
115
|
cursor: result.hasMore ? String(page + 1) : undefined,
|
|
115
116
|
total: undefined, // Unknown for paginated results
|
|
@@ -138,33 +139,39 @@ export class WordPressDataStreamer {
|
|
|
138
139
|
* Streams WordPress posts with author and taxonomy information
|
|
139
140
|
*/
|
|
140
141
|
static async *streamPosts(
|
|
141
|
-
posts:
|
|
142
|
+
posts: unknown[],
|
|
142
143
|
options: {
|
|
143
144
|
includeAuthor?: boolean;
|
|
144
145
|
includeCategories?: boolean;
|
|
145
146
|
includeTags?: boolean;
|
|
146
147
|
batchSize?: number;
|
|
147
148
|
} = {},
|
|
148
|
-
): AsyncGenerator<StreamingResult<
|
|
149
|
-
const streamer = new DataStreamer<
|
|
149
|
+
): AsyncGenerator<StreamingResult<unknown>, void, unknown> {
|
|
150
|
+
const streamer = new DataStreamer<unknown>({
|
|
150
151
|
batchSize: options.batchSize || 20,
|
|
151
|
-
transformItem: (post) =>
|
|
152
|
-
|
|
153
|
-
title
|
|
154
|
-
excerpt
|
|
155
|
-
|
|
156
|
-
:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
152
|
+
transformItem: (post) => {
|
|
153
|
+
const p = post as Record<string, unknown>;
|
|
154
|
+
const title = p.title as Record<string, unknown> | undefined;
|
|
155
|
+
const excerpt = p.excerpt as Record<string, unknown> | undefined;
|
|
156
|
+
return {
|
|
157
|
+
id: p.id,
|
|
158
|
+
title: title?.rendered || "Untitled",
|
|
159
|
+
excerpt: excerpt?.rendered ? sanitizeHtml(String(excerpt.rendered)).substring(0, 150) + "..." : "No excerpt",
|
|
160
|
+
status: p.status,
|
|
161
|
+
date: new Date(String(p.date)).toLocaleDateString(),
|
|
162
|
+
link: p.link,
|
|
163
|
+
author: options.includeAuthor ? p.author : undefined,
|
|
164
|
+
categories: options.includeCategories ? p.categories : undefined,
|
|
165
|
+
tags: options.includeTags ? p.tags : undefined,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
filterItem: (post) => {
|
|
169
|
+
const p = post as Record<string, unknown>;
|
|
170
|
+
return p.status !== "trash";
|
|
171
|
+
}, // Filter out trashed posts
|
|
165
172
|
});
|
|
166
173
|
|
|
167
|
-
const processor = async (batch:
|
|
174
|
+
const processor = async (batch: unknown[]) => {
|
|
168
175
|
// Simulate processing time for large datasets
|
|
169
176
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
170
177
|
return batch;
|
|
@@ -179,27 +186,32 @@ export class WordPressDataStreamer {
|
|
|
179
186
|
* Streams WordPress users with role information
|
|
180
187
|
*/
|
|
181
188
|
static async *streamUsers(
|
|
182
|
-
users:
|
|
189
|
+
users: unknown[],
|
|
183
190
|
options: {
|
|
184
191
|
includeRoles?: boolean;
|
|
185
192
|
includeCapabilities?: boolean;
|
|
186
193
|
batchSize?: number;
|
|
187
194
|
} = {},
|
|
188
|
-
): AsyncGenerator<StreamingResult<
|
|
189
|
-
const streamer = new DataStreamer<
|
|
195
|
+
): AsyncGenerator<StreamingResult<unknown>, void, unknown> {
|
|
196
|
+
const streamer = new DataStreamer<unknown>({
|
|
190
197
|
batchSize: options.batchSize || 30,
|
|
191
|
-
transformItem: (user) =>
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
transformItem: (user) => {
|
|
199
|
+
const u = user as Record<string, unknown>;
|
|
200
|
+
return {
|
|
201
|
+
id: u.id,
|
|
202
|
+
name: u.name || "No name",
|
|
203
|
+
username: u.slug || "unknown",
|
|
204
|
+
email: u.email || "No email",
|
|
205
|
+
roles: options.includeRoles ? u.roles : undefined,
|
|
206
|
+
capabilities: options.includeCapabilities
|
|
207
|
+
? Object.keys((u.capabilities as Record<string, unknown>) || {})
|
|
208
|
+
: undefined,
|
|
209
|
+
registeredDate: u.registered_date ? new Date(String(u.registered_date)).toLocaleDateString() : "Unknown",
|
|
210
|
+
};
|
|
211
|
+
},
|
|
200
212
|
});
|
|
201
213
|
|
|
202
|
-
const processor = async (batch:
|
|
214
|
+
const processor = async (batch: unknown[]) => {
|
|
203
215
|
// Add user processing logic here if needed
|
|
204
216
|
return batch;
|
|
205
217
|
};
|
|
@@ -213,35 +225,40 @@ export class WordPressDataStreamer {
|
|
|
213
225
|
* Streams WordPress comments with moderation status
|
|
214
226
|
*/
|
|
215
227
|
static async *streamComments(
|
|
216
|
-
comments:
|
|
228
|
+
comments: unknown[],
|
|
217
229
|
options: {
|
|
218
230
|
includeAuthor?: boolean;
|
|
219
231
|
includePost?: boolean;
|
|
220
232
|
batchSize?: number;
|
|
221
233
|
} = {},
|
|
222
|
-
): AsyncGenerator<StreamingResult<
|
|
223
|
-
const streamer = new DataStreamer<
|
|
234
|
+
): AsyncGenerator<StreamingResult<unknown>, void, unknown> {
|
|
235
|
+
const streamer = new DataStreamer<unknown>({
|
|
224
236
|
batchSize: options.batchSize || 40,
|
|
225
|
-
transformItem: (comment) =>
|
|
226
|
-
|
|
227
|
-
content
|
|
228
|
-
|
|
229
|
-
:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
transformItem: (comment) => {
|
|
238
|
+
const c = comment as Record<string, unknown>;
|
|
239
|
+
const content = c.content as Record<string, unknown> | undefined;
|
|
240
|
+
return {
|
|
241
|
+
id: c.id,
|
|
242
|
+
content: content?.rendered ? sanitizeHtml(String(content.rendered)).substring(0, 200) + "..." : "No content",
|
|
243
|
+
status: c.status,
|
|
244
|
+
date: new Date(String(c.date)).toLocaleDateString(),
|
|
245
|
+
author: options.includeAuthor
|
|
246
|
+
? {
|
|
247
|
+
name: c.author_name,
|
|
248
|
+
email: c.author_email,
|
|
249
|
+
url: c.author_url,
|
|
250
|
+
}
|
|
251
|
+
: undefined,
|
|
252
|
+
post: options.includePost ? c.post : undefined,
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
filterItem: (comment) => {
|
|
256
|
+
const c = comment as Record<string, unknown>;
|
|
257
|
+
return c.status !== "spam";
|
|
258
|
+
}, // Filter out spam comments
|
|
242
259
|
});
|
|
243
260
|
|
|
244
|
-
const processor = async (batch:
|
|
261
|
+
const processor = async (batch: unknown[]) => {
|
|
245
262
|
// Add comment processing logic here if needed
|
|
246
263
|
return batch;
|
|
247
264
|
};
|
|
@@ -260,7 +277,7 @@ export class StreamingUtils {
|
|
|
260
277
|
* Formats streaming results for display
|
|
261
278
|
*/
|
|
262
279
|
static formatStreamingResponse(
|
|
263
|
-
results: StreamingResult<
|
|
280
|
+
results: StreamingResult<unknown>[],
|
|
264
281
|
type: "posts" | "users" | "comments" | "media" = "posts",
|
|
265
282
|
): string {
|
|
266
283
|
const allData = results.flatMap((result) => result.data);
|
|
@@ -290,12 +307,15 @@ export class StreamingUtils {
|
|
|
290
307
|
|
|
291
308
|
// Format individual items
|
|
292
309
|
allData.forEach((item, index) => {
|
|
293
|
-
|
|
310
|
+
// Type-safe property access
|
|
311
|
+
const itemObj = item as Record<string, unknown>;
|
|
312
|
+
const title = itemObj.title || itemObj.name || (itemObj.content as string)?.substring(0, 50) || "Item";
|
|
313
|
+
response += `${index + 1}. **${title}**\n`;
|
|
294
314
|
|
|
295
|
-
if (
|
|
296
|
-
if (
|
|
297
|
-
if (
|
|
298
|
-
if (
|
|
315
|
+
if (itemObj.excerpt) response += ` 📝 ${itemObj.excerpt}\n`;
|
|
316
|
+
if (itemObj.email) response += ` 📧 ${itemObj.email}\n`;
|
|
317
|
+
if (itemObj.status) response += ` 🏷️ Status: ${itemObj.status}\n`;
|
|
318
|
+
if (itemObj.date) response += ` 📅 Date: ${itemObj.date}\n`;
|
|
299
319
|
|
|
300
320
|
response += "\n";
|
|
301
321
|
});
|
|
@@ -321,7 +341,6 @@ export class StreamingUtils {
|
|
|
321
341
|
|
|
322
342
|
const results: T[] = [];
|
|
323
343
|
let offset = 0;
|
|
324
|
-
let hasMore = true;
|
|
325
344
|
|
|
326
345
|
// Initial load
|
|
327
346
|
const initialBatch = await fetcher(offset, initialLoad);
|
|
@@ -333,11 +352,10 @@ export class StreamingUtils {
|
|
|
333
352
|
}
|
|
334
353
|
|
|
335
354
|
// Progressive loading
|
|
336
|
-
while (
|
|
355
|
+
while (results.length < maxItems) {
|
|
337
356
|
const batch = await fetcher(offset, batchSize);
|
|
338
357
|
|
|
339
358
|
if (batch.length === 0) {
|
|
340
|
-
hasMore = false;
|
|
341
359
|
break;
|
|
342
360
|
}
|
|
343
361
|
|
package/src/utils/toolWrapper.ts
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { getErrorMessage } from "./error.js";
|
|
7
|
+
import type { IWordPressClient } from '../types/client.js';
|
|
8
|
+
import type { BaseToolParams } from '../types/tools.js';
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Wrapper for tool methods that standardizes error handling
|
|
10
12
|
*/
|
|
11
|
-
export function withErrorHandling<T extends
|
|
13
|
+
export function withErrorHandling<T extends readonly unknown[], R>(
|
|
12
14
|
operation: string,
|
|
13
15
|
fn: (...args: T) => Promise<R>,
|
|
14
16
|
): (...args: T) => Promise<R> {
|
|
@@ -24,7 +26,7 @@ export function withErrorHandling<T extends any[], R>(
|
|
|
24
26
|
/**
|
|
25
27
|
* Wrapper for tool methods with validation
|
|
26
28
|
*/
|
|
27
|
-
export function withValidation<T extends
|
|
29
|
+
export function withValidation<T extends readonly unknown[], R>(
|
|
28
30
|
operation: string,
|
|
29
31
|
validator: (...args: T) => void,
|
|
30
32
|
fn: (...args: T) => Promise<R>,
|
|
@@ -43,7 +45,7 @@ export function withValidation<T extends any[], R>(
|
|
|
43
45
|
* Common validation functions
|
|
44
46
|
*/
|
|
45
47
|
export const validators = {
|
|
46
|
-
requireSite: (client:
|
|
48
|
+
requireSite: (client: IWordPressClient | null | undefined, params: BaseToolParams) => {
|
|
47
49
|
if (!client) {
|
|
48
50
|
throw new Error("WordPress client is required");
|
|
49
51
|
}
|
|
@@ -55,13 +57,13 @@ export const validators = {
|
|
|
55
57
|
}
|
|
56
58
|
},
|
|
57
59
|
|
|
58
|
-
requireNonEmpty: (value:
|
|
60
|
+
requireNonEmpty: (value: unknown, fieldName: string) => {
|
|
59
61
|
if (!value || (typeof value === "string" && value.trim() === "")) {
|
|
60
62
|
throw new Error(`${fieldName} cannot be empty`);
|
|
61
63
|
}
|
|
62
64
|
},
|
|
63
65
|
|
|
64
|
-
requireFields: (params:
|
|
66
|
+
requireFields: (params: Record<string, unknown>, fields: readonly string[]) => {
|
|
65
67
|
for (const field of fields) {
|
|
66
68
|
if (params[field] === undefined || params[field] === null) {
|
|
67
69
|
throw new Error(`${field} is required`);
|
|
@@ -74,14 +76,10 @@ export const validators = {
|
|
|
74
76
|
* Decorator for class methods to add error handling
|
|
75
77
|
*/
|
|
76
78
|
export function errorHandler(operation: string) {
|
|
77
|
-
return function (
|
|
78
|
-
target: any,
|
|
79
|
-
propertyKey: string,
|
|
80
|
-
descriptor: PropertyDescriptor,
|
|
81
|
-
) {
|
|
79
|
+
return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
82
80
|
const originalMethod = descriptor.value;
|
|
83
81
|
|
|
84
|
-
descriptor.value = async function (...args:
|
|
82
|
+
descriptor.value = async function (...args: unknown[]) {
|
|
85
83
|
try {
|
|
86
84
|
return await originalMethod.apply(this, args);
|
|
87
85
|
} catch (error) {
|
|
@@ -103,7 +101,7 @@ export function formatSuccessResponse(content: string): string {
|
|
|
103
101
|
/**
|
|
104
102
|
* Helper to format error responses consistently
|
|
105
103
|
*/
|
|
106
|
-
export function formatErrorResponse(operation: string, error:
|
|
104
|
+
export function formatErrorResponse(operation: string, error: unknown): never {
|
|
107
105
|
const message = getErrorMessage(error);
|
|
108
106
|
throw new Error(`${operation}: ${message}`);
|
|
109
107
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Basic validation functions for common data types including IDs, strings, and arrays.
|
|
5
|
+
* These validators form the foundation for more specific validation logic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { WordPressAPIError } from "../../types/client.js";
|
|
9
|
+
import { WordPressId, createWordPressId } from "../../types/enhanced.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validates and sanitizes numeric IDs with comprehensive edge case handling
|
|
13
|
+
* Returns branded WordPress ID type for enhanced type safety
|
|
14
|
+
*/
|
|
15
|
+
export function validateId(id: unknown, fieldName: string = "id"): WordPressId {
|
|
16
|
+
// Handle null/undefined
|
|
17
|
+
if (id === null || id === undefined) {
|
|
18
|
+
throw new WordPressAPIError(`${fieldName} is required`, 400, "MISSING_PARAMETER");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Convert to string first to handle various input types
|
|
22
|
+
const strId = String(id).trim();
|
|
23
|
+
|
|
24
|
+
// Check for empty string after trim
|
|
25
|
+
if (strId === "") {
|
|
26
|
+
throw new WordPressAPIError(`${fieldName} cannot be empty`, 400, "INVALID_PARAMETER");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Handle decimal inputs
|
|
30
|
+
if (strId.includes(".")) {
|
|
31
|
+
throw new WordPressAPIError(`${fieldName} must be a whole number, not a decimal`, 400, "INVALID_PARAMETER");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const numId = parseInt(strId, 10);
|
|
35
|
+
|
|
36
|
+
// Check for NaN
|
|
37
|
+
if (isNaN(numId)) {
|
|
38
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: "${id}" is not a valid number`, 400, "INVALID_PARAMETER");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for negative or zero
|
|
42
|
+
if (numId <= 0) {
|
|
43
|
+
throw new WordPressAPIError(
|
|
44
|
+
`Invalid ${fieldName}: must be a positive number (got ${numId})`,
|
|
45
|
+
400,
|
|
46
|
+
"INVALID_PARAMETER",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for max int32 limit (WordPress database limit)
|
|
51
|
+
if (numId > 2147483647) {
|
|
52
|
+
throw new WordPressAPIError(
|
|
53
|
+
`Invalid ${fieldName}: exceeds maximum allowed value (2147483647)`,
|
|
54
|
+
400,
|
|
55
|
+
"INVALID_PARAMETER",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return createWordPressId(numId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validates string length within bounds
|
|
64
|
+
*/
|
|
65
|
+
export function validateString(
|
|
66
|
+
value: unknown,
|
|
67
|
+
fieldName: string,
|
|
68
|
+
minLength: number = 1,
|
|
69
|
+
maxLength: number = 1000,
|
|
70
|
+
): string {
|
|
71
|
+
if (typeof value !== "string") {
|
|
72
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be a string`, 400, "INVALID_PARAMETER");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const trimmed = value.trim();
|
|
76
|
+
if (trimmed.length < minLength || trimmed.length > maxLength) {
|
|
77
|
+
throw new WordPressAPIError(
|
|
78
|
+
`Invalid ${fieldName}: length must be between ${minLength} and ${maxLength} characters`,
|
|
79
|
+
400,
|
|
80
|
+
"INVALID_PARAMETER",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return trimmed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validates arrays with size constraints and type safety
|
|
89
|
+
*/
|
|
90
|
+
export function validateArray<T>(value: unknown, fieldName: string, minItems: number = 0, maxItems: number = 100): T[] {
|
|
91
|
+
if (!Array.isArray(value)) {
|
|
92
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must be an array`, 400, "INVALID_PARAMETER");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (value.length < minItems) {
|
|
96
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: must have at least ${minItems} items`, 400, "INVALID_PARAMETER");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (value.length > maxItems) {
|
|
100
|
+
throw new WordPressAPIError(
|
|
101
|
+
`Invalid ${fieldName}: cannot have more than ${maxItems} items`,
|
|
102
|
+
400,
|
|
103
|
+
"INVALID_PARAMETER",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return value as T[];
|
|
108
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utilities - Modular Export Index
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports all validation functions from their focused modules
|
|
5
|
+
* for backward compatibility and convenient imports.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Import all validators (backward compatible)
|
|
10
|
+
* import { validateId, validateUrl, validatePostParams } from "./utils/validation";
|
|
11
|
+
*
|
|
12
|
+
* // Or import from specific modules
|
|
13
|
+
* import { validateId } from "./utils/validation/core";
|
|
14
|
+
* import { validateUrl } from "./utils/validation/network";
|
|
15
|
+
* import { validatePostParams } from "./utils/validation/wordpress";
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Core validators - basic data types
|
|
20
|
+
export { validateId, validateString, validateArray } from "./core.js";
|
|
21
|
+
|
|
22
|
+
// Security validators - file and content safety
|
|
23
|
+
export { validateFilePath, validateFileSize, validateMimeType, sanitizeHtml } from "./security.js";
|
|
24
|
+
|
|
25
|
+
// Network validators - URLs, emails, usernames
|
|
26
|
+
export { validateUrl, validateEmail, validateUsername } from "./network.js";
|
|
27
|
+
|
|
28
|
+
// WordPress-specific validators
|
|
29
|
+
export { validatePostStatus, validateSearchQuery, validatePaginationParams, validatePostParams } from "./wordpress.js";
|
|
30
|
+
|
|
31
|
+
// Rate limiting utilities
|
|
32
|
+
export { RateLimiter, authRateLimiter } from "./rateLimit.js";
|
|
33
|
+
|
|
34
|
+
// Re-export type dependencies for convenience
|
|
35
|
+
export type { WordPressId } from "../../types/enhanced.js";
|
|
36
|
+
export { WordPressAPIError } from "../../types/client.js";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Validation functions for network-related data including URLs, email addresses,
|
|
5
|
+
* and username validation with security considerations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { WordPressAPIError } from "../../types/client.js";
|
|
9
|
+
import { config } from "../../config/Config.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validates and sanitizes URLs with enhanced edge case handling
|
|
13
|
+
*/
|
|
14
|
+
export function validateUrl(url: string, fieldName: string = "url"): string {
|
|
15
|
+
// Check for empty or whitespace-only URLs
|
|
16
|
+
const trimmedUrl = url.trim();
|
|
17
|
+
if (!trimmedUrl) {
|
|
18
|
+
throw new WordPressAPIError(`${fieldName} cannot be empty`, 400, "INVALID_PARAMETER");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Remove trailing slashes for consistency
|
|
22
|
+
const cleanUrl = trimmedUrl.replace(/\/+$/, "");
|
|
23
|
+
|
|
24
|
+
// Check for common URL mistakes
|
|
25
|
+
if (!cleanUrl.match(/^https?:\/\//i)) {
|
|
26
|
+
throw new WordPressAPIError(
|
|
27
|
+
`Invalid ${fieldName}: must start with http:// or https:// (got "${cleanUrl}")`,
|
|
28
|
+
400,
|
|
29
|
+
"INVALID_PARAMETER",
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const urlObj = new URL(cleanUrl);
|
|
35
|
+
|
|
36
|
+
// Only allow http and https protocols
|
|
37
|
+
if (!["http:", "https:"].includes(urlObj.protocol)) {
|
|
38
|
+
throw new WordPressAPIError(
|
|
39
|
+
`Invalid ${fieldName}: only HTTP and HTTPS protocols are allowed`,
|
|
40
|
+
400,
|
|
41
|
+
"INVALID_PARAMETER",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate hostname
|
|
46
|
+
if (!urlObj.hostname || urlObj.hostname.length < 3) {
|
|
47
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: hostname is missing or too short`, 400, "INVALID_PARAMETER");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for localhost in production
|
|
51
|
+
if (config().app.isProduction && (urlObj.hostname === "localhost" || urlObj.hostname === "127.0.0.1")) {
|
|
52
|
+
throw new WordPressAPIError(
|
|
53
|
+
`Invalid ${fieldName}: localhost URLs are not allowed in production`,
|
|
54
|
+
400,
|
|
55
|
+
"INVALID_PARAMETER",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Validate port if present
|
|
60
|
+
if (urlObj.port) {
|
|
61
|
+
const port = parseInt(urlObj.port);
|
|
62
|
+
if (port < 1 || port > 65535) {
|
|
63
|
+
throw new WordPressAPIError(
|
|
64
|
+
`Invalid ${fieldName}: port number must be between 1 and 65535`,
|
|
65
|
+
400,
|
|
66
|
+
"INVALID_PARAMETER",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return cleanUrl;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof WordPressAPIError) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
throw new WordPressAPIError(`Invalid ${fieldName}: malformed URL "${cleanUrl}"`, 400, "INVALID_PARAMETER");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates email addresses
|
|
82
|
+
*/
|
|
83
|
+
export function validateEmail(email: string): string {
|
|
84
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
85
|
+
if (!emailRegex.test(email)) {
|
|
86
|
+
throw new WordPressAPIError("Invalid email address format", 400, "INVALID_PARAMETER");
|
|
87
|
+
}
|
|
88
|
+
return email.toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates username format with enhanced security checks
|
|
93
|
+
*/
|
|
94
|
+
export function validateUsername(username: string): string {
|
|
95
|
+
// Trim and check for empty
|
|
96
|
+
const trimmed = username.trim();
|
|
97
|
+
if (!trimmed) {
|
|
98
|
+
throw new WordPressAPIError("Username cannot be empty", 400, "INVALID_PARAMETER");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// WordPress username rules: alphanumeric, space, underscore, hyphen, period, @ symbol
|
|
102
|
+
const usernameRegex = /^[a-zA-Z0-9 _.\-@]+$/;
|
|
103
|
+
if (!usernameRegex.test(trimmed)) {
|
|
104
|
+
throw new WordPressAPIError(
|
|
105
|
+
"Invalid username: can only contain letters, numbers, spaces, and _.-@ symbols",
|
|
106
|
+
400,
|
|
107
|
+
"INVALID_PARAMETER",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Length validation
|
|
112
|
+
if (trimmed.length < 3 || trimmed.length > 60) {
|
|
113
|
+
throw new WordPressAPIError(
|
|
114
|
+
`Invalid username: must be between 3 and 60 characters (got ${trimmed.length})`,
|
|
115
|
+
400,
|
|
116
|
+
"INVALID_PARAMETER",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check for consecutive spaces
|
|
121
|
+
if (/\s{2,}/.test(trimmed)) {
|
|
122
|
+
throw new WordPressAPIError("Invalid username: cannot contain consecutive spaces", 400, "INVALID_PARAMETER");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Security: Prevent common problematic usernames
|
|
126
|
+
const blacklist = ["admin", "root", "wordpress", "wp-admin", "administrator"];
|
|
127
|
+
if (blacklist.includes(trimmed.toLowerCase())) {
|
|
128
|
+
throw new WordPressAPIError(`Username "${trimmed}" is reserved and cannot be used`, 400, "RESERVED_USERNAME");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return trimmed;
|
|
132
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiting Utilities
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory rate limiting for authentication and API requests.
|
|
5
|
+
* For production, consider using Redis or similar distributed cache.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { WordPressAPIError } from "../../types/client.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Rate limiting tracker (simple in-memory implementation)
|
|
12
|
+
* For production, use Redis or similar
|
|
13
|
+
*/
|
|
14
|
+
class RateLimiter {
|
|
15
|
+
private attempts: Map<string, { count: number; resetTime: number }> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private maxAttempts: number = 5,
|
|
19
|
+
private windowMs: number = 60000, // 1 minute
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
check(identifier: string): void {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const record = this.attempts.get(identifier);
|
|
25
|
+
|
|
26
|
+
if (!record || record.resetTime < now) {
|
|
27
|
+
this.attempts.set(identifier, {
|
|
28
|
+
count: 1,
|
|
29
|
+
resetTime: now + this.windowMs,
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (record.count >= this.maxAttempts) {
|
|
35
|
+
const waitTime = Math.ceil((record.resetTime - now) / 1000);
|
|
36
|
+
throw new WordPressAPIError(
|
|
37
|
+
`Rate limit exceeded. Please wait ${waitTime} seconds before trying again.`,
|
|
38
|
+
429,
|
|
39
|
+
"RATE_LIMIT_EXCEEDED",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
record.count++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
reset(identifier: string): void {
|
|
47
|
+
this.attempts.delete(identifier);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Export a default rate limiter for authentication attempts
|
|
52
|
+
export const authRateLimiter = new RateLimiter(5, 300000); // 5 attempts per 5 minutes
|
|
53
|
+
|
|
54
|
+
export { RateLimiter };
|