@firela/billclaw-core 0.1.5 → 0.3.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/dist/billclaw.d.ts +8 -0
  3. package/dist/billclaw.d.ts.map +1 -1
  4. package/dist/billclaw.js +85 -3
  5. package/dist/billclaw.js.map +1 -1
  6. package/dist/config/config-manager.d.ts +127 -0
  7. package/dist/config/config-manager.d.ts.map +1 -0
  8. package/dist/config/config-manager.js +304 -0
  9. package/dist/config/config-manager.js.map +1 -0
  10. package/dist/config/env-loader.d.ts +33 -0
  11. package/dist/config/env-loader.d.ts.map +1 -0
  12. package/dist/config/env-loader.js +115 -0
  13. package/dist/config/env-loader.js.map +1 -0
  14. package/dist/config/index.d.ts +14 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +14 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/errors/errors.d.ts +110 -11
  19. package/dist/errors/errors.d.ts.map +1 -1
  20. package/dist/errors/errors.js +421 -122
  21. package/dist/errors/errors.js.map +1 -1
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/models/config.d.ts +147 -0
  27. package/dist/models/config.d.ts.map +1 -1
  28. package/dist/models/config.js +36 -0
  29. package/dist/models/config.js.map +1 -1
  30. package/dist/oauth/index.d.ts +12 -0
  31. package/dist/oauth/index.d.ts.map +1 -0
  32. package/dist/oauth/index.js +13 -0
  33. package/dist/oauth/index.js.map +1 -0
  34. package/dist/oauth/providers/gmail.d.ts +63 -0
  35. package/dist/oauth/providers/gmail.d.ts.map +1 -0
  36. package/dist/oauth/providers/gmail.js +213 -0
  37. package/dist/oauth/providers/gmail.js.map +1 -0
  38. package/dist/oauth/providers/plaid.d.ts +40 -0
  39. package/dist/oauth/providers/plaid.d.ts.map +1 -0
  40. package/dist/oauth/providers/plaid.js +90 -0
  41. package/dist/oauth/providers/plaid.js.map +1 -0
  42. package/dist/oauth/types.d.ts +102 -0
  43. package/dist/oauth/types.d.ts.map +1 -0
  44. package/dist/oauth/types.js +10 -0
  45. package/dist/oauth/types.js.map +1 -0
  46. package/dist/runtime/types.d.ts +2 -0
  47. package/dist/runtime/types.d.ts.map +1 -1
  48. package/dist/runtime/types.js.map +1 -1
  49. package/dist/sources/gmail/gmail-fetch.d.ts +2 -2
  50. package/dist/sources/gmail/gmail-fetch.d.ts.map +1 -1
  51. package/dist/sources/gmail/gmail-fetch.js +34 -4
  52. package/dist/sources/gmail/gmail-fetch.js.map +1 -1
  53. package/dist/sources/plaid/plaid-sync.d.ts +2 -2
  54. package/dist/sources/plaid/plaid-sync.d.ts.map +1 -1
  55. package/dist/sources/plaid/plaid-sync.js +78 -18
  56. package/dist/sources/plaid/plaid-sync.js.map +1 -1
  57. package/dist/storage/locking.d.ts +4 -0
  58. package/dist/storage/locking.d.ts.map +1 -1
  59. package/dist/storage/locking.js +4 -0
  60. package/dist/storage/locking.js.map +1 -1
  61. package/dist/sync/sync-service.d.ts +2 -2
  62. package/dist/sync/sync-service.d.ts.map +1 -1
  63. package/dist/sync/sync-service.js +6 -1
  64. package/dist/sync/sync-service.js.map +1 -1
  65. package/dist/test-fixtures.d.ts.map +1 -1
  66. package/dist/test-fixtures.js +5 -0
  67. package/dist/test-fixtures.js.map +1 -1
  68. package/dist/webhooks/deduplication.d.ts +117 -0
  69. package/dist/webhooks/deduplication.d.ts.map +1 -0
  70. package/dist/webhooks/deduplication.js +258 -0
  71. package/dist/webhooks/deduplication.js.map +1 -0
  72. package/dist/webhooks/handlers/gmail.d.ts +39 -0
  73. package/dist/webhooks/handlers/gmail.d.ts.map +1 -0
  74. package/dist/webhooks/handlers/gmail.js +56 -0
  75. package/dist/webhooks/handlers/gmail.js.map +1 -0
  76. package/dist/webhooks/handlers/gocardless.d.ts +39 -0
  77. package/dist/webhooks/handlers/gocardless.d.ts.map +1 -0
  78. package/dist/webhooks/handlers/gocardless.js +73 -0
  79. package/dist/webhooks/handlers/gocardless.js.map +1 -0
  80. package/dist/webhooks/handlers/index.d.ts +10 -0
  81. package/dist/webhooks/handlers/index.d.ts.map +1 -0
  82. package/dist/webhooks/handlers/index.js +10 -0
  83. package/dist/webhooks/handlers/index.js.map +1 -0
  84. package/dist/webhooks/handlers/plaid.d.ts +73 -0
  85. package/dist/webhooks/handlers/plaid.d.ts.map +1 -0
  86. package/dist/webhooks/handlers/plaid.js +169 -0
  87. package/dist/webhooks/handlers/plaid.js.map +1 -0
  88. package/dist/webhooks/index.d.ts +15 -0
  89. package/dist/webhooks/index.d.ts.map +1 -0
  90. package/dist/webhooks/index.js +17 -0
  91. package/dist/webhooks/index.js.map +1 -0
  92. package/dist/webhooks/processor.d.ts +76 -0
  93. package/dist/webhooks/processor.d.ts.map +1 -0
  94. package/dist/webhooks/processor.js +116 -0
  95. package/dist/webhooks/processor.js.map +1 -0
  96. package/dist/webhooks/router.d.ts +80 -0
  97. package/dist/webhooks/router.d.ts.map +1 -0
  98. package/dist/webhooks/router.js +107 -0
  99. package/dist/webhooks/router.js.map +1 -0
  100. package/dist/webhooks/security.d.ts +90 -0
  101. package/dist/webhooks/security.d.ts.map +1 -0
  102. package/dist/webhooks/security.js +138 -0
  103. package/dist/webhooks/security.js.map +1 -0
  104. package/dist/webhooks/sync-rate-limiter.d.ts +138 -0
  105. package/dist/webhooks/sync-rate-limiter.d.ts.map +1 -0
  106. package/dist/webhooks/sync-rate-limiter.js +228 -0
  107. package/dist/webhooks/sync-rate-limiter.js.map +1 -0
  108. package/dist/webhooks/types.d.ts +140 -0
  109. package/dist/webhooks/types.d.ts.map +1 -0
  110. package/dist/webhooks/types.js +18 -0
  111. package/dist/webhooks/types.js.map +1 -0
  112. package/package.json +12 -12
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Webhook security layer (P0)
3
+ *
4
+ * Provides replay attack protection, signature verification, and
5
+ * rate limiting coordination for inbound webhooks.
6
+ *
7
+ * Security features:
8
+ * - Replay attack protection using timestamp + nonce validation
9
+ * - HMAC-SHA256 signature verification with timing-safe comparison
10
+ * - Rate limiting coordination
11
+ */
12
+ import type { Logger } from "../errors/errors.js";
13
+ import type { WebhookDeduplication } from "./deduplication.js";
14
+ /**
15
+ * Configuration for webhook security
16
+ */
17
+ export interface WebhookSecurityConfig {
18
+ /**
19
+ * Maximum allowed timestamp age in milliseconds
20
+ * Default: 15 minutes
21
+ */
22
+ maxTimestampAge?: number;
23
+ /**
24
+ * Future timestamp tolerance in milliseconds
25
+ * Default: 5 minutes (to handle clock skew)
26
+ */
27
+ futureTimestampTolerance?: number;
28
+ /**
29
+ * Deduplication cache for nonce tracking
30
+ */
31
+ deduplication: WebhookDeduplication;
32
+ /**
33
+ * Logger instance
34
+ */
35
+ logger: Logger;
36
+ }
37
+ /**
38
+ * Webhook security layer
39
+ *
40
+ * Provides P0 security features for webhook processing.
41
+ */
42
+ export declare class WebhookSecurity {
43
+ private readonly maxTimestampAge;
44
+ private readonly futureTolerance;
45
+ private readonly deduplication;
46
+ private readonly logger;
47
+ constructor(config: WebhookSecurityConfig);
48
+ /**
49
+ * Validate replay protection (timestamp + nonce)
50
+ *
51
+ * Checks that:
52
+ * 1. Timestamp is within valid range (not too old, not too far in future)
53
+ * 2. Nonce has not been used before
54
+ *
55
+ * @param timestamp - Webhook timestamp (Unix milliseconds)
56
+ * @param nonce - Unique webhook identifier
57
+ * @returns True if valid, false if replay detected
58
+ */
59
+ validateReplayProtection(timestamp: number | undefined, nonce: string | undefined): Promise<boolean>;
60
+ /**
61
+ * Verify webhook signature
62
+ *
63
+ * Uses timing-safe comparison to prevent timing attacks.
64
+ *
65
+ * @param payload - Request payload (stringified JSON)
66
+ * @param signature - Signature to verify (format: "sha256=<hex>")
67
+ * @param secret - Webhook secret key
68
+ * @returns True if signature is valid
69
+ */
70
+ verifySignature(payload: string, signature: string | undefined, secret: string): boolean;
71
+ /**
72
+ * Generate HMAC-SHA256 signature for testing
73
+ *
74
+ * @param payload - Payload to sign
75
+ * @param secret - Secret key for HMAC
76
+ * @returns Signature in format "sha256=<hex>"
77
+ */
78
+ generateSignature(payload: string, secret: string): string;
79
+ /**
80
+ * Create a nonce for testing
81
+ *
82
+ * @returns Unique nonce string
83
+ */
84
+ generateNonce(): string;
85
+ }
86
+ /**
87
+ * Create a webhook security instance with default configuration
88
+ */
89
+ export declare function createWebhookSecurity(deduplication: WebhookDeduplication, logger: Logger): WebhookSecurity;
90
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/webhooks/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAE9D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,MAAM,CAAA;IAEjC;;OAEG;IACH,aAAa,EAAE,oBAAoB,CAAA;IAEnC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAQD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;gBAEnB,MAAM,EAAE,qBAAqB;IAOzC;;;;;;;;;;OAUG;IACG,wBAAwB,CAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,OAAO,CAAC;IA4CnB;;;;;;;;;OASG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,MAAM,GACb,OAAO;IA2BV;;;;;;OAMG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAM1D;;;;OAIG;IACH,aAAa,IAAI,MAAM;CAGxB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,oBAAoB,EACnC,MAAM,EAAE,MAAM,GACb,eAAe,CAKjB"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Webhook security layer (P0)
3
+ *
4
+ * Provides replay attack protection, signature verification, and
5
+ * rate limiting coordination for inbound webhooks.
6
+ *
7
+ * Security features:
8
+ * - Replay attack protection using timestamp + nonce validation
9
+ * - HMAC-SHA256 signature verification with timing-safe comparison
10
+ * - Rate limiting coordination
11
+ */
12
+ import * as crypto from "node:crypto";
13
+ /**
14
+ * Default configuration values
15
+ */
16
+ const DEFAULT_MAX_TIMESTAMP_AGE = 15 * 60 * 1000; // 15 minutes
17
+ const DEFAULT_FUTURE_TOLERANCE = 5 * 60 * 1000; // 5 minutes
18
+ /**
19
+ * Webhook security layer
20
+ *
21
+ * Provides P0 security features for webhook processing.
22
+ */
23
+ export class WebhookSecurity {
24
+ maxTimestampAge;
25
+ futureTolerance;
26
+ deduplication;
27
+ logger;
28
+ constructor(config) {
29
+ this.maxTimestampAge = config.maxTimestampAge ?? DEFAULT_MAX_TIMESTAMP_AGE;
30
+ this.futureTolerance = config.futureTimestampTolerance ?? DEFAULT_FUTURE_TOLERANCE;
31
+ this.deduplication = config.deduplication;
32
+ this.logger = config.logger;
33
+ }
34
+ /**
35
+ * Validate replay protection (timestamp + nonce)
36
+ *
37
+ * Checks that:
38
+ * 1. Timestamp is within valid range (not too old, not too far in future)
39
+ * 2. Nonce has not been used before
40
+ *
41
+ * @param timestamp - Webhook timestamp (Unix milliseconds)
42
+ * @param nonce - Unique webhook identifier
43
+ * @returns True if valid, false if replay detected
44
+ */
45
+ async validateReplayProtection(timestamp, nonce) {
46
+ const now = Date.now();
47
+ // Check if timestamp is provided
48
+ if (timestamp === undefined) {
49
+ this.logger.debug?.("Webhook missing timestamp");
50
+ return false;
51
+ }
52
+ // Check if nonce is provided
53
+ if (nonce === undefined) {
54
+ this.logger.debug?.("Webhook missing nonce");
55
+ return false;
56
+ }
57
+ // Check timestamp is not too old
58
+ if (now - timestamp > this.maxTimestampAge) {
59
+ this.logger.warn?.(`Webhook rejected: timestamp too old (${now - timestamp}ms ago)`);
60
+ return false;
61
+ }
62
+ // Check timestamp is not too far in future (allow clock skew)
63
+ if (timestamp - now > this.futureTolerance) {
64
+ this.logger.warn?.(`Webhook rejected: timestamp too far in future (${timestamp - now}ms)`);
65
+ return false;
66
+ }
67
+ // Check nonce has not been used before
68
+ const isProcessed = await this.deduplication.isProcessed(nonce);
69
+ if (isProcessed) {
70
+ this.logger.warn?.(`Webhook rejected: nonce already used (${nonce})`);
71
+ return false;
72
+ }
73
+ // Mark nonce as processed
74
+ await this.deduplication.markProcessed(nonce, 60_000); // 60 second TTL
75
+ return true;
76
+ }
77
+ /**
78
+ * Verify webhook signature
79
+ *
80
+ * Uses timing-safe comparison to prevent timing attacks.
81
+ *
82
+ * @param payload - Request payload (stringified JSON)
83
+ * @param signature - Signature to verify (format: "sha256=<hex>")
84
+ * @param secret - Webhook secret key
85
+ * @returns True if signature is valid
86
+ */
87
+ verifySignature(payload, signature, secret) {
88
+ if (!signature) {
89
+ this.logger.debug?.("Webhook missing signature");
90
+ return false;
91
+ }
92
+ try {
93
+ // Compute expected signature
94
+ const expectedSignature = crypto
95
+ .createHmac("sha256", secret)
96
+ .update(payload)
97
+ .digest("hex");
98
+ // Extract provided signature (remove "sha256=" prefix if present)
99
+ const providedSignature = signature.replace(/^sha256=/i, "");
100
+ // Use timing-safe comparison to prevent timing attacks
101
+ return crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(providedSignature));
102
+ }
103
+ catch (error) {
104
+ this.logger.error?.("Signature verification failed:", error);
105
+ return false;
106
+ }
107
+ }
108
+ /**
109
+ * Generate HMAC-SHA256 signature for testing
110
+ *
111
+ * @param payload - Payload to sign
112
+ * @param secret - Secret key for HMAC
113
+ * @returns Signature in format "sha256=<hex>"
114
+ */
115
+ generateSignature(payload, secret) {
116
+ const hmac = crypto.createHmac("sha256", secret);
117
+ hmac.update(payload);
118
+ return `sha256=${hmac.digest("hex")}`;
119
+ }
120
+ /**
121
+ * Create a nonce for testing
122
+ *
123
+ * @returns Unique nonce string
124
+ */
125
+ generateNonce() {
126
+ return `nonce_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
127
+ }
128
+ }
129
+ /**
130
+ * Create a webhook security instance with default configuration
131
+ */
132
+ export function createWebhookSecurity(deduplication, logger) {
133
+ return new WebhookSecurity({
134
+ deduplication,
135
+ logger,
136
+ });
137
+ }
138
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/webhooks/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Sync rate limiter (P0)
3
+ *
4
+ * Prevents Plaid API bans from webhook-triggered sync floods.
5
+ * Implements separate rate limit buckets for manual vs webhook-triggered syncs.
6
+ *
7
+ * Design:
8
+ * - Separate rate limit buckets: manual vs webhook-triggered
9
+ * - Circuit breaker to disable webhook syncs when rate limit near
10
+ * - Sliding window for accurate rate limiting
11
+ */
12
+ import type { Logger } from "../errors/errors.js";
13
+ /**
14
+ * Rate limit configuration
15
+ */
16
+ export interface RateLimitConfig {
17
+ /**
18
+ * Maximum number of requests allowed
19
+ */
20
+ requests: number;
21
+ /**
22
+ * Time window in milliseconds
23
+ */
24
+ window: number;
25
+ }
26
+ /**
27
+ * Sync rate limiter configuration
28
+ */
29
+ export interface SyncRateLimiterConfig {
30
+ /**
31
+ * Rate limit for manual syncs
32
+ */
33
+ manual: RateLimitConfig;
34
+ /**
35
+ * Rate limit for webhook-triggered syncs
36
+ */
37
+ webhook: RateLimitConfig;
38
+ /**
39
+ * Circuit breaker threshold (0-1)
40
+ * Disable webhook syncs when usage exceeds this ratio
41
+ */
42
+ circuitThreshold?: number;
43
+ /**
44
+ * Logger instance
45
+ */
46
+ logger: Logger;
47
+ }
48
+ /**
49
+ * Sync rate limiter
50
+ *
51
+ * Tracks sync requests and enforces rate limits separately for
52
+ * manual and webhook-triggered syncs.
53
+ */
54
+ export declare class SyncRateLimiter {
55
+ private readonly config;
56
+ private readonly logger;
57
+ private readonly requests;
58
+ private circuitOpen;
59
+ private circuitOpenUntil;
60
+ constructor(config: SyncRateLimiterConfig);
61
+ /**
62
+ * Record a manual sync request
63
+ *
64
+ * @param accountId - Account ID for the sync
65
+ */
66
+ recordManualSync(accountId: string): void;
67
+ /**
68
+ * Record a webhook-triggered sync request
69
+ *
70
+ * @param accountId - Account ID for the sync
71
+ */
72
+ recordWebhookSync(accountId: string): void;
73
+ /**
74
+ * Check if webhook sync is allowed
75
+ *
76
+ * @param accountId - Account ID for the sync
77
+ * @returns True if sync is allowed
78
+ */
79
+ isWebhookSyncAllowed(accountId: string): boolean;
80
+ /**
81
+ * Check if manual sync is allowed
82
+ *
83
+ * @param accountId - Account ID for the sync
84
+ * @returns True if sync is allowed
85
+ */
86
+ isManualSyncAllowed(accountId: string): boolean;
87
+ /**
88
+ * Check if circuit breaker is open
89
+ */
90
+ isCircuitOpen(): boolean;
91
+ /**
92
+ * Open circuit breaker
93
+ *
94
+ * Disables webhook syncs for a cooldown period.
95
+ */
96
+ openCircuit(): void;
97
+ /**
98
+ * Close circuit breaker
99
+ */
100
+ closeCircuit(): void;
101
+ /**
102
+ * Get rate limiter statistics
103
+ */
104
+ getStats(): {
105
+ manualCount: number;
106
+ webhookCount: number;
107
+ circuitOpen: boolean;
108
+ usageRatio: number;
109
+ };
110
+ /**
111
+ * Reset rate limiter (for testing)
112
+ */
113
+ reset(): void;
114
+ /**
115
+ * Clean up old requests
116
+ */
117
+ cleanup(): void;
118
+ /**
119
+ * Record a request
120
+ */
121
+ private recordRequest;
122
+ }
123
+ /**
124
+ * Create a sync rate limiter with default configuration
125
+ */
126
+ export declare function createSyncRateLimiter(logger: Logger, config?: Partial<SyncRateLimiterConfig>): SyncRateLimiter;
127
+ /**
128
+ * In-memory rate limiter for testing
129
+ */
130
+ export declare class InMemoryRateLimiter {
131
+ private readonly config;
132
+ private readonly counters;
133
+ constructor(config: RateLimitConfig, _logger: Logger);
134
+ recordRequest(identifier: string): Promise<void>;
135
+ isRateLimited(identifier: string): Promise<boolean>;
136
+ reset(): void;
137
+ }
138
+ //# sourceMappingURL=sync-rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/webhooks/sync-rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,eAAe,CAAA;IAEvB;;OAEG;IACH,OAAO,EAAE,eAAe,CAAA;IAExB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;CACf;AAyBD;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiD;IACxE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAAI;gBAEhB,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIzC;;;;OAIG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAI1C;;;;;OAKG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAyChD;;;;;OAKG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAmB/C;;OAEG;IACH,aAAa,IAAI,OAAO;IAcxB;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAKnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAMpB;;OAEG;IACH,QAAQ,IAAI;QACV,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;QACpB,UAAU,EAAE,MAAM,CAAA;KACnB;IAsBD;;OAEG;IACH,KAAK,IAAI,IAAI;IAKb;;OAEG;IACH,OAAO,IAAI,IAAI;IAmBf;;OAEG;IACH,OAAO,CAAC,aAAa;CAWtB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,GACtC,eAAe,CAQjB;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAI5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAGlC,MAAM,EAAE,eAAe,EACxC,OAAO,EAAE,MAAM;IAGX,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKhD,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKzD,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Sync rate limiter (P0)
3
+ *
4
+ * Prevents Plaid API bans from webhook-triggered sync floods.
5
+ * Implements separate rate limit buckets for manual vs webhook-triggered syncs.
6
+ *
7
+ * Design:
8
+ * - Separate rate limit buckets: manual vs webhook-triggered
9
+ * - Circuit breaker to disable webhook syncs when rate limit near
10
+ * - Sliding window for accurate rate limiting
11
+ */
12
+ /**
13
+ * Default configuration values
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ manual: {
17
+ requests: 10,
18
+ window: 60_000, // 1 minute
19
+ },
20
+ webhook: {
21
+ requests: 3,
22
+ window: 60_000, // 1 minute
23
+ },
24
+ circuitThreshold: 0.8, // 80%
25
+ };
26
+ /**
27
+ * Sync rate limiter
28
+ *
29
+ * Tracks sync requests and enforces rate limits separately for
30
+ * manual and webhook-triggered syncs.
31
+ */
32
+ export class SyncRateLimiter {
33
+ config;
34
+ logger;
35
+ requests = [];
36
+ circuitOpen = false;
37
+ circuitOpenUntil = 0;
38
+ constructor(config) {
39
+ this.config = { ...DEFAULT_CONFIG, ...config };
40
+ this.logger = config.logger;
41
+ }
42
+ /**
43
+ * Record a manual sync request
44
+ *
45
+ * @param accountId - Account ID for the sync
46
+ */
47
+ recordManualSync(accountId) {
48
+ this.recordRequest("manual", accountId);
49
+ }
50
+ /**
51
+ * Record a webhook-triggered sync request
52
+ *
53
+ * @param accountId - Account ID for the sync
54
+ */
55
+ recordWebhookSync(accountId) {
56
+ this.recordRequest("webhook", accountId);
57
+ }
58
+ /**
59
+ * Check if webhook sync is allowed
60
+ *
61
+ * @param accountId - Account ID for the sync
62
+ * @returns True if sync is allowed
63
+ */
64
+ isWebhookSyncAllowed(accountId) {
65
+ // Check circuit breaker
66
+ if (this.isCircuitOpen()) {
67
+ this.logger.warn?.(`Webhook sync blocked for ${accountId}: circuit breaker open`);
68
+ return false;
69
+ }
70
+ // Check rate limit
71
+ const now = Date.now();
72
+ const windowStart = now - this.config.webhook.window;
73
+ // Count webhook requests in window
74
+ const webhookCount = this.requests.filter((r) => r.type === "webhook" && r.timestamp >= windowStart).length;
75
+ if (webhookCount >= this.config.webhook.requests) {
76
+ this.logger.warn?.(`Webhook sync blocked for ${accountId}: rate limit exceeded (${webhookCount}/${this.config.webhook.requests})`);
77
+ return false;
78
+ }
79
+ // Check if we should open circuit breaker
80
+ const totalCount = this.requests.filter((r) => r.timestamp >= windowStart).length;
81
+ const totalLimit = this.config.manual.requests + this.config.webhook.requests;
82
+ const usageRatio = totalCount / totalLimit;
83
+ if (usageRatio >= this.config.circuitThreshold) {
84
+ this.openCircuit();
85
+ this.logger.warn?.(`Circuit breaker opened: usage at ${Math.round(usageRatio * 100)}%`);
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+ /**
91
+ * Check if manual sync is allowed
92
+ *
93
+ * @param accountId - Account ID for the sync
94
+ * @returns True if sync is allowed
95
+ */
96
+ isManualSyncAllowed(accountId) {
97
+ const now = Date.now();
98
+ const windowStart = now - this.config.manual.window;
99
+ // Count manual requests in window
100
+ const manualCount = this.requests.filter((r) => r.type === "manual" && r.timestamp >= windowStart).length;
101
+ if (manualCount >= this.config.manual.requests) {
102
+ this.logger.warn?.(`Manual sync blocked for ${accountId}: rate limit exceeded (${manualCount}/${this.config.manual.requests})`);
103
+ return false;
104
+ }
105
+ return true;
106
+ }
107
+ /**
108
+ * Check if circuit breaker is open
109
+ */
110
+ isCircuitOpen() {
111
+ if (!this.circuitOpen) {
112
+ return false;
113
+ }
114
+ // Check if circuit should close
115
+ if (Date.now() > this.circuitOpenUntil) {
116
+ this.closeCircuit();
117
+ return false;
118
+ }
119
+ return true;
120
+ }
121
+ /**
122
+ * Open circuit breaker
123
+ *
124
+ * Disables webhook syncs for a cooldown period.
125
+ */
126
+ openCircuit() {
127
+ this.circuitOpen = true;
128
+ this.circuitOpenUntil = Date.now() + this.config.manual.window; // Open for 1 window period
129
+ }
130
+ /**
131
+ * Close circuit breaker
132
+ */
133
+ closeCircuit() {
134
+ this.circuitOpen = false;
135
+ this.circuitOpenUntil = 0;
136
+ this.logger.info?.("Circuit breaker closed");
137
+ }
138
+ /**
139
+ * Get rate limiter statistics
140
+ */
141
+ getStats() {
142
+ const now = Date.now();
143
+ const windowStart = now - this.config.manual.window;
144
+ const manualCount = this.requests.filter((r) => r.type === "manual" && r.timestamp >= windowStart).length;
145
+ const webhookCount = this.requests.filter((r) => r.type === "webhook" && r.timestamp >= windowStart).length;
146
+ const totalLimit = this.config.manual.requests + this.config.webhook.requests;
147
+ const usageRatio = (manualCount + webhookCount) / totalLimit;
148
+ return {
149
+ manualCount,
150
+ webhookCount,
151
+ circuitOpen: this.isCircuitOpen(),
152
+ usageRatio,
153
+ };
154
+ }
155
+ /**
156
+ * Reset rate limiter (for testing)
157
+ */
158
+ reset() {
159
+ this.requests.splice(0, this.requests.length);
160
+ this.closeCircuit();
161
+ }
162
+ /**
163
+ * Clean up old requests
164
+ */
165
+ cleanup() {
166
+ const now = Date.now();
167
+ const windowStart = now - this.config.manual.window;
168
+ const initialLength = this.requests.length;
169
+ const toKeep = [];
170
+ for (const req of this.requests) {
171
+ if (req.timestamp >= windowStart) {
172
+ toKeep.push(req);
173
+ }
174
+ }
175
+ this.requests.splice(0, this.requests.length, ...toKeep);
176
+ const removed = initialLength - this.requests.length;
177
+ if (removed > 0) {
178
+ this.logger.debug?.(`Cleaned up ${removed} old request entries`);
179
+ }
180
+ }
181
+ /**
182
+ * Record a request
183
+ */
184
+ recordRequest(type, _accountId) {
185
+ this.requests.push({
186
+ timestamp: Date.now(),
187
+ type,
188
+ });
189
+ // Cleanup old entries periodically
190
+ if (this.requests.length > 1000) {
191
+ this.cleanup();
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Create a sync rate limiter with default configuration
197
+ */
198
+ export function createSyncRateLimiter(logger, config) {
199
+ return new SyncRateLimiter({
200
+ manual: { requests: 10, window: 60_000 },
201
+ webhook: { requests: 3, window: 60_000 },
202
+ circuitThreshold: 0.8,
203
+ logger,
204
+ ...config,
205
+ });
206
+ }
207
+ /**
208
+ * In-memory rate limiter for testing
209
+ */
210
+ export class InMemoryRateLimiter {
211
+ config;
212
+ counters = new Map();
213
+ constructor(config, _logger) {
214
+ this.config = config;
215
+ }
216
+ async recordRequest(identifier) {
217
+ const count = (this.counters.get(identifier) || 0) + 1;
218
+ this.counters.set(identifier, count);
219
+ }
220
+ async isRateLimited(identifier) {
221
+ const count = this.counters.get(identifier) || 0;
222
+ return count >= this.config.requests;
223
+ }
224
+ reset() {
225
+ this.counters.clear();
226
+ }
227
+ }
228
+ //# sourceMappingURL=sync-rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-rate-limiter.js","sourceRoot":"","sources":["../../src/webhooks/sync-rate-limiter.ts"],"names":[],"mappings":"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"}