@serve.zone/dcrouter 32.1.1 → 32.1.3
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/deno.json +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +2 -2
- package/dist_ts/config/classes.gateway-client-manager.js +1 -1
- package/dist_ts/db/documents/classes.cached.email.d.ts +0 -1
- package/dist_ts/db/documents/classes.cached.email.js +45 -48
- package/dist_ts/db/documents/classes.email-traffic-bucket.doc.d.ts +0 -1
- package/dist_ts/db/documents/classes.email-traffic-bucket.doc.js +61 -50
- package/dist_ts/db/documents/classes.gateway-client.doc.d.ts +9 -2
- package/dist_ts/db/documents/classes.gateway-client.doc.js +9 -11
- package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +4 -3
- package/dist_ts/db/documents/classes.ip-intelligence.doc.js +27 -31
- package/dist_ts/db/documents/classes.ops-config-event.doc.d.ts +0 -2
- package/dist_ts/db/documents/classes.ops-config-event.doc.js +70 -68
- package/dist_ts/db/documents/classes.smartmta-storage.doc.d.ts +14 -0
- package/dist_ts/db/documents/classes.smartmta-storage.doc.js +17 -3
- package/dist_ts/db/documents/classes.webpush-admission.doc.d.ts +0 -1
- package/dist_ts/db/documents/classes.webpush-admission.doc.js +14 -5
- package/dist_ts/db/documents/classes.webpush-binding.doc.d.ts +1 -1
- package/dist_ts/db/documents/classes.webpush-binding.doc.js +46 -8
- package/dist_ts/db/documents/classes.webpush-spool.doc.d.ts +0 -1
- package/dist_ts/db/documents/classes.webpush-spool.doc.js +34 -6
- package/dist_ts/dns/manager.dns.js +7 -3
- package/dist_ts/email/classes.smartmta-storage-manager.d.ts +15 -3
- package/dist_ts/email/classes.smartmta-storage-manager.js +62 -36
- package/dist_ts/security/classes.security-policy-manager.js +11 -9
- package/dist_ts/webpush/classes.webpush-manager.d.ts +5 -1
- package/dist_ts/webpush/classes.webpush-manager.js +162 -232
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +3 -3
- package/readme.md +1 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +1 -1
- package/ts/config/classes.gateway-client-manager.ts +4 -2
- package/ts/db/documents/classes.cached.email.ts +51 -66
- package/ts/db/documents/classes.email-traffic-bucket.doc.ts +65 -59
- package/ts/db/documents/classes.gateway-client.doc.ts +38 -18
- package/ts/db/documents/classes.ip-intelligence.doc.ts +37 -40
- package/ts/db/documents/classes.ops-config-event.doc.ts +80 -77
- package/ts/db/documents/classes.smartmta-storage.doc.ts +16 -1
- package/ts/db/documents/classes.webpush-admission.doc.ts +14 -5
- package/ts/db/documents/classes.webpush-binding.doc.ts +54 -8
- package/ts/db/documents/classes.webpush-spool.doc.ts +40 -6
- package/ts/dns/manager.dns.ts +6 -2
- package/ts/email/classes.smartmta-storage-manager.ts +66 -50
- package/ts/security/classes.security-policy-manager.ts +10 -8
- package/ts/webpush/classes.webpush-manager.ts +175 -280
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
1
|
import type {
|
|
3
2
|
IStorageManager,
|
|
4
3
|
IStorageListPageOptions,
|
|
5
4
|
IStorageListPageResult,
|
|
6
5
|
} from '@push.rocks/smartmta';
|
|
6
|
+
import { SmartMtaStorageDoc } from '../db/documents/classes.smartmta-storage.doc.js';
|
|
7
7
|
import {
|
|
8
8
|
getSmartMtaTextNamespace,
|
|
9
9
|
hashSmartMtaStorageKey,
|
|
@@ -11,46 +11,45 @@ import {
|
|
|
11
11
|
validateSmartMtaTextValue,
|
|
12
12
|
} from './smartmta-storage-policy.js';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
/**
|
|
15
|
+
* SmartMTA's key/value storage, backed by the content-addressed `SmartMtaStorageDoc`.
|
|
16
|
+
*
|
|
17
|
+
* The document model declares `identityAsDocumentId: 'id'`, so the `sha256(normalizedKey)`
|
|
18
|
+
* identity is the stored `_id`: every read and write below pins `id` by equality and is
|
|
19
|
+
* answered through the primary key, which is what makes `set()` one race-free upsert
|
|
20
|
+
* without a secondary unique index. The model owns its database binding, so this manager
|
|
21
|
+
* holds no connection and no collection handle of its own.
|
|
22
|
+
*/
|
|
23
23
|
export class SmartMtaStorageManager implements IStorageManager {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A stored document is only usable when it still carries the content address it was
|
|
26
|
+
* written under. `_id` is not re-read here: it equals `id` by construction — an
|
|
27
|
+
* identity-pinned upsert derives the inserted `_id` from its own filter, and the client
|
|
28
|
+
* refuses to store a document whose `_id` diverges from its declared identity.
|
|
29
|
+
*/
|
|
30
30
|
private validateDocument(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
):
|
|
34
|
-
const normalizedKey = normalizeSmartMtaStorageKey(
|
|
31
|
+
documentArg: SmartMtaStorageDoc,
|
|
32
|
+
expectedKeyArg?: string,
|
|
33
|
+
): SmartMtaStorageDoc {
|
|
34
|
+
const normalizedKey = normalizeSmartMtaStorageKey(documentArg.key);
|
|
35
35
|
const expectedId = hashSmartMtaStorageKey(normalizedKey);
|
|
36
36
|
if (
|
|
37
|
-
|
|
38
|
-
||
|
|
39
|
-
||
|
|
40
|
-
||
|
|
41
|
-
|| typeof document.value !== 'string'
|
|
37
|
+
documentArg.id !== expectedId
|
|
38
|
+
|| documentArg.key !== normalizedKey
|
|
39
|
+
|| (expectedKeyArg && documentArg.key !== expectedKeyArg)
|
|
40
|
+
|| typeof documentArg.value !== 'string'
|
|
42
41
|
) {
|
|
43
|
-
throw new Error(`Corrupt SmartMTA storage document: ${
|
|
42
|
+
throw new Error(`Corrupt SmartMTA storage document: ${documentArg.id}`);
|
|
44
43
|
}
|
|
45
|
-
validateSmartMtaTextValue(
|
|
46
|
-
return
|
|
44
|
+
validateSmartMtaTextValue(documentArg.key, documentArg.value);
|
|
45
|
+
return documentArg;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
public async get(key: string): Promise<string | null> {
|
|
50
49
|
const normalizedKey = normalizeSmartMtaStorageKey(key);
|
|
51
50
|
getSmartMtaTextNamespace(normalizedKey);
|
|
52
51
|
const id = hashSmartMtaStorageKey(normalizedKey);
|
|
53
|
-
const document = await
|
|
52
|
+
const document = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
|
|
54
53
|
return document ? this.validateDocument(document, normalizedKey).value : null;
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -58,17 +57,21 @@ export class SmartMtaStorageManager implements IStorageManager {
|
|
|
58
57
|
const normalizedKey = normalizeSmartMtaStorageKey(key);
|
|
59
58
|
validateSmartMtaTextValue(normalizedKey, value);
|
|
60
59
|
const id = hashSmartMtaStorageKey(normalizedKey);
|
|
61
|
-
const existing = await
|
|
60
|
+
const existing = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
|
|
62
61
|
if (existing) {
|
|
63
62
|
this.validateDocument(existing, normalizedKey);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
const now = new Date().toISOString();
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// The equality anchor on `id` is mandatory for an upsert on this model: MongoDB derives
|
|
67
|
+
// the inserted primary key from the filter, and the client refuses an unpinned upsert
|
|
68
|
+
// rather than let a generated `_id` contradict the declared identity. It seeds `id`
|
|
69
|
+
// itself from that anchor, so the value never has to be repeated in `$setOnInsert` —
|
|
70
|
+
// and may not be, because `$set` can never modify an immutable `@unI()` field.
|
|
71
|
+
await SmartMtaStorageDoc.atomicUpdate<SmartMtaStorageDoc>(
|
|
72
|
+
{ id },
|
|
69
73
|
{
|
|
70
74
|
$set: {
|
|
71
|
-
id,
|
|
72
75
|
key: normalizedKey,
|
|
73
76
|
value,
|
|
74
77
|
updatedAt: now,
|
|
@@ -80,7 +83,7 @@ export class SmartMtaStorageManager implements IStorageManager {
|
|
|
80
83
|
{ upsert: true },
|
|
81
84
|
);
|
|
82
85
|
|
|
83
|
-
const stored = await
|
|
86
|
+
const stored = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
|
|
84
87
|
if (!stored || this.validateDocument(stored, normalizedKey).value !== value) {
|
|
85
88
|
throw new Error(`SmartMTA storage write verification failed: ${normalizedKey}`);
|
|
86
89
|
}
|
|
@@ -89,16 +92,22 @@ export class SmartMtaStorageManager implements IStorageManager {
|
|
|
89
92
|
public async list(prefix: string): Promise<string[]> {
|
|
90
93
|
const normalizedPrefix = normalizeSmartMtaStorageKey(prefix);
|
|
91
94
|
getSmartMtaTextNamespace(normalizedPrefix);
|
|
92
|
-
const
|
|
95
|
+
const cursor = await SmartMtaStorageDoc.getCursor<SmartMtaStorageDoc>({
|
|
93
96
|
key: {
|
|
94
97
|
$gte: normalizedPrefix,
|
|
95
|
-
$lt: `${normalizedPrefix}
|
|
98
|
+
$lt: `${normalizedPrefix}`,
|
|
96
99
|
},
|
|
97
|
-
})
|
|
100
|
+
});
|
|
101
|
+
let documents: SmartMtaStorageDoc[];
|
|
102
|
+
try {
|
|
103
|
+
documents = await cursor.toArray();
|
|
104
|
+
} finally {
|
|
105
|
+
await cursor.close();
|
|
106
|
+
}
|
|
98
107
|
|
|
99
108
|
return documents
|
|
100
|
-
.map((
|
|
101
|
-
.filter((
|
|
109
|
+
.map((documentArg) => this.validateDocument(documentArg).key)
|
|
110
|
+
.filter((keyArg) => keyArg.startsWith(normalizedPrefix))
|
|
102
111
|
.sort();
|
|
103
112
|
}
|
|
104
113
|
|
|
@@ -126,24 +135,29 @@ export class SmartMtaStorageManager implements IStorageManager {
|
|
|
126
135
|
seekAfter = decoded;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
const
|
|
130
|
-
|
|
138
|
+
const cursor = await SmartMtaStorageDoc.getCursor<SmartMtaStorageDoc>(
|
|
139
|
+
{
|
|
131
140
|
key: {
|
|
132
141
|
...(seekAfter ? { $gt: seekAfter } : { $gte: normalizedPrefix }),
|
|
133
142
|
$lt: `${normalizedPrefix}`,
|
|
134
143
|
},
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
},
|
|
145
|
+
{ sort: { key: 1 }, limit: limit + 1 },
|
|
146
|
+
);
|
|
147
|
+
let documents: SmartMtaStorageDoc[];
|
|
148
|
+
try {
|
|
149
|
+
documents = await cursor.toArray();
|
|
150
|
+
} finally {
|
|
151
|
+
await cursor.close();
|
|
152
|
+
}
|
|
139
153
|
|
|
140
154
|
const hasMore = documents.length > limit;
|
|
141
155
|
const pageDocuments = documents.slice(0, limit);
|
|
142
156
|
const keys = pageDocuments
|
|
143
|
-
.map((
|
|
144
|
-
.filter((
|
|
157
|
+
.map((documentArg) => this.validateDocument(documentArg).key)
|
|
158
|
+
.filter((keyArg) => keyArg.startsWith(normalizedPrefix));
|
|
145
159
|
const lastKey = pageDocuments.length > 0
|
|
146
|
-
?
|
|
160
|
+
? pageDocuments[pageDocuments.length - 1].key
|
|
147
161
|
: undefined;
|
|
148
162
|
|
|
149
163
|
return {
|
|
@@ -158,10 +172,12 @@ export class SmartMtaStorageManager implements IStorageManager {
|
|
|
158
172
|
const normalizedKey = normalizeSmartMtaStorageKey(key);
|
|
159
173
|
getSmartMtaTextNamespace(normalizedKey);
|
|
160
174
|
const id = hashSmartMtaStorageKey(normalizedKey);
|
|
161
|
-
const existing = await
|
|
175
|
+
const existing = await SmartMtaStorageDoc.getInstance<SmartMtaStorageDoc>({ id });
|
|
162
176
|
if (existing) {
|
|
163
177
|
this.validateDocument(existing, normalizedKey);
|
|
164
|
-
|
|
178
|
+
// The identity pins a single document, which is what the client requires of a
|
|
179
|
+
// singular delete; the primary key answers it.
|
|
180
|
+
await SmartMtaStorageDoc.atomicDelete<SmartMtaStorageDoc>({ id });
|
|
165
181
|
}
|
|
166
182
|
}
|
|
167
183
|
}
|
|
@@ -296,13 +296,13 @@ export class SecurityPolicyManager {
|
|
|
296
296
|
const expiryCutoff = this.now() - IP_INTELLIGENCE_RETENTION_MAX_AGE_MS;
|
|
297
297
|
|
|
298
298
|
while (!this.isStopping) {
|
|
299
|
-
const
|
|
299
|
+
const expiredIpAddresses = await IpIntelligenceDoc.findOldestIpAddresses({
|
|
300
300
|
lastSeenBefore: expiryCutoff,
|
|
301
301
|
limit: IP_INTELLIGENCE_RETENTION_BATCH_SIZE,
|
|
302
302
|
});
|
|
303
|
-
if (
|
|
304
|
-
deletedCount += await IpIntelligenceDoc.
|
|
305
|
-
if (
|
|
303
|
+
if (expiredIpAddresses.length === 0) break;
|
|
304
|
+
deletedCount += await IpIntelligenceDoc.deleteByIpAddresses(expiredIpAddresses);
|
|
305
|
+
if (expiredIpAddresses.length < IP_INTELLIGENCE_RETENTION_BATCH_SIZE) break;
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
let currentCount = await IpIntelligenceDoc.countAll();
|
|
@@ -315,13 +315,15 @@ export class SecurityPolicyManager {
|
|
|
315
315
|
remainingToDelete,
|
|
316
316
|
IP_INTELLIGENCE_RETENTION_BATCH_SIZE,
|
|
317
317
|
);
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
318
|
+
const oldestIpAddresses = await IpIntelligenceDoc.findOldestIpAddresses({
|
|
319
|
+
limit: batchLimit,
|
|
320
|
+
});
|
|
321
|
+
if (oldestIpAddresses.length === 0) break;
|
|
322
|
+
const deletedThisBatch = await IpIntelligenceDoc.deleteByIpAddresses(oldestIpAddresses);
|
|
321
323
|
deletedCount += deletedThisBatch;
|
|
322
324
|
currentCount -= deletedThisBatch;
|
|
323
325
|
remainingToDelete -= deletedThisBatch;
|
|
324
|
-
if (deletedThisBatch === 0 ||
|
|
326
|
+
if (deletedThisBatch === 0 || oldestIpAddresses.length < batchLimit) break;
|
|
325
327
|
}
|
|
326
328
|
}
|
|
327
329
|
|