@firela/billclaw-core 0.1.4 → 0.2.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 (96) 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 +51 -1
  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/index.d.ts +4 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +7 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/models/config.d.ts +147 -0
  23. package/dist/models/config.d.ts.map +1 -1
  24. package/dist/models/config.js +36 -0
  25. package/dist/models/config.js.map +1 -1
  26. package/dist/oauth/index.d.ts +12 -0
  27. package/dist/oauth/index.d.ts.map +1 -0
  28. package/dist/oauth/index.js +13 -0
  29. package/dist/oauth/index.js.map +1 -0
  30. package/dist/oauth/providers/gmail.d.ts +63 -0
  31. package/dist/oauth/providers/gmail.d.ts.map +1 -0
  32. package/dist/oauth/providers/gmail.js +213 -0
  33. package/dist/oauth/providers/gmail.js.map +1 -0
  34. package/dist/oauth/providers/plaid.d.ts +40 -0
  35. package/dist/oauth/providers/plaid.d.ts.map +1 -0
  36. package/dist/oauth/providers/plaid.js +90 -0
  37. package/dist/oauth/providers/plaid.js.map +1 -0
  38. package/dist/oauth/types.d.ts +102 -0
  39. package/dist/oauth/types.d.ts.map +1 -0
  40. package/dist/oauth/types.js +10 -0
  41. package/dist/oauth/types.js.map +1 -0
  42. package/dist/runtime/types.d.ts +2 -0
  43. package/dist/runtime/types.d.ts.map +1 -1
  44. package/dist/runtime/types.js.map +1 -1
  45. package/dist/storage/locking.d.ts +4 -0
  46. package/dist/storage/locking.d.ts.map +1 -1
  47. package/dist/storage/locking.js +4 -0
  48. package/dist/storage/locking.js.map +1 -1
  49. package/dist/test-fixtures.d.ts.map +1 -1
  50. package/dist/test-fixtures.js +5 -0
  51. package/dist/test-fixtures.js.map +1 -1
  52. package/dist/webhooks/deduplication.d.ts +117 -0
  53. package/dist/webhooks/deduplication.d.ts.map +1 -0
  54. package/dist/webhooks/deduplication.js +258 -0
  55. package/dist/webhooks/deduplication.js.map +1 -0
  56. package/dist/webhooks/handlers/gmail.d.ts +39 -0
  57. package/dist/webhooks/handlers/gmail.d.ts.map +1 -0
  58. package/dist/webhooks/handlers/gmail.js +56 -0
  59. package/dist/webhooks/handlers/gmail.js.map +1 -0
  60. package/dist/webhooks/handlers/gocardless.d.ts +39 -0
  61. package/dist/webhooks/handlers/gocardless.d.ts.map +1 -0
  62. package/dist/webhooks/handlers/gocardless.js +73 -0
  63. package/dist/webhooks/handlers/gocardless.js.map +1 -0
  64. package/dist/webhooks/handlers/index.d.ts +10 -0
  65. package/dist/webhooks/handlers/index.d.ts.map +1 -0
  66. package/dist/webhooks/handlers/index.js +10 -0
  67. package/dist/webhooks/handlers/index.js.map +1 -0
  68. package/dist/webhooks/handlers/plaid.d.ts +73 -0
  69. package/dist/webhooks/handlers/plaid.d.ts.map +1 -0
  70. package/dist/webhooks/handlers/plaid.js +169 -0
  71. package/dist/webhooks/handlers/plaid.js.map +1 -0
  72. package/dist/webhooks/index.d.ts +15 -0
  73. package/dist/webhooks/index.d.ts.map +1 -0
  74. package/dist/webhooks/index.js +17 -0
  75. package/dist/webhooks/index.js.map +1 -0
  76. package/dist/webhooks/processor.d.ts +76 -0
  77. package/dist/webhooks/processor.d.ts.map +1 -0
  78. package/dist/webhooks/processor.js +116 -0
  79. package/dist/webhooks/processor.js.map +1 -0
  80. package/dist/webhooks/router.d.ts +80 -0
  81. package/dist/webhooks/router.d.ts.map +1 -0
  82. package/dist/webhooks/router.js +107 -0
  83. package/dist/webhooks/router.js.map +1 -0
  84. package/dist/webhooks/security.d.ts +90 -0
  85. package/dist/webhooks/security.d.ts.map +1 -0
  86. package/dist/webhooks/security.js +138 -0
  87. package/dist/webhooks/security.js.map +1 -0
  88. package/dist/webhooks/sync-rate-limiter.d.ts +138 -0
  89. package/dist/webhooks/sync-rate-limiter.d.ts.map +1 -0
  90. package/dist/webhooks/sync-rate-limiter.js +228 -0
  91. package/dist/webhooks/sync-rate-limiter.js.map +1 -0
  92. package/dist/webhooks/types.d.ts +140 -0
  93. package/dist/webhooks/types.d.ts.map +1 -0
  94. package/dist/webhooks/types.js +18 -0
  95. package/dist/webhooks/types.js.map +1 -0
  96. package/package.json +12 -12
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Webhook router registry
3
+ *
4
+ * Registry pattern for managing webhook handlers.
5
+ * Provides handler registration and lookup functionality.
6
+ */
7
+ /**
8
+ * Webhook router
9
+ *
10
+ * Registry for webhook handlers with routing functionality.
11
+ */
12
+ export class WebhookRouter {
13
+ handlers = new Map();
14
+ logger;
15
+ constructor(options) {
16
+ this.logger = options.logger;
17
+ }
18
+ /**
19
+ * Register a webhook handler for a specific source
20
+ *
21
+ * @param source - Webhook source identifier
22
+ * @param handler - Handler implementation
23
+ */
24
+ register(source, handler) {
25
+ if (this.handlers.has(source)) {
26
+ this.logger.warn?.(`Handler already registered for ${source}, replacing`);
27
+ }
28
+ this.handlers.set(source, handler);
29
+ this.logger.info?.(`Registered webhook handler for ${source}`);
30
+ }
31
+ /**
32
+ * Unregister a handler for a specific source
33
+ *
34
+ * @param source - Webhook source identifier
35
+ */
36
+ unregister(source) {
37
+ if (this.handlers.delete(source)) {
38
+ this.logger.info?.(`Unregistered webhook handler for ${source}`);
39
+ }
40
+ }
41
+ /**
42
+ * Get handler by source
43
+ *
44
+ * @param source - Webhook source identifier
45
+ * @returns Handler or undefined if not found
46
+ */
47
+ getHandler(source) {
48
+ return this.handlers.get(source);
49
+ }
50
+ /**
51
+ * Check if handler is registered for source
52
+ *
53
+ * @param source - Webhook source identifier
54
+ * @returns True if handler exists
55
+ */
56
+ hasHandler(source) {
57
+ return this.handlers.has(source);
58
+ }
59
+ /**
60
+ * Get all registered sources
61
+ *
62
+ * @returns Array of registered source identifiers
63
+ */
64
+ getSources() {
65
+ return Array.from(this.handlers.keys());
66
+ }
67
+ /**
68
+ * Route request to appropriate handler
69
+ *
70
+ * @param request - Webhook request
71
+ * @returns Response from handler or error if not found
72
+ */
73
+ async route(request) {
74
+ const handler = this.handlers.get(request.source);
75
+ if (!handler) {
76
+ this.logger.warn?.(`No handler registered for source: ${request.source}`);
77
+ return {
78
+ status: 400,
79
+ body: {
80
+ received: false,
81
+ error: `No handler registered for source: ${request.source}`,
82
+ },
83
+ };
84
+ }
85
+ return handler.handle(request);
86
+ }
87
+ /**
88
+ * Clear all registered handlers
89
+ */
90
+ clear() {
91
+ this.handlers.clear();
92
+ this.logger.info?.("Cleared all webhook handlers");
93
+ }
94
+ /**
95
+ * Get number of registered handlers
96
+ */
97
+ get size() {
98
+ return this.handlers.size;
99
+ }
100
+ }
101
+ /**
102
+ * Create a webhook router with default configuration
103
+ */
104
+ export function createWebhookRouter(logger) {
105
+ return new WebhookRouter({ logger });
106
+ }
107
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/webhooks/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH;;;;GAIG;AACH,MAAM,OAAO,aAAa;IACP,QAAQ,GAAG,IAAI,GAAG,EAAiC,CAAA;IACnD,MAAM,CAAQ;IAE/B,YAAY,OAA6B;QACvC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAC9B,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,MAAqB,EAAE,OAAuB;QACrD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,kCAAkC,MAAM,aAAa,CAAC,CAAA;QAC3E,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAClC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAA;IAChE,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,MAAqB;QAC9B,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,oCAAoC,MAAM,EAAE,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,MAAqB;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,MAAqB;QAC9B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAED;;;;OAIG;IACH,UAAU;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IACzC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,OAAuB;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEjD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,qCAAqC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;YACzE,OAAO;gBACL,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE;oBACJ,QAAQ,EAAE,KAAK;oBACf,KAAK,EAAE,qCAAqC,OAAO,CAAC,MAAM,EAAE;iBAC7D;aACF,CAAA;QACH,CAAC;QAED,OAAO,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;QACrB,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,8BAA8B,CAAC,CAAA;IACpD,CAAC;IAED;;OAEG;IACH,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA;IAC3B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,IAAI,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;AACtC,CAAC"}
@@ -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"}