@serve.zone/dcrouter 13.22.0 → 13.24.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 (51) hide show
  1. package/dist_serve/bundle.js +952 -792
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +4 -0
  4. package/dist_ts/classes.dcrouter.js +69 -2
  5. package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +25 -0
  6. package/dist_ts/db/documents/classes.ip-intelligence.doc.js +175 -0
  7. package/dist_ts/db/documents/classes.security-block-rule.doc.d.ts +17 -0
  8. package/dist_ts/db/documents/classes.security-block-rule.doc.js +124 -0
  9. package/dist_ts/db/documents/classes.security-policy-audit.doc.d.ts +11 -0
  10. package/dist_ts/db/documents/classes.security-policy-audit.doc.js +95 -0
  11. package/dist_ts/db/documents/index.d.ts +3 -0
  12. package/dist_ts/db/documents/index.js +4 -1
  13. package/dist_ts/monitoring/classes.metricsmanager.js +2 -1
  14. package/dist_ts/opsserver/handlers/security.handler.js +63 -1
  15. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +10 -0
  16. package/dist_ts/remoteingress/classes.remoteingress-manager.js +9 -1
  17. package/dist_ts/security/classes.security-policy-manager.d.ts +46 -0
  18. package/dist_ts/security/classes.security-policy-manager.js +304 -0
  19. package/dist_ts/security/index.d.ts +1 -0
  20. package/dist_ts/security/index.js +2 -1
  21. package/dist_ts_interfaces/data/index.d.ts +1 -0
  22. package/dist_ts_interfaces/data/index.js +2 -1
  23. package/dist_ts_interfaces/data/security-policy.d.ts +32 -0
  24. package/dist_ts_interfaces/data/security-policy.js +2 -0
  25. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  26. package/dist_ts_interfaces/requests/index.js +2 -1
  27. package/dist_ts_interfaces/requests/security-policy.d.ts +95 -0
  28. package/dist_ts_interfaces/requests/security-policy.js +2 -0
  29. package/dist_ts_web/00_commitinfo_data.js +1 -1
  30. package/dist_ts_web/appstate.d.ts +28 -0
  31. package/dist_ts_web/appstate.js +171 -4
  32. package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +9 -0
  33. package/dist_ts_web/elements/network/ops-view-network-activity.js +210 -3
  34. package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +12 -3
  35. package/dist_ts_web/elements/security/ops-view-security-blocked.js +407 -52
  36. package/package.json +3 -3
  37. package/ts/00_commitinfo_data.ts +1 -1
  38. package/ts/classes.dcrouter.ts +91 -3
  39. package/ts/db/documents/classes.ip-intelligence.doc.ts +75 -0
  40. package/ts/db/documents/classes.security-block-rule.doc.ts +52 -0
  41. package/ts/db/documents/classes.security-policy-audit.doc.ts +33 -0
  42. package/ts/db/documents/index.ts +3 -0
  43. package/ts/monitoring/classes.metricsmanager.ts +2 -0
  44. package/ts/opsserver/handlers/security.handler.ts +107 -0
  45. package/ts/remoteingress/classes.remoteingress-manager.ts +15 -2
  46. package/ts/security/classes.security-policy-manager.ts +340 -0
  47. package/ts/security/index.ts +7 -1
  48. package/ts_web/00_commitinfo_data.ts +1 -1
  49. package/ts_web/appstate.ts +236 -3
  50. package/ts_web/elements/network/ops-view-network-activity.ts +219 -2
  51. package/ts_web/elements/security/ops-view-security-blocked.ts +414 -51
@@ -0,0 +1,340 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ import { IpIntelligenceDoc, SecurityBlockRuleDoc, SecurityPolicyAuditDoc } from '../db/index.js';
4
+ import type {
5
+ IIpIntelligenceRecord,
6
+ ISecurityBlockRule,
7
+ ISecurityCompiledPolicy,
8
+ ISecurityPolicyAuditEvent,
9
+ TSecurityBlockRuleMatchMode,
10
+ TSecurityBlockRuleType,
11
+ } from '../../ts_interfaces/data/security-policy.js';
12
+
13
+ export interface ISecurityPolicyManagerOptions {
14
+ intelligenceRefreshMs?: number;
15
+ onPolicyChanged?: () => void | Promise<void>;
16
+ }
17
+
18
+ export interface IRemoteIngressFirewallSnapshot {
19
+ blockedIps?: string[];
20
+ }
21
+
22
+ export class SecurityPolicyManager {
23
+ private readonly smartNetwork = new plugins.smartnetwork.SmartNetwork({
24
+ cacheTtl: 24 * 60 * 60 * 1000,
25
+ });
26
+ private readonly intelligenceRefreshMs: number;
27
+ private readonly inFlightObservations = new Set<string>();
28
+ private readonly onPolicyChanged?: () => void | Promise<void>;
29
+
30
+ constructor(options: ISecurityPolicyManagerOptions = {}) {
31
+ this.intelligenceRefreshMs = options.intelligenceRefreshMs ?? 24 * 60 * 60 * 1000;
32
+ this.onPolicyChanged = options.onPolicyChanged;
33
+ }
34
+
35
+ public async start(): Promise<void> {
36
+ logger.log('info', 'SecurityPolicyManager started');
37
+ }
38
+
39
+ public async stop(): Promise<void> {
40
+ await this.smartNetwork.stop();
41
+ }
42
+
43
+ public async observeIps(ips: string[]): Promise<void> {
44
+ const uniqueIps = [...new Set(ips.map((ip) => this.normalizeIp(ip)).filter(Boolean) as string[])];
45
+ await Promise.allSettled(uniqueIps.map((ip) => this.observeIp(ip)));
46
+ }
47
+
48
+ public async observeIp(ipAddress: string, options: { force?: boolean } = {}): Promise<void> {
49
+ const ip = this.normalizeIp(ipAddress);
50
+ if (!ip || !this.isPublicIp(ip) || this.inFlightObservations.has(ip)) {
51
+ return;
52
+ }
53
+
54
+ this.inFlightObservations.add(ip);
55
+ try {
56
+ const now = Date.now();
57
+ let doc = await IpIntelligenceDoc.findByIp(ip);
58
+ if (doc && !options.force && now - doc.updatedAt < this.intelligenceRefreshMs) {
59
+ if (now - doc.lastSeenAt > 60_000) {
60
+ doc.lastSeenAt = now;
61
+ doc.seenCount = (doc.seenCount || 0) + 1;
62
+ await doc.save();
63
+ }
64
+ return;
65
+ }
66
+
67
+ const intelligence = await this.smartNetwork.getIpIntelligence(ip);
68
+ if (!doc) {
69
+ doc = new IpIntelligenceDoc();
70
+ doc.ipAddress = ip;
71
+ doc.firstSeenAt = now;
72
+ }
73
+ Object.assign(doc, intelligence);
74
+ doc.lastSeenAt = now;
75
+ doc.updatedAt = now;
76
+ doc.seenCount = (doc.seenCount || 0) + 1;
77
+ await doc.save();
78
+
79
+ if (await this.matchesAnyReactiveRule(doc)) {
80
+ await this.notifyPolicyChanged();
81
+ }
82
+ } catch (err) {
83
+ logger.log('warn', `Failed to enrich IP ${ip}: ${(err as Error).message}`);
84
+ } finally {
85
+ this.inFlightObservations.delete(ip);
86
+ }
87
+ }
88
+
89
+ public async listBlockRules(): Promise<ISecurityBlockRule[]> {
90
+ return (await SecurityBlockRuleDoc.findAll()).map((doc) => this.ruleFromDoc(doc));
91
+ }
92
+
93
+ public async listIpIntelligence(): Promise<IIpIntelligenceRecord[]> {
94
+ return (await IpIntelligenceDoc.findAll()).map((doc) => this.intelligenceFromDoc(doc));
95
+ }
96
+
97
+ public async refreshIpIntelligence(ipAddress: string): Promise<IIpIntelligenceRecord | null> {
98
+ const ip = this.normalizeIp(ipAddress);
99
+ if (!ip || !this.isPublicIp(ip)) {
100
+ return null;
101
+ }
102
+ await this.observeIp(ip, { force: true });
103
+ const doc = await IpIntelligenceDoc.findByIp(ip);
104
+ return doc ? this.intelligenceFromDoc(doc) : null;
105
+ }
106
+
107
+ public async listAuditEvents(limit = 100): Promise<ISecurityPolicyAuditEvent[]> {
108
+ return (await SecurityPolicyAuditDoc.findRecent(limit)).map((doc) => ({
109
+ id: doc.id,
110
+ action: doc.action,
111
+ actor: doc.actor,
112
+ details: doc.details,
113
+ createdAt: doc.createdAt,
114
+ }));
115
+ }
116
+
117
+ private intelligenceFromDoc(doc: IpIntelligenceDoc): IIpIntelligenceRecord {
118
+ return {
119
+ ipAddress: doc.ipAddress,
120
+ asn: doc.asn,
121
+ asnOrg: doc.asnOrg,
122
+ registrantOrg: doc.registrantOrg,
123
+ registrantCountry: doc.registrantCountry,
124
+ networkRange: doc.networkRange,
125
+ abuseContact: doc.abuseContact,
126
+ country: doc.country,
127
+ countryCode: doc.countryCode,
128
+ city: doc.city,
129
+ latitude: doc.latitude,
130
+ longitude: doc.longitude,
131
+ accuracyRadius: doc.accuracyRadius,
132
+ timezone: doc.timezone,
133
+ firstSeenAt: doc.firstSeenAt,
134
+ lastSeenAt: doc.lastSeenAt,
135
+ updatedAt: doc.updatedAt,
136
+ seenCount: doc.seenCount,
137
+ };
138
+ }
139
+
140
+ public async createBlockRule(input: {
141
+ type: TSecurityBlockRuleType;
142
+ value: string;
143
+ matchMode?: TSecurityBlockRuleMatchMode;
144
+ reason?: string;
145
+ enabled?: boolean;
146
+ }, actor = 'system'): Promise<ISecurityBlockRule> {
147
+ const now = Date.now();
148
+ const doc = new SecurityBlockRuleDoc();
149
+ doc.id = plugins.uuid.v4();
150
+ doc.type = input.type;
151
+ doc.value = input.value.trim();
152
+ doc.matchMode = input.matchMode;
153
+ doc.reason = input.reason;
154
+ doc.enabled = input.enabled ?? true;
155
+ doc.createdAt = now;
156
+ doc.updatedAt = now;
157
+ doc.createdBy = actor;
158
+ await doc.save();
159
+ await this.writeAudit('createBlockRule', actor, { rule: this.ruleFromDoc(doc) });
160
+ await this.notifyPolicyChanged();
161
+ return this.ruleFromDoc(doc);
162
+ }
163
+
164
+ public async updateBlockRule(id: string, patch: Partial<Pick<ISecurityBlockRule, 'value' | 'matchMode' | 'reason' | 'enabled'>>, actor = 'system'): Promise<ISecurityBlockRule | null> {
165
+ const doc = await SecurityBlockRuleDoc.findById(id);
166
+ if (!doc) {
167
+ return null;
168
+ }
169
+ if (patch.value !== undefined) doc.value = patch.value.trim();
170
+ if (patch.matchMode !== undefined) doc.matchMode = patch.matchMode;
171
+ if (patch.reason !== undefined) doc.reason = patch.reason;
172
+ if (patch.enabled !== undefined) doc.enabled = patch.enabled;
173
+ doc.updatedAt = Date.now();
174
+ await doc.save();
175
+ await this.writeAudit('updateBlockRule', actor, { id, patch });
176
+ await this.notifyPolicyChanged();
177
+ return this.ruleFromDoc(doc);
178
+ }
179
+
180
+ public async deleteBlockRule(id: string, actor = 'system'): Promise<boolean> {
181
+ const doc = await SecurityBlockRuleDoc.findById(id);
182
+ if (!doc) {
183
+ return false;
184
+ }
185
+ await doc.delete();
186
+ await this.writeAudit('deleteBlockRule', actor, { id });
187
+ await this.notifyPolicyChanged();
188
+ return true;
189
+ }
190
+
191
+ public async compilePolicy(): Promise<ISecurityCompiledPolicy> {
192
+ const rules = await SecurityBlockRuleDoc.findEnabled();
193
+ const intelligenceDocs = await IpIntelligenceDoc.findAll();
194
+ const blockedIps = new Set<string>();
195
+ const blockedCidrs = new Set<string>();
196
+
197
+ for (const rule of rules) {
198
+ const normalizedValue = rule.value.trim();
199
+ if (!normalizedValue) continue;
200
+
201
+ if (rule.type === 'ip') {
202
+ const ip = this.normalizeIp(normalizedValue);
203
+ if (ip && plugins.net.isIP(ip)) blockedIps.add(ip);
204
+ continue;
205
+ }
206
+
207
+ if (rule.type === 'cidr') {
208
+ const cidr = this.normalizeCidr(normalizedValue);
209
+ if (cidr) blockedCidrs.add(cidr);
210
+ continue;
211
+ }
212
+
213
+ for (const doc of intelligenceDocs) {
214
+ if (!this.ruleMatchesIntelligence(rule, doc)) continue;
215
+ const cidr = this.normalizeCidr(doc.networkRange || '');
216
+ if (cidr) {
217
+ blockedCidrs.add(cidr);
218
+ } else if (this.normalizeIp(doc.ipAddress)) {
219
+ blockedIps.add(this.normalizeIp(doc.ipAddress)!);
220
+ }
221
+ }
222
+ }
223
+
224
+ return {
225
+ blockedIps: [...blockedIps].sort(),
226
+ blockedCidrs: [...blockedCidrs].sort(),
227
+ };
228
+ }
229
+
230
+ public async compileSmartProxyPolicy(): Promise<ISecurityCompiledPolicy> {
231
+ return await this.compilePolicy();
232
+ }
233
+
234
+ public async compileRemoteIngressFirewall(): Promise<IRemoteIngressFirewallSnapshot | undefined> {
235
+ const policy = await this.compilePolicy();
236
+ const blockedIps = [
237
+ ...policy.blockedIps.filter((ip) => plugins.net.isIP(ip) === 4),
238
+ ...policy.blockedCidrs.filter((cidr) => plugins.net.isIP(cidr.split('/')[0]) === 4),
239
+ ];
240
+ return blockedIps.length > 0 ? { blockedIps } : undefined;
241
+ }
242
+
243
+ private async matchesAnyReactiveRule(doc: IpIntelligenceDoc): Promise<boolean> {
244
+ const rules = await SecurityBlockRuleDoc.findEnabled();
245
+ return rules.some((rule) => rule.type === 'asn' || rule.type === 'organization'
246
+ ? this.ruleMatchesIntelligence(rule, doc)
247
+ : false);
248
+ }
249
+
250
+ private ruleMatchesIntelligence(rule: SecurityBlockRuleDoc, doc: IpIntelligenceDoc): boolean {
251
+ const value = rule.value.trim().toLowerCase();
252
+ if (!value) return false;
253
+
254
+ if (rule.type === 'asn') {
255
+ return String(doc.asn ?? '') === value.replace(/^as/i, '');
256
+ }
257
+
258
+ if (rule.type === 'organization') {
259
+ const candidates = [doc.asnOrg, doc.registrantOrg]
260
+ .filter(Boolean)
261
+ .map((candidate) => candidate!.toLowerCase());
262
+ if (rule.matchMode === 'exact') {
263
+ return candidates.some((candidate) => candidate === value);
264
+ }
265
+ return candidates.some((candidate) => candidate.includes(value));
266
+ }
267
+
268
+ return false;
269
+ }
270
+
271
+ private normalizeIp(ipAddress: string): string | undefined {
272
+ const ip = ipAddress.trim();
273
+ if (ip.startsWith('::ffff:')) {
274
+ return ip.slice('::ffff:'.length);
275
+ }
276
+ return plugins.net.isIP(ip) ? ip : undefined;
277
+ }
278
+
279
+ private normalizeCidr(value: string): string | undefined {
280
+ const [rawIp, rawPrefix] = value.trim().split('/');
281
+ if (!rawIp || !rawPrefix) return undefined;
282
+ const ip = this.normalizeIp(rawIp);
283
+ if (!ip) return undefined;
284
+ const prefix = Number(rawPrefix);
285
+ const maxPrefix = plugins.net.isIP(ip) === 4 ? 32 : 128;
286
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > maxPrefix) return undefined;
287
+ return `${ip}/${prefix}`;
288
+ }
289
+
290
+ private isPublicIp(ip: string): boolean {
291
+ const family = plugins.net.isIP(ip);
292
+ if (family === 4) {
293
+ const parts = ip.split('.').map((part) => Number(part));
294
+ const [a, b] = parts;
295
+ if (a === 10 || a === 127 || a === 0 || a >= 224) return false;
296
+ if (a === 100 && b >= 64 && b <= 127) return false;
297
+ if (a === 169 && b === 254) return false;
298
+ if (a === 172 && b >= 16 && b <= 31) return false;
299
+ if (a === 192 && b === 168) return false;
300
+ return true;
301
+ }
302
+ if (family === 6) {
303
+ const lower = ip.toLowerCase();
304
+ if (lower === '::1' || lower === '::') return false;
305
+ if (lower.startsWith('fe80:') || lower.startsWith('fc') || lower.startsWith('fd')) return false;
306
+ return true;
307
+ }
308
+ return false;
309
+ }
310
+
311
+ private ruleFromDoc(doc: SecurityBlockRuleDoc): ISecurityBlockRule {
312
+ return {
313
+ id: doc.id,
314
+ type: doc.type,
315
+ value: doc.value,
316
+ matchMode: doc.matchMode,
317
+ enabled: doc.enabled,
318
+ reason: doc.reason,
319
+ createdAt: doc.createdAt,
320
+ updatedAt: doc.updatedAt,
321
+ createdBy: doc.createdBy,
322
+ };
323
+ }
324
+
325
+ private async writeAudit(action: string, actor: string, details: Record<string, unknown>): Promise<void> {
326
+ const doc = new SecurityPolicyAuditDoc();
327
+ doc.id = plugins.uuid.v4();
328
+ doc.action = action;
329
+ doc.actor = actor;
330
+ doc.details = details;
331
+ doc.createdAt = Date.now();
332
+ await doc.save();
333
+ }
334
+
335
+ private async notifyPolicyChanged(): Promise<void> {
336
+ if (this.onPolicyChanged) {
337
+ await this.onPolicyChanged();
338
+ }
339
+ }
340
+ }
@@ -18,4 +18,10 @@ export {
18
18
  ThreatCategory,
19
19
  type IScanResult,
20
20
  type IContentScannerOptions
21
- } from './classes.contentscanner.js';
21
+ } from './classes.contentscanner.js';
22
+
23
+ export {
24
+ SecurityPolicyManager,
25
+ type ISecurityPolicyManagerOptions,
26
+ type IRemoteIngressFirewallSnapshot,
27
+ } from './classes.security-policy-manager.js';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.22.0',
6
+ version: '13.24.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -54,6 +54,7 @@ export interface INetworkState {
54
54
  topIPs: Array<{ ip: string; count: number }>;
55
55
  topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
56
56
  throughputByIP: Array<{ ip: string; in: number; out: number }>;
57
+ ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
57
58
  domainActivity: interfaces.data.IDomainActivity[];
58
59
  throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
59
60
  requestsPerSecond: number;
@@ -66,6 +67,16 @@ export interface INetworkState {
66
67
  error: string | null;
67
68
  }
68
69
 
70
+ export interface ISecurityPolicyState {
71
+ rules: interfaces.data.ISecurityBlockRule[];
72
+ ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
73
+ compiledPolicy: interfaces.data.ISecurityCompiledPolicy | null;
74
+ auditEvents: interfaces.data.ISecurityPolicyAuditEvent[];
75
+ isLoading: boolean;
76
+ error: string | null;
77
+ lastUpdated: number;
78
+ }
79
+
69
80
  export interface ICertificateState {
70
81
  certificates: interfaces.requests.ICertificateInfo[];
71
82
  summary: { total: number; valid: number; expiring: number; expired: number; failed: number; unknown: number };
@@ -164,6 +175,7 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
164
175
  topIPs: [],
165
176
  topIPsByBandwidth: [],
166
177
  throughputByIP: [],
178
+ ipIntelligence: [],
167
179
  domainActivity: [],
168
180
  throughputHistory: [],
169
181
  requestsPerSecond: 0,
@@ -178,6 +190,20 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
178
190
  'soft'
179
191
  );
180
192
 
193
+ export const securityPolicyStatePart = await appState.getStatePart<ISecurityPolicyState>(
194
+ 'securityPolicy',
195
+ {
196
+ rules: [],
197
+ ipIntelligence: [],
198
+ compiledPolicy: null,
199
+ auditEvents: [],
200
+ isLoading: false,
201
+ error: null,
202
+ lastUpdated: 0,
203
+ },
204
+ 'soft',
205
+ );
206
+
181
207
  export const emailOpsStatePart = await appState.getStatePart<IEmailOpsState>(
182
208
  'emailOps',
183
209
  {
@@ -517,9 +543,18 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
517
543
  interfaces.requests.IReq_GetNetworkStats
518
544
  >('/typedrequest', 'getNetworkStats');
519
545
 
520
- const networkStatsResponse = await networkStatsRequest.fire({
521
- identity: context.identity,
522
- });
546
+ const ipIntelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
547
+ interfaces.requests.IReq_ListIpIntelligence
548
+ >('/typedrequest', 'listIpIntelligence');
549
+
550
+ const [networkStatsResponse, ipIntelligenceResponse] = await Promise.all([
551
+ networkStatsRequest.fire({
552
+ identity: context.identity,
553
+ }),
554
+ ipIntelligenceRequest.fire({
555
+ identity: context.identity,
556
+ }),
557
+ ]);
523
558
 
524
559
  // Use the connections data for the connection list
525
560
  // and network stats for throughput and IP analytics
@@ -561,6 +596,7 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
561
596
  topIPs: networkStatsResponse.topIPs || [],
562
597
  topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
563
598
  throughputByIP: networkStatsResponse.throughputByIP || [],
599
+ ipIntelligence: ipIntelligenceResponse.records || [],
564
600
  domainActivity: networkStatsResponse.domainActivity || [],
565
601
  throughputHistory: networkStatsResponse.throughputHistory || [],
566
602
  requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
@@ -582,6 +618,182 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
582
618
  }
583
619
  });
584
620
 
621
+ // ============================================================================
622
+ // Security Policy Actions
623
+ // ============================================================================
624
+
625
+ export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
626
+ async (statePartArg): Promise<ISecurityPolicyState> => {
627
+ const context = getActionContext();
628
+ const currentState = statePartArg.getState()!;
629
+ if (!context.identity) return currentState;
630
+
631
+ try {
632
+ const rulesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
633
+ interfaces.requests.IReq_ListSecurityBlockRules
634
+ >('/typedrequest', 'listSecurityBlockRules');
635
+ const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
636
+ interfaces.requests.IReq_ListIpIntelligence
637
+ >('/typedrequest', 'listIpIntelligence');
638
+ const compiledPolicyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
639
+ interfaces.requests.IReq_GetCompiledSecurityPolicy
640
+ >('/typedrequest', 'getCompiledSecurityPolicy');
641
+ const auditRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
642
+ interfaces.requests.IReq_ListSecurityPolicyAudit
643
+ >('/typedrequest', 'listSecurityPolicyAudit');
644
+
645
+ const [rulesResponse, intelligenceResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
646
+ rulesRequest.fire({ identity: context.identity }),
647
+ intelligenceRequest.fire({ identity: context.identity }),
648
+ compiledPolicyRequest.fire({ identity: context.identity }),
649
+ auditRequest.fire({ identity: context.identity, limit: 100 }),
650
+ ]);
651
+
652
+ return {
653
+ rules: rulesResponse.rules || [],
654
+ ipIntelligence: intelligenceResponse.records || [],
655
+ compiledPolicy: compiledPolicyResponse.policy,
656
+ auditEvents: auditResponse.events || [],
657
+ isLoading: false,
658
+ error: null,
659
+ lastUpdated: Date.now(),
660
+ };
661
+ } catch (error: unknown) {
662
+ return {
663
+ ...currentState,
664
+ isLoading: false,
665
+ error: error instanceof Error ? error.message : 'Failed to fetch security policy',
666
+ };
667
+ }
668
+ },
669
+ );
670
+
671
+ export const createSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
672
+ type: interfaces.data.TSecurityBlockRuleType;
673
+ value: string;
674
+ matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
675
+ reason?: string;
676
+ enabled?: boolean;
677
+ }>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
678
+ const context = getActionContext();
679
+ const currentState = statePartArg.getState()!;
680
+ if (!context.identity) return currentState;
681
+
682
+ try {
683
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
684
+ interfaces.requests.IReq_CreateSecurityBlockRule
685
+ >('/typedrequest', 'createSecurityBlockRule');
686
+
687
+ const response = await request.fire({
688
+ identity: context.identity,
689
+ type: dataArg.type,
690
+ value: dataArg.value,
691
+ matchMode: dataArg.matchMode,
692
+ reason: dataArg.reason,
693
+ enabled: dataArg.enabled,
694
+ });
695
+
696
+ if (!response.success) {
697
+ return { ...currentState, error: response.message || 'Failed to create security block rule' };
698
+ }
699
+
700
+ return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
701
+ } catch (error: unknown) {
702
+ return {
703
+ ...currentState,
704
+ error: error instanceof Error ? error.message : 'Failed to create security block rule',
705
+ };
706
+ }
707
+ });
708
+
709
+ export const updateSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
710
+ id: string;
711
+ value?: string;
712
+ matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
713
+ reason?: string;
714
+ enabled?: boolean;
715
+ }>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
716
+ const context = getActionContext();
717
+ const currentState = statePartArg.getState()!;
718
+ if (!context.identity) return currentState;
719
+
720
+ try {
721
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
722
+ interfaces.requests.IReq_UpdateSecurityBlockRule
723
+ >('/typedrequest', 'updateSecurityBlockRule');
724
+
725
+ const response = await request.fire({
726
+ identity: context.identity,
727
+ id: dataArg.id,
728
+ value: dataArg.value,
729
+ matchMode: dataArg.matchMode,
730
+ reason: dataArg.reason,
731
+ enabled: dataArg.enabled,
732
+ });
733
+
734
+ if (!response.success) {
735
+ return { ...currentState, error: response.message || 'Failed to update security block rule' };
736
+ }
737
+
738
+ return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
739
+ } catch (error: unknown) {
740
+ return {
741
+ ...currentState,
742
+ error: error instanceof Error ? error.message : 'Failed to update security block rule',
743
+ };
744
+ }
745
+ });
746
+
747
+ export const deleteSecurityBlockRuleAction = securityPolicyStatePart.createAction<string>(
748
+ async (statePartArg, ruleId, actionContext): Promise<ISecurityPolicyState> => {
749
+ const context = getActionContext();
750
+ const currentState = statePartArg.getState()!;
751
+ if (!context.identity) return currentState;
752
+
753
+ try {
754
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
755
+ interfaces.requests.IReq_DeleteSecurityBlockRule
756
+ >('/typedrequest', 'deleteSecurityBlockRule');
757
+
758
+ const response = await request.fire({ identity: context.identity, id: ruleId });
759
+ if (!response.success) {
760
+ return { ...currentState, error: response.message || 'Failed to delete security block rule' };
761
+ }
762
+
763
+ return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
764
+ } catch (error: unknown) {
765
+ return {
766
+ ...currentState,
767
+ error: error instanceof Error ? error.message : 'Failed to delete security block rule',
768
+ };
769
+ }
770
+ },
771
+ );
772
+
773
+ export const refreshIpIntelligenceAction = securityPolicyStatePart.createAction<string>(
774
+ async (statePartArg, ipAddress, actionContext): Promise<ISecurityPolicyState> => {
775
+ const context = getActionContext();
776
+ const currentState = statePartArg.getState()!;
777
+ if (!context.identity) return currentState;
778
+
779
+ try {
780
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
781
+ interfaces.requests.IReq_RefreshIpIntelligence
782
+ >('/typedrequest', 'refreshIpIntelligence');
783
+ const response = await request.fire({ identity: context.identity, ipAddress });
784
+ if (!response.success) {
785
+ return { ...currentState, error: response.message || 'Failed to refresh IP intelligence' };
786
+ }
787
+ return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
788
+ } catch (error: unknown) {
789
+ return {
790
+ ...currentState,
791
+ error: error instanceof Error ? error.message : 'Failed to refresh IP intelligence',
792
+ };
793
+ }
794
+ },
795
+ );
796
+
585
797
  // ============================================================================
586
798
  // Email Operations Actions
587
799
  // ============================================================================
@@ -2665,6 +2877,27 @@ async function dispatchCombinedRefreshActionInner() {
2665
2877
  isLoading: false,
2666
2878
  error: null,
2667
2879
  });
2880
+
2881
+ try {
2882
+ const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
2883
+ interfaces.requests.IReq_ListIpIntelligence
2884
+ >('/typedrequest', 'listIpIntelligence');
2885
+ const intelligenceResponse = await intelligenceRequest.fire({ identity: context.identity });
2886
+ networkStatePart.setState({
2887
+ ...networkStatePart.getState()!,
2888
+ ipIntelligence: intelligenceResponse.records || [],
2889
+ });
2890
+ } catch (error) {
2891
+ console.error('IP intelligence refresh failed:', error);
2892
+ }
2893
+ }
2894
+
2895
+ if (currentView === 'security') {
2896
+ try {
2897
+ await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
2898
+ } catch (error) {
2899
+ console.error('Security policy refresh failed:', error);
2900
+ }
2668
2901
  }
2669
2902
 
2670
2903
  // Refresh certificate data if on Domains > Certificates subview