@brightchain/brightchain-api-lib 0.24.1 → 0.26.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/package.json +4 -3
- package/src/lib/application.d.ts +3 -14
- package/src/lib/application.d.ts.map +1 -1
- package/src/lib/application.js +121 -44
- package/src/lib/application.js.map +1 -1
- package/src/lib/auth/aclDocumentStore.d.ts +90 -0
- package/src/lib/auth/aclDocumentStore.d.ts.map +1 -0
- package/src/lib/auth/aclDocumentStore.js +155 -0
- package/src/lib/auth/aclDocumentStore.js.map +1 -0
- package/src/lib/auth/index.d.ts +4 -0
- package/src/lib/auth/index.d.ts.map +1 -1
- package/src/lib/auth/index.js +4 -0
- package/src/lib/auth/index.js.map +1 -1
- package/src/lib/auth/writeAclApiRouter.d.ts +32 -0
- package/src/lib/auth/writeAclApiRouter.d.ts.map +1 -0
- package/src/lib/auth/writeAclApiRouter.js +348 -0
- package/src/lib/auth/writeAclApiRouter.js.map +1 -0
- package/src/lib/auth/writeAclAuditLogger.d.ts +94 -0
- package/src/lib/auth/writeAclAuditLogger.d.ts.map +1 -0
- package/src/lib/auth/writeAclAuditLogger.js +143 -0
- package/src/lib/auth/writeAclAuditLogger.js.map +1 -0
- package/src/lib/auth/writeProofMiddleware.d.ts +39 -0
- package/src/lib/auth/writeProofMiddleware.d.ts.map +1 -0
- package/src/lib/auth/writeProofMiddleware.js +56 -0
- package/src/lib/auth/writeProofMiddleware.js.map +1 -0
- package/src/lib/availability/aclDocumentSyncHandler.d.ts +39 -0
- package/src/lib/availability/aclDocumentSyncHandler.d.ts.map +1 -0
- package/src/lib/availability/aclDocumentSyncHandler.js +81 -0
- package/src/lib/availability/aclDocumentSyncHandler.js.map +1 -0
- package/src/lib/availability/gossipService.d.ts +4 -1
- package/src/lib/availability/gossipService.d.ts.map +1 -1
- package/src/lib/availability/gossipService.js +15 -1
- package/src/lib/availability/gossipService.js.map +1 -1
- package/src/lib/availability/headUpdateSyncHandler.d.ts +32 -0
- package/src/lib/availability/headUpdateSyncHandler.d.ts.map +1 -0
- package/src/lib/availability/headUpdateSyncHandler.js +78 -0
- package/src/lib/availability/headUpdateSyncHandler.js.map +1 -0
- package/src/lib/availability/index.d.ts +2 -0
- package/src/lib/availability/index.d.ts.map +1 -1
- package/src/lib/availability/index.js +2 -0
- package/src/lib/availability/index.js.map +1 -1
- package/src/lib/constants.d.ts.map +1 -1
- package/src/lib/constants.js +2 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/controllers/api/brighthub/connectionController.d.ts +6 -0
- package/src/lib/controllers/api/brighthub/connectionController.d.ts.map +1 -1
- package/src/lib/controllers/api/brighthub/connectionController.js +158 -11
- package/src/lib/controllers/api/brighthub/connectionController.js.map +1 -1
- package/src/lib/controllers/api/brighthub/messagingController.d.ts +6 -0
- package/src/lib/controllers/api/brighthub/messagingController.d.ts.map +1 -1
- package/src/lib/controllers/api/brighthub/messagingController.js +190 -58
- package/src/lib/controllers/api/brighthub/messagingController.js.map +1 -1
- package/src/lib/controllers/api/brighthub/postController.d.ts +12 -3
- package/src/lib/controllers/api/brighthub/postController.d.ts.map +1 -1
- package/src/lib/controllers/api/brighthub/postController.js +60 -0
- package/src/lib/controllers/api/brighthub/postController.js.map +1 -1
- package/src/lib/controllers/api/brighthub/timelineController.d.ts +19 -0
- package/src/lib/controllers/api/brighthub/timelineController.d.ts.map +1 -1
- package/src/lib/controllers/api/brighthub/timelineController.js +133 -0
- package/src/lib/controllers/api/brighthub/timelineController.js.map +1 -1
- package/src/lib/controllers/api/emails.d.ts +4 -1
- package/src/lib/controllers/api/emails.d.ts.map +1 -1
- package/src/lib/controllers/api/emails.js +28 -1
- package/src/lib/controllers/api/emails.js.map +1 -1
- package/src/lib/controllers/api/user.d.ts.map +1 -1
- package/src/lib/controllers/api/user.js +10 -0
- package/src/lib/controllers/api/user.js.map +1 -1
- package/src/lib/databaseInit.d.ts +7 -11
- package/src/lib/databaseInit.d.ts.map +1 -1
- package/src/lib/databaseInit.js +41 -97
- package/src/lib/databaseInit.js.map +1 -1
- package/src/lib/datastore/block-document-store-factory.d.ts +3 -0
- package/src/lib/datastore/block-document-store-factory.d.ts.map +1 -1
- package/src/lib/datastore/block-document-store-factory.js +15 -18
- package/src/lib/datastore/block-document-store-factory.js.map +1 -1
- package/src/lib/datastore/block-document-store.d.ts +2 -191
- package/src/lib/datastore/block-document-store.d.ts.map +1 -1
- package/src/lib/datastore/block-document-store.js +4 -628
- package/src/lib/datastore/block-document-store.js.map +1 -1
- package/src/lib/datastore/document-store.d.ts +1 -62
- package/src/lib/datastore/document-store.d.ts.map +1 -1
- package/src/lib/datastore/memory-document-store.d.ts +1 -8
- package/src/lib/datastore/memory-document-store.d.ts.map +1 -1
- package/src/lib/datastore/memory-document-store.js +3 -214
- package/src/lib/datastore/memory-document-store.js.map +1 -1
- package/src/lib/environment.d.ts +3 -20
- package/src/lib/environment.d.ts.map +1 -1
- package/src/lib/environment.js +2 -45
- package/src/lib/environment.js.map +1 -1
- package/src/lib/factories/blockStoreFactory.d.ts.map +1 -1
- package/src/lib/factories/blockStoreFactory.js +4 -1
- package/src/lib/factories/blockStoreFactory.js.map +1 -1
- package/src/lib/interfaces/environment.d.ts +23 -2
- package/src/lib/interfaces/environment.d.ts.map +1 -1
- package/src/lib/interfaces/responses/brighthub/api-post-response.d.ts +8 -1
- package/src/lib/interfaces/responses/brighthub/api-post-response.d.ts.map +1 -1
- package/src/lib/middleware/index.d.ts +1 -1
- package/src/lib/middleware/index.d.ts.map +1 -1
- package/src/lib/middleware/index.js +3 -2
- package/src/lib/middleware/index.js.map +1 -1
- package/src/lib/middleware/validateBody.d.ts +1 -12
- package/src/lib/middleware/validateBody.d.ts.map +1 -1
- package/src/lib/middleware/validateBody.js +4 -32
- package/src/lib/middleware/validateBody.js.map +1 -1
- package/src/lib/middlewares.d.ts.map +1 -1
- package/src/lib/middlewares.js +7 -1
- package/src/lib/middlewares.js.map +1 -1
- package/src/lib/plugins/brightchain-database-plugin.d.ts +27 -79
- package/src/lib/plugins/brightchain-database-plugin.d.ts.map +1 -1
- package/src/lib/plugins/brightchain-database-plugin.js +27 -97
- package/src/lib/plugins/brightchain-database-plugin.js.map +1 -1
- package/src/lib/routers/api.d.ts +18 -1
- package/src/lib/routers/api.d.ts.map +1 -1
- package/src/lib/routers/api.js +24 -1
- package/src/lib/routers/api.js.map +1 -1
- package/src/lib/routers/app.d.ts.map +1 -1
- package/src/lib/routers/app.js +5 -2
- package/src/lib/routers/app.js.map +1 -1
- package/src/lib/services/auth.d.ts.map +1 -1
- package/src/lib/services/auth.js +37 -3
- package/src/lib/services/auth.js.map +1 -1
- package/src/lib/services/blockStore.d.ts +8 -1
- package/src/lib/services/blockStore.d.ts.map +1 -1
- package/src/lib/services/blockStore.js +19 -7
- package/src/lib/services/blockStore.js.map +1 -1
- package/src/lib/services/brightChainBackupCodeService.d.ts +42 -39
- package/src/lib/services/brightChainBackupCodeService.d.ts.map +1 -1
- package/src/lib/services/brightChainBackupCodeService.js +86 -61
- package/src/lib/services/brightChainBackupCodeService.js.map +1 -1
- package/src/lib/services/brighthub/collectionAdapter.d.ts +81 -0
- package/src/lib/services/brighthub/collectionAdapter.d.ts.map +1 -0
- package/src/lib/services/brighthub/collectionAdapter.js +127 -0
- package/src/lib/services/brighthub/collectionAdapter.js.map +1 -0
- package/src/lib/services/brighthub/connectionService.d.ts.map +1 -1
- package/src/lib/services/brighthub/connectionService.js +3 -0
- package/src/lib/services/brighthub/connectionService.js.map +1 -1
- package/src/lib/services/brighthub/messagingService.d.ts +4 -0
- package/src/lib/services/brighthub/messagingService.d.ts.map +1 -1
- package/src/lib/services/brighthub/messagingService.js +25 -4
- package/src/lib/services/brighthub/messagingService.js.map +1 -1
- package/src/lib/services/brighthub/notificationService.d.ts.map +1 -1
- package/src/lib/services/brighthub/notificationService.js +35 -20
- package/src/lib/services/brighthub/notificationService.js.map +1 -1
- package/src/lib/services/brighthub/postService.d.ts +7 -1
- package/src/lib/services/brighthub/postService.d.ts.map +1 -1
- package/src/lib/services/brighthub/postService.js +22 -1
- package/src/lib/services/brighthub/postService.js.map +1 -1
- package/src/lib/services/brighthub/userProfileService.d.ts +19 -1
- package/src/lib/services/brighthub/userProfileService.d.ts.map +1 -1
- package/src/lib/services/brighthub/userProfileService.js +74 -0
- package/src/lib/services/brighthub/userProfileService.js.map +1 -1
- package/src/lib/services/emailGateway/antiSpamFilter.d.ts +229 -0
- package/src/lib/services/emailGateway/antiSpamFilter.d.ts.map +1 -0
- package/src/lib/services/emailGateway/antiSpamFilter.js +325 -0
- package/src/lib/services/emailGateway/antiSpamFilter.js.map +1 -0
- package/src/lib/services/emailGateway/bounceProcessor.d.ts +171 -0
- package/src/lib/services/emailGateway/bounceProcessor.d.ts.map +1 -0
- package/src/lib/services/emailGateway/bounceProcessor.js +378 -0
- package/src/lib/services/emailGateway/bounceProcessor.js.map +1 -0
- package/src/lib/services/emailGateway/emailAuthVerifier.d.ts +99 -0
- package/src/lib/services/emailGateway/emailAuthVerifier.d.ts.map +1 -0
- package/src/lib/services/emailGateway/emailAuthVerifier.js +202 -0
- package/src/lib/services/emailGateway/emailAuthVerifier.js.map +1 -0
- package/src/lib/services/emailGateway/emailGatewayConfig.d.ts +73 -0
- package/src/lib/services/emailGateway/emailGatewayConfig.d.ts.map +1 -0
- package/src/lib/services/emailGateway/emailGatewayConfig.js +107 -0
- package/src/lib/services/emailGateway/emailGatewayConfig.js.map +1 -0
- package/src/lib/services/emailGateway/emailGatewayService.d.ts +152 -0
- package/src/lib/services/emailGateway/emailGatewayService.d.ts.map +1 -0
- package/src/lib/services/emailGateway/emailGatewayService.js +201 -0
- package/src/lib/services/emailGateway/emailGatewayService.js.map +1 -0
- package/src/lib/services/emailGateway/gatewayObservability.d.ts +123 -0
- package/src/lib/services/emailGateway/gatewayObservability.d.ts.map +1 -0
- package/src/lib/services/emailGateway/gatewayObservability.js +186 -0
- package/src/lib/services/emailGateway/gatewayObservability.js.map +1 -0
- package/src/lib/services/emailGateway/inboundProcessor.d.ts +113 -0
- package/src/lib/services/emailGateway/inboundProcessor.d.ts.map +1 -0
- package/src/lib/services/emailGateway/inboundProcessor.js +298 -0
- package/src/lib/services/emailGateway/inboundProcessor.js.map +1 -0
- package/src/lib/services/emailGateway/index.d.ts +23 -0
- package/src/lib/services/emailGateway/index.d.ts.map +1 -0
- package/src/lib/services/emailGateway/index.js +26 -0
- package/src/lib/services/emailGateway/index.js.map +1 -0
- package/src/lib/services/emailGateway/outboundDeliveryWorker.d.ts +111 -0
- package/src/lib/services/emailGateway/outboundDeliveryWorker.d.ts.map +1 -0
- package/src/lib/services/emailGateway/outboundDeliveryWorker.js +97 -0
- package/src/lib/services/emailGateway/outboundDeliveryWorker.js.map +1 -0
- package/src/lib/services/emailGateway/outboundQueue.d.ts +135 -0
- package/src/lib/services/emailGateway/outboundQueue.d.ts.map +1 -0
- package/src/lib/services/emailGateway/outboundQueue.js +227 -0
- package/src/lib/services/emailGateway/outboundQueue.js.map +1 -0
- package/src/lib/services/emailGateway/outboundQueueStore.d.ts +110 -0
- package/src/lib/services/emailGateway/outboundQueueStore.d.ts.map +1 -0
- package/src/lib/services/emailGateway/outboundQueueStore.js +131 -0
- package/src/lib/services/emailGateway/outboundQueueStore.js.map +1 -0
- package/src/lib/services/emailGateway/recipientLookupService.d.ts +135 -0
- package/src/lib/services/emailGateway/recipientLookupService.d.ts.map +1 -0
- package/src/lib/services/emailGateway/recipientLookupService.js +294 -0
- package/src/lib/services/emailGateway/recipientLookupService.js.map +1 -0
- package/src/lib/services/emailGateway/retryBackoff.d.ts +79 -0
- package/src/lib/services/emailGateway/retryBackoff.d.ts.map +1 -0
- package/src/lib/services/emailGateway/retryBackoff.js +77 -0
- package/src/lib/services/emailGateway/retryBackoff.js.map +1 -0
- package/src/lib/services/eventNotificationSystem.d.ts.map +1 -1
- package/src/lib/services/eventNotificationSystem.js.map +1 -1
- package/src/lib/services/index.d.ts +2 -1
- package/src/lib/services/index.d.ts.map +1 -1
- package/src/lib/services/index.js +2 -1
- package/src/lib/services/index.js.map +1 -1
- package/src/lib/services/quorumDatabaseAdapter.d.ts +7 -1
- package/src/lib/services/quorumDatabaseAdapter.d.ts.map +1 -1
- package/src/lib/services/quorumDatabaseAdapter.js +83 -0
- package/src/lib/services/quorumDatabaseAdapter.js.map +1 -1
- package/src/lib/services/sessionAdapter.d.ts +2 -61
- package/src/lib/services/sessionAdapter.d.ts.map +1 -1
- package/src/lib/services/sessionAdapter.js +2 -102
- package/src/lib/services/sessionAdapter.js.map +1 -1
- package/src/lib/shared-types.d.ts +7 -15
- package/src/lib/shared-types.d.ts.map +1 -1
- package/src/lib/stores/availabilityAwareBlockStore.d.ts +4 -3
- package/src/lib/stores/availabilityAwareBlockStore.d.ts.map +1 -1
- package/src/lib/stores/availabilityAwareBlockStore.js +5 -2
- package/src/lib/stores/availabilityAwareBlockStore.js.map +1 -1
- package/src/lib/stores/cloudBlockStoreBase.d.ts +2 -1
- package/src/lib/stores/cloudBlockStoreBase.d.ts.map +1 -1
- package/src/lib/stores/cloudBlockStoreBase.js +34 -13
- package/src/lib/stores/cloudBlockStoreBase.js.map +1 -1
- package/src/lib/stores/diskBlockAsyncStore.d.ts +21 -1
- package/src/lib/stores/diskBlockAsyncStore.d.ts.map +1 -1
- package/src/lib/stores/diskBlockAsyncStore.js +48 -17
- package/src/lib/stores/diskBlockAsyncStore.js.map +1 -1
- package/src/lib/stores/diskBlockStore.d.ts +10 -2
- package/src/lib/stores/diskBlockStore.d.ts.map +1 -1
- package/src/lib/stores/diskBlockStore.js +43 -19
- package/src/lib/stores/diskBlockStore.js.map +1 -1
- package/src/lib/types/backend-id.d.ts +1 -2
- package/src/lib/types/backend-id.d.ts.map +1 -1
- package/src/lib/utils/emailValidation.d.ts.map +1 -1
- package/src/lib/utils/emailValidation.js +2 -1
- package/src/lib/utils/emailValidation.js.map +1 -1
- package/src/lib/validation/userValidation.d.ts +2 -43
- package/src/lib/validation/userValidation.d.ts.map +1 -1
- package/src/lib/validation/userValidation.js +6 -144
- package/src/lib/validation/userValidation.js.map +1 -1
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AntiSpamFilter — milter integration adapter for SpamAssassin/Rspamd
|
|
4
|
+
* score classification of inbound email messages.
|
|
5
|
+
*
|
|
6
|
+
* The filter wraps a configurable spam engine client (SpamAssassin via
|
|
7
|
+
* spamc protocol or Rspamd via HTTP API) and classifies messages as
|
|
8
|
+
* ham, probable-spam, or definite-spam based on configurable score
|
|
9
|
+
* thresholds from `ISpamThresholds`.
|
|
10
|
+
*
|
|
11
|
+
* Integration with Postfix is via the milter protocol (RFC 6008):
|
|
12
|
+
* - Definite-spam → signal rejection (SMFIS_REJECT, 550)
|
|
13
|
+
* - Probable-spam → accept and tag metadata with spam flag and score
|
|
14
|
+
* - Ham → accept without modification
|
|
15
|
+
*
|
|
16
|
+
* @see Requirements 7.1, 7.2, 7.3, 7.4, 7.5
|
|
17
|
+
* @module antiSpamFilter
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.AntiSpamFilter = exports.RspamdClient = exports.SpamAssassinClient = void 0;
|
|
21
|
+
const tslib_1 = require("tslib");
|
|
22
|
+
const http = tslib_1.__importStar(require("http"));
|
|
23
|
+
const net = tslib_1.__importStar(require("net"));
|
|
24
|
+
const brightchain_lib_1 = require("@brightchain/brightchain-lib");
|
|
25
|
+
// ─── SpamAssassin Client ────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* SpamAssassin engine client — communicates with spamd via the spamc
|
|
28
|
+
* TCP protocol (default port 783).
|
|
29
|
+
*
|
|
30
|
+
* Protocol overview:
|
|
31
|
+
* 1. Connect to spamd TCP socket
|
|
32
|
+
* 2. Send `SYMBOLS SPAMC/1.5\r\nContent-length: <len>\r\n\r\n<message>`
|
|
33
|
+
* 3. Parse response for `Spam: True/False ; <score> / <threshold>` header
|
|
34
|
+
*
|
|
35
|
+
* @see Requirement 7.1
|
|
36
|
+
*/
|
|
37
|
+
class SpamAssassinClient {
|
|
38
|
+
host;
|
|
39
|
+
port;
|
|
40
|
+
timeoutMs;
|
|
41
|
+
constructor(host = 'localhost', port = 783, timeoutMs = 30_000) {
|
|
42
|
+
this.host = host;
|
|
43
|
+
this.port = port;
|
|
44
|
+
this.timeoutMs = timeoutMs;
|
|
45
|
+
}
|
|
46
|
+
async scan(rawMessage) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const socket = net.createConnection({ host: this.host, port: this.port }, () => {
|
|
49
|
+
const request = `SYMBOLS SPAMC/1.5\r\n` +
|
|
50
|
+
`Content-length: ${rawMessage.length}\r\n` +
|
|
51
|
+
`\r\n`;
|
|
52
|
+
socket.write(request);
|
|
53
|
+
socket.write(rawMessage);
|
|
54
|
+
});
|
|
55
|
+
socket.setTimeout(this.timeoutMs);
|
|
56
|
+
const chunks = [];
|
|
57
|
+
socket.on('data', (chunk) => {
|
|
58
|
+
chunks.push(chunk);
|
|
59
|
+
});
|
|
60
|
+
socket.on('end', () => {
|
|
61
|
+
const response = Buffer.concat(chunks).toString('utf-8');
|
|
62
|
+
resolve(SpamAssassinClient.parseResponse(response));
|
|
63
|
+
});
|
|
64
|
+
socket.on('timeout', () => {
|
|
65
|
+
socket.destroy();
|
|
66
|
+
reject(new Error('SpamAssassin spamd connection timed out'));
|
|
67
|
+
});
|
|
68
|
+
socket.on('error', (err) => {
|
|
69
|
+
reject(new Error(`SpamAssassin spamd connection error: ${err.message}`));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse the spamd response to extract score and spam determination.
|
|
75
|
+
*
|
|
76
|
+
* Expected header line format:
|
|
77
|
+
* `Spam: True ; 15.3 / 5.0`
|
|
78
|
+
* `Spam: False ; 1.2 / 5.0`
|
|
79
|
+
*/
|
|
80
|
+
static parseResponse(response) {
|
|
81
|
+
const spamMatch = response.match(/Spam:\s*(True|False|Yes|No)\s*;\s*([\d.]+)\s*\/\s*([\d.]+)/i);
|
|
82
|
+
if (!spamMatch) {
|
|
83
|
+
return {
|
|
84
|
+
score: 0,
|
|
85
|
+
isSpam: false,
|
|
86
|
+
details: 'Unable to parse spamd response',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const isSpam = spamMatch[1].toLowerCase() === 'true' ||
|
|
90
|
+
spamMatch[1].toLowerCase() === 'yes';
|
|
91
|
+
const score = parseFloat(spamMatch[2]);
|
|
92
|
+
// Extract rule matches from the body (lines after headers)
|
|
93
|
+
const bodyStart = response.indexOf('\r\n\r\n');
|
|
94
|
+
const details = bodyStart >= 0 ? response.substring(bodyStart + 4).trim() : undefined;
|
|
95
|
+
return { score, isSpam, details: details || undefined };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.SpamAssassinClient = SpamAssassinClient;
|
|
99
|
+
// ─── Rspamd Client ──────────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Rspamd engine client — communicates with rspamd via its HTTP API
|
|
102
|
+
* (default port 11333).
|
|
103
|
+
*
|
|
104
|
+
* Protocol overview:
|
|
105
|
+
* 1. POST raw message to `http://<host>:<port>/checkv2`
|
|
106
|
+
* 2. Parse JSON response for `score`, `is_spam`/`action`, and `symbols`
|
|
107
|
+
*
|
|
108
|
+
* @see Requirement 7.1
|
|
109
|
+
*/
|
|
110
|
+
class RspamdClient {
|
|
111
|
+
host;
|
|
112
|
+
port;
|
|
113
|
+
timeoutMs;
|
|
114
|
+
constructor(host = 'localhost', port = 11333, timeoutMs = 30_000) {
|
|
115
|
+
this.host = host;
|
|
116
|
+
this.port = port;
|
|
117
|
+
this.timeoutMs = timeoutMs;
|
|
118
|
+
}
|
|
119
|
+
async scan(rawMessage) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const options = {
|
|
122
|
+
hostname: this.host,
|
|
123
|
+
port: this.port,
|
|
124
|
+
path: '/checkv2',
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
'Content-Length': rawMessage.length,
|
|
128
|
+
},
|
|
129
|
+
timeout: this.timeoutMs,
|
|
130
|
+
};
|
|
131
|
+
const req = http.request(options, (res) => {
|
|
132
|
+
const chunks = [];
|
|
133
|
+
res.on('data', (chunk) => {
|
|
134
|
+
chunks.push(chunk);
|
|
135
|
+
});
|
|
136
|
+
res.on('end', () => {
|
|
137
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
138
|
+
try {
|
|
139
|
+
resolve(RspamdClient.parseResponse(body));
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
reject(new Error(`Rspamd response parse error: ${err instanceof Error ? err.message : String(err)}`));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
req.on('timeout', () => {
|
|
147
|
+
req.destroy();
|
|
148
|
+
reject(new Error('Rspamd HTTP connection timed out'));
|
|
149
|
+
});
|
|
150
|
+
req.on('error', (err) => {
|
|
151
|
+
reject(new Error(`Rspamd HTTP connection error: ${err.message}`));
|
|
152
|
+
});
|
|
153
|
+
req.write(rawMessage);
|
|
154
|
+
req.end();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Parse the Rspamd JSON response.
|
|
159
|
+
*
|
|
160
|
+
* Expected JSON structure:
|
|
161
|
+
* ```json
|
|
162
|
+
* {
|
|
163
|
+
* "score": 15.3,
|
|
164
|
+
* "required_score": 15.0,
|
|
165
|
+
* "action": "reject" | "add header" | "no action" | ...,
|
|
166
|
+
* "symbols": { ... }
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
static parseResponse(body) {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(body);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return {
|
|
177
|
+
score: 0,
|
|
178
|
+
isSpam: false,
|
|
179
|
+
details: 'Unable to parse Rspamd JSON response',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const score = typeof parsed['score'] === 'number' ? parsed['score'] : 0;
|
|
183
|
+
const action = typeof parsed['action'] === 'string' ? parsed['action'] : '';
|
|
184
|
+
// Rspamd actions: "reject", "rewrite subject", "add header",
|
|
185
|
+
// "greylist", "no action", "soft reject"
|
|
186
|
+
const isSpam = action === 'reject' || action === 'add header';
|
|
187
|
+
// Summarize matched symbols if available
|
|
188
|
+
let details;
|
|
189
|
+
if (parsed['symbols'] &&
|
|
190
|
+
typeof parsed['symbols'] === 'object' &&
|
|
191
|
+
parsed['symbols'] !== null) {
|
|
192
|
+
const symbolNames = Object.keys(parsed['symbols']);
|
|
193
|
+
if (symbolNames.length > 0) {
|
|
194
|
+
details = symbolNames.join(', ');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { score, isSpam, details };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.RspamdClient = RspamdClient;
|
|
201
|
+
// ─── Anti-Spam Filter Implementation ────────────────────────────────────────
|
|
202
|
+
/**
|
|
203
|
+
* Milter integration adapter for SpamAssassin/Rspamd score classification.
|
|
204
|
+
*
|
|
205
|
+
* Wraps a configurable spam engine client and classifies messages based
|
|
206
|
+
* on `ISpamThresholds`:
|
|
207
|
+
* - score < probableSpamScore → Ham (accept)
|
|
208
|
+
* - probableSpamScore ≤ score < definiteSpamScore → ProbableSpam (tag metadata)
|
|
209
|
+
* - score ≥ definiteSpamScore → DefiniteSpam (reject 550)
|
|
210
|
+
*
|
|
211
|
+
* @see Requirements 7.1, 7.2, 7.3, 7.4, 7.5
|
|
212
|
+
*/
|
|
213
|
+
class AntiSpamFilter {
|
|
214
|
+
engineClient;
|
|
215
|
+
thresholds;
|
|
216
|
+
/**
|
|
217
|
+
* Create an AntiSpamFilter with the specified engine and thresholds.
|
|
218
|
+
*
|
|
219
|
+
* @param engine - Which spam engine to use: `'spamassassin'` or `'rspamd'`
|
|
220
|
+
* @param thresholds - Score thresholds for classification
|
|
221
|
+
* @param engineClient - Optional pre-configured engine client (for testing / DI)
|
|
222
|
+
* @param engineHost - Host for the spam engine (default: localhost)
|
|
223
|
+
* @param enginePort - Port for the spam engine (default: engine-specific)
|
|
224
|
+
*/
|
|
225
|
+
constructor(engine, thresholds, engineClient, engineHost, enginePort) {
|
|
226
|
+
this.thresholds = thresholds;
|
|
227
|
+
if (engineClient) {
|
|
228
|
+
this.engineClient = engineClient;
|
|
229
|
+
}
|
|
230
|
+
else if (engine === 'rspamd') {
|
|
231
|
+
this.engineClient = new RspamdClient(engineHost ?? 'localhost', enginePort ?? 11333);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.engineClient = new SpamAssassinClient(engineHost ?? 'localhost', enginePort ?? 783);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Classify a spam score against the configured thresholds.
|
|
239
|
+
*
|
|
240
|
+
* @param score - Numeric spam score from the engine
|
|
241
|
+
* @returns The spam classification
|
|
242
|
+
*
|
|
243
|
+
* @see Requirement 7.2
|
|
244
|
+
*/
|
|
245
|
+
classify(score) {
|
|
246
|
+
if (score >= this.thresholds.definiteSpamScore) {
|
|
247
|
+
return brightchain_lib_1.SpamClassification.DefiniteSpam;
|
|
248
|
+
}
|
|
249
|
+
if (score >= this.thresholds.probableSpamScore) {
|
|
250
|
+
return brightchain_lib_1.SpamClassification.ProbableSpam;
|
|
251
|
+
}
|
|
252
|
+
return brightchain_lib_1.SpamClassification.Ham;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Scan a raw email message and return the spam classification result.
|
|
256
|
+
*
|
|
257
|
+
* Delegates to the configured spam engine client, then classifies
|
|
258
|
+
* the returned score against the configured thresholds.
|
|
259
|
+
*
|
|
260
|
+
* @param rawMessage - The raw RFC 5322 message bytes
|
|
261
|
+
* @returns Scan result with score, classification, and optional details
|
|
262
|
+
*
|
|
263
|
+
* @see Requirements 7.1, 7.2
|
|
264
|
+
*/
|
|
265
|
+
async scan(rawMessage) {
|
|
266
|
+
const engineResult = await this.engineClient.scan(rawMessage);
|
|
267
|
+
const classification = this.classify(engineResult.score);
|
|
268
|
+
return {
|
|
269
|
+
score: engineResult.score,
|
|
270
|
+
classification,
|
|
271
|
+
details: engineResult.details,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Scan a message and return the milter protocol response for Postfix.
|
|
276
|
+
*
|
|
277
|
+
* Maps classification to milter actions:
|
|
278
|
+
* - DefiniteSpam → reject (SMFIS_REJECT, 550)
|
|
279
|
+
* - ProbableSpam → continue with X-Spam-Flag / X-Spam-Score headers
|
|
280
|
+
* - Ham → accept
|
|
281
|
+
*
|
|
282
|
+
* @param rawMessage - The raw RFC 5322 message bytes
|
|
283
|
+
* @returns Milter response with action, optional reply code, and headers
|
|
284
|
+
*
|
|
285
|
+
* @see Requirements 7.3, 7.4, 7.5
|
|
286
|
+
*/
|
|
287
|
+
async milterCheck(rawMessage) {
|
|
288
|
+
const scanResult = await this.scan(rawMessage);
|
|
289
|
+
switch (scanResult.classification) {
|
|
290
|
+
case brightchain_lib_1.SpamClassification.DefiniteSpam:
|
|
291
|
+
// Req 7.3: reject at SMTP time with 550
|
|
292
|
+
return {
|
|
293
|
+
action: 'reject',
|
|
294
|
+
replyCode: 550,
|
|
295
|
+
replyText: '5.7.1 Message rejected as spam',
|
|
296
|
+
scanResult,
|
|
297
|
+
};
|
|
298
|
+
case brightchain_lib_1.SpamClassification.ProbableSpam:
|
|
299
|
+
// Req 7.4: accept and tag metadata with spam flag and score
|
|
300
|
+
return {
|
|
301
|
+
action: 'continue',
|
|
302
|
+
addHeaders: {
|
|
303
|
+
'X-Spam-Flag': 'YES',
|
|
304
|
+
'X-Spam-Score': scanResult.score.toFixed(2),
|
|
305
|
+
'X-Spam-Status': `Yes, score=${scanResult.score.toFixed(2)}${scanResult.details ? ` tests=${scanResult.details}` : ''}`,
|
|
306
|
+
},
|
|
307
|
+
scanResult,
|
|
308
|
+
};
|
|
309
|
+
case brightchain_lib_1.SpamClassification.Ham:
|
|
310
|
+
default:
|
|
311
|
+
// Ham: accept without modification
|
|
312
|
+
return {
|
|
313
|
+
action: 'accept',
|
|
314
|
+
addHeaders: {
|
|
315
|
+
'X-Spam-Flag': 'NO',
|
|
316
|
+
'X-Spam-Score': scanResult.score.toFixed(2),
|
|
317
|
+
'X-Spam-Status': `No, score=${scanResult.score.toFixed(2)}${scanResult.details ? ` tests=${scanResult.details}` : ''}`,
|
|
318
|
+
},
|
|
319
|
+
scanResult,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
exports.AntiSpamFilter = AntiSpamFilter;
|
|
325
|
+
//# sourceMappingURL=antiSpamFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"antiSpamFilter.js","sourceRoot":"","sources":["../../../../../../brightchain-api-lib/src/lib/services/emailGateway/antiSpamFilter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BounceProcessor — parses DSN (Delivery Status Notification) messages
|
|
3
|
+
* per RFC 3464, correlates them to original outbound messages, updates
|
|
4
|
+
* delivery status, and notifies the original sender via Gossip Protocol.
|
|
5
|
+
*
|
|
6
|
+
* DSN correlation strategy:
|
|
7
|
+
* 1. Extract `Original-Message-ID` or `Message-ID` from the DSN body
|
|
8
|
+
* 2. Parse VERP-encoded bounce address to extract original message ID
|
|
9
|
+
* (e.g. `bounces+msgid=domain@canonical.domain`)
|
|
10
|
+
* 3. Fall back to fixed bounce address matching
|
|
11
|
+
*
|
|
12
|
+
* On permanent failure (5xx / "failed" action):
|
|
13
|
+
* - Update delivery status to FAILED in the metadata store
|
|
14
|
+
* - Generate an IBounceNotification and deliver to the original sender
|
|
15
|
+
* via the Gossip Protocol
|
|
16
|
+
*
|
|
17
|
+
* On transient failure (4xx / "delayed" action):
|
|
18
|
+
* - Record the transient bounce but do not mark as permanently failed
|
|
19
|
+
*
|
|
20
|
+
* @see Requirements 5.1, 5.2, 5.3, 5.4
|
|
21
|
+
* @module bounceProcessor
|
|
22
|
+
*/
|
|
23
|
+
import type { IGossipService } from '@brightchain/brightchain-lib';
|
|
24
|
+
import { EmailMessageService } from '@brightchain/brightchain-lib';
|
|
25
|
+
import type { IEmailGatewayConfig } from './emailGatewayConfig';
|
|
26
|
+
import type { IOutboundQueueStore } from './outboundQueueStore';
|
|
27
|
+
/**
|
|
28
|
+
* Parsed result from a DSN message (RFC 3464).
|
|
29
|
+
*/
|
|
30
|
+
export interface IParsedDsn {
|
|
31
|
+
/** The original Message-ID that this DSN refers to */
|
|
32
|
+
originalMessageId: string | undefined;
|
|
33
|
+
/** The recipient address that bounced */
|
|
34
|
+
recipientAddress: string | undefined;
|
|
35
|
+
/** DSN action: failed, delayed, delivered, relayed, expanded */
|
|
36
|
+
action: 'failed' | 'delayed' | 'delivered' | 'relayed' | 'expanded' | undefined;
|
|
37
|
+
/** SMTP diagnostic code (e.g. "550 5.1.1 User unknown") */
|
|
38
|
+
diagnosticCode: string | undefined;
|
|
39
|
+
/** SMTP status code (e.g. "5.1.1") */
|
|
40
|
+
statusCode: string | undefined;
|
|
41
|
+
/** The envelope-to / return-path from the DSN */
|
|
42
|
+
envelopeSender: string | undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Interface for the BounceProcessor to allow dependency injection in tests.
|
|
46
|
+
*/
|
|
47
|
+
export interface IBounceProcessor {
|
|
48
|
+
/**
|
|
49
|
+
* Process a raw DSN message.
|
|
50
|
+
*
|
|
51
|
+
* @param rawDsn - The raw RFC 3464 DSN message content
|
|
52
|
+
* @param envelopeSender - Optional envelope sender (Return-Path) for VERP correlation
|
|
53
|
+
*/
|
|
54
|
+
processDsn(rawDsn: string | Uint8Array, envelopeSender?: string): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Processes DSN (Delivery Status Notification) messages to track bounce
|
|
58
|
+
* status for outbound email.
|
|
59
|
+
*
|
|
60
|
+
* @see Requirements 5.1, 5.2, 5.3, 5.4
|
|
61
|
+
*/
|
|
62
|
+
export declare class BounceProcessor implements IBounceProcessor {
|
|
63
|
+
private readonly config;
|
|
64
|
+
private readonly emailMessageService;
|
|
65
|
+
private readonly outboundQueueStore;
|
|
66
|
+
private readonly gossipService;
|
|
67
|
+
constructor(config: IEmailGatewayConfig, emailMessageService: EmailMessageService, outboundQueueStore: IOutboundQueueStore, gossipService: IGossipService);
|
|
68
|
+
/**
|
|
69
|
+
* Process a raw DSN message.
|
|
70
|
+
*
|
|
71
|
+
* Pipeline:
|
|
72
|
+
* 1. Parse the DSN to extract original message ID, recipient, action, and diagnostic
|
|
73
|
+
* 2. Correlate to the original outbound message via Message-ID or VERP
|
|
74
|
+
* 3. On permanent failure: update delivery status to FAILED, notify sender via gossip
|
|
75
|
+
* 4. On transient failure: log but do not mark as permanently failed
|
|
76
|
+
*
|
|
77
|
+
* @param rawDsn - The raw RFC 3464 DSN message content (string or bytes)
|
|
78
|
+
* @param envelopeSender - Optional envelope sender (Return-Path) for VERP correlation
|
|
79
|
+
*
|
|
80
|
+
* @see Requirements 5.1, 5.2, 5.3, 5.4
|
|
81
|
+
*/
|
|
82
|
+
processDsn(rawDsn: string | Uint8Array, envelopeSender?: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Parse a raw DSN message (RFC 3464) to extract the original message
|
|
85
|
+
* identifier, recipient address, action, and diagnostic code.
|
|
86
|
+
*
|
|
87
|
+
* RFC 3464 DSN messages are multipart/report with:
|
|
88
|
+
* - Part 1: Human-readable explanation
|
|
89
|
+
* - Part 2: message/delivery-status with per-recipient fields
|
|
90
|
+
* - Part 3: (optional) original message or headers
|
|
91
|
+
*
|
|
92
|
+
* This parser extracts key fields from the delivery-status part using
|
|
93
|
+
* simple line-based parsing (no full MIME parser needed for DSN fields).
|
|
94
|
+
*
|
|
95
|
+
* @param dsnText - The raw DSN message as a string
|
|
96
|
+
* @returns Parsed DSN fields
|
|
97
|
+
*
|
|
98
|
+
* @see Requirement 5.1
|
|
99
|
+
*/
|
|
100
|
+
static parseDsnMessage(dsnText: string): IParsedDsn;
|
|
101
|
+
/**
|
|
102
|
+
* Parse a VERP (Variable Envelope Return Path) encoded bounce address
|
|
103
|
+
* to extract the original message identifier.
|
|
104
|
+
*
|
|
105
|
+
* VERP format: `bounces+<encoded-msgid>=<encoded-domain>@<canonical-domain>`
|
|
106
|
+
*
|
|
107
|
+
* Examples:
|
|
108
|
+
* - `bounces+abc123=example.com@brightchain.org` → `<abc123@example.com>`
|
|
109
|
+
* - `bounces+msg-001=mail.test.org@brightchain.org` → `<msg-001@mail.test.org>`
|
|
110
|
+
*
|
|
111
|
+
* @param bounceAddress - The envelope sender / Return-Path address
|
|
112
|
+
* @param canonicalDomain - The canonical domain to validate against
|
|
113
|
+
* @returns The extracted original Message-ID, or undefined if not VERP-encoded
|
|
114
|
+
*
|
|
115
|
+
* @see Requirement 5.4
|
|
116
|
+
*/
|
|
117
|
+
static parseVerpAddress(bounceAddress: string, canonicalDomain: string): string | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* Classify a parsed DSN as permanent or transient bounce.
|
|
120
|
+
*
|
|
121
|
+
* - Action "failed" or status code starting with "5" → permanent
|
|
122
|
+
* - Action "delayed" or status code starting with "4" → transient
|
|
123
|
+
* - Default: permanent (fail-safe)
|
|
124
|
+
*
|
|
125
|
+
* @param parsed - The parsed DSN fields
|
|
126
|
+
* @returns 'permanent' or 'transient'
|
|
127
|
+
*/
|
|
128
|
+
static classifyBounce(parsed: IParsedDsn): 'permanent' | 'transient';
|
|
129
|
+
/**
|
|
130
|
+
* Correlate a parsed DSN to the original outbound message.
|
|
131
|
+
*
|
|
132
|
+
* Strategy (in order):
|
|
133
|
+
* 1. Use `originalMessageId` extracted from DSN headers
|
|
134
|
+
* 2. Parse VERP-encoded envelope sender to extract message ID
|
|
135
|
+
* 3. Return undefined if correlation fails
|
|
136
|
+
*
|
|
137
|
+
* @param parsed - The parsed DSN fields
|
|
138
|
+
* @returns The original Message-ID, or undefined if correlation fails
|
|
139
|
+
*
|
|
140
|
+
* @see Requirement 5.4
|
|
141
|
+
*/
|
|
142
|
+
private correlateToOriginal;
|
|
143
|
+
/**
|
|
144
|
+
* Update the delivery status of the original message to FAILED.
|
|
145
|
+
*
|
|
146
|
+
* Updates both:
|
|
147
|
+
* - The outbound queue store (OutboundDeliveryStatus.PermanentFailure)
|
|
148
|
+
* - The email metadata store (DeliveryStatus.Failed on the delivery receipt)
|
|
149
|
+
*
|
|
150
|
+
* @param messageId - The original Message-ID
|
|
151
|
+
* @param recipientAddress - The recipient that bounced
|
|
152
|
+
* @param failureReason - Human-readable failure reason
|
|
153
|
+
*
|
|
154
|
+
* @see Requirement 5.2
|
|
155
|
+
*/
|
|
156
|
+
private updateDeliveryStatusToFailed;
|
|
157
|
+
/**
|
|
158
|
+
* Find the key in the delivery receipts map that matches a recipient address.
|
|
159
|
+
*/
|
|
160
|
+
private findRecipientKey;
|
|
161
|
+
/**
|
|
162
|
+
* Generate a bounce notification and deliver it to the original sender
|
|
163
|
+
* via the Gossip Protocol.
|
|
164
|
+
*
|
|
165
|
+
* @param notification - The bounce notification to deliver
|
|
166
|
+
*
|
|
167
|
+
* @see Requirement 5.3
|
|
168
|
+
*/
|
|
169
|
+
private sendBounceNotification;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=bounceProcessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounceProcessor.d.ts","sourceRoot":"","sources":["../../../../../../brightchain-api-lib/src/lib/services/emailGateway/bounceProcessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAEV,cAAc,EACf,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,mBAAmB,EACpB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIhE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;IAEtC,yCAAyC;IACzC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAErC,gEAAgE;IAChE,MAAM,EACF,QAAQ,GACR,SAAS,GACT,WAAW,GACX,SAAS,GACT,UAAU,GACV,SAAS,CAAC;IAEd,2DAA2D;IAC3D,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAEnC,sCAAsC;IACtC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAE/B,iDAAiD;IACjD,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,UAAU,CACR,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAID;;;;;GAKG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IAEpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IACpC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAHb,MAAM,EAAE,mBAAmB,EAC3B,mBAAmB,EAAE,mBAAmB,EACxC,kBAAkB,EAAE,mBAAmB,EACvC,aAAa,EAAE,cAAc;IAKhD;;;;;;;;;;;;;OAaG;IACG,UAAU,CACd,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC;IAiDhB;;;;;;;;;;;;;;;;OAgBG;IACH,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAuFnD;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,gBAAgB,CACrB,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS;IAiCrB;;;;;;;;;OASG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW;IAgBpE;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAkCjC;;;;;;;;;;;;OAYG;YACW,4BAA4B;IAgD1C;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;;;;;OAOG;YACW,sBAAsB;CAkCrC"}
|