@serve.zone/coremail 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +43 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.auth.d.ts +19 -0
- package/dist_ts/classes.auth.js +58 -1
- package/dist_ts/classes.coremail.d.ts +3 -0
- package/dist_ts/classes.coremail.js +11 -4
- package/dist_ts/classes.gateway.d.ts +11 -1
- package/dist_ts/classes.gateway.js +101 -63
- package/dist_ts/classes.inbound.d.ts +10 -1
- package/dist_ts/classes.inbound.js +45 -7
- package/dist_ts/classes.maintenance.js +16 -1
- package/dist_ts/classes.models.d.ts +24 -2
- package/dist_ts/classes.models.js +72 -2
- package/dist_ts/classes.server.d.ts +6 -2
- package/dist_ts/classes.server.js +18 -68
- package/dist_ts/classes.smtpsubmission.d.ts +85 -0
- package/dist_ts/classes.smtpsubmission.js +379 -0
- package/dist_ts/classes.storage.d.ts +9 -0
- package/dist_ts/classes.storage.js +44 -1
- package/dist_ts/classes.submissions.d.ts +27 -1
- package/dist_ts/classes.submissions.js +164 -14
- package/dist_ts/coremail.errors.d.ts +18 -0
- package/dist_ts/coremail.errors.js +84 -0
- package/dist_ts/coremail.log.d.ts +1 -1
- package/dist_ts/coremail.log.js +1 -1
- package/dist_ts/coremail.mime.d.ts +15 -0
- package/dist_ts/coremail.mime.js +76 -1
- package/dist_ts/coremail.persistence.d.ts +38 -1
- package/dist_ts/coremail.persistence.js +124 -40
- package/dist_ts/coremail.stats.d.ts +72 -0
- package/dist_ts/coremail.stats.js +242 -0
- package/dist_ts/coremail.validation.d.ts +15 -2
- package/dist_ts/coremail.validation.js +26 -78
- package/dist_ts/interfaces.d.ts +18 -0
- package/dist_ts/plugins.d.ts +1 -0
- package/dist_ts/plugins.js +2 -1
- package/package.json +12 -11
- package/readme.md +51 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.auth.ts +77 -0
- package/ts/classes.coremail.ts +20 -0
- package/ts/classes.gateway.ts +121 -66
- package/ts/classes.inbound.ts +71 -6
- package/ts/classes.maintenance.ts +14 -0
- package/ts/classes.models.ts +67 -1
- package/ts/classes.server.ts +23 -80
- package/ts/classes.smtpsubmission.ts +466 -0
- package/ts/classes.storage.ts +54 -0
- package/ts/classes.submissions.ts +247 -12
- package/ts/coremail.errors.ts +111 -0
- package/ts/coremail.log.ts +1 -0
- package/ts/coremail.mime.ts +89 -0
- package/ts/coremail.persistence.ts +196 -48
- package/ts/coremail.stats.ts +342 -0
- package/ts/coremail.validation.ts +31 -103
- package/ts/interfaces.ts +20 -0
- package/ts/plugins.ts +1 -0
|
@@ -93,107 +93,17 @@ export const normalizeCoreMailEnvelope = (
|
|
|
93
93
|
return { mailFrom, rcptTo };
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'heloName',
|
|
107
|
-
'tls',
|
|
108
|
-
'authUsername',
|
|
109
|
-
'authenticatedCredentialId',
|
|
110
|
-
],
|
|
111
|
-
'connectionInfo',
|
|
112
|
-
);
|
|
113
|
-
if (
|
|
114
|
-
value.protocol !== 'smtp'
|
|
115
|
-
&& value.protocol !== 'submission'
|
|
116
|
-
&& value.protocol !== 'api'
|
|
117
|
-
) {
|
|
118
|
-
throw new Error('CoreMail gateway connection protocol is invalid.');
|
|
119
|
-
}
|
|
120
|
-
for (const key of [
|
|
121
|
-
'remoteAddress',
|
|
122
|
-
'proxyAddress',
|
|
123
|
-
'heloName',
|
|
124
|
-
'authUsername',
|
|
125
|
-
'authenticatedCredentialId',
|
|
126
|
-
] as const) {
|
|
127
|
-
if (
|
|
128
|
-
value[key] !== undefined
|
|
129
|
-
&& (
|
|
130
|
-
typeof value[key] !== 'string'
|
|
131
|
-
|| value[key].length === 0
|
|
132
|
-
|| Buffer.byteLength(value[key], 'utf8') > 512
|
|
133
|
-
|| value[key].trim() !== value[key]
|
|
134
|
-
)
|
|
135
|
-
) {
|
|
136
|
-
throw new Error(`CoreMail gateway connection ${key} is invalid.`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (value.tls !== undefined && typeof value.tls !== 'boolean') {
|
|
140
|
-
throw new Error('CoreMail gateway connection TLS state is invalid.');
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
protocol: value.protocol,
|
|
144
|
-
...(value.remoteAddress === undefined
|
|
145
|
-
? {}
|
|
146
|
-
: { remoteAddress: value.remoteAddress as string }),
|
|
147
|
-
...(value.proxyAddress === undefined
|
|
148
|
-
? {}
|
|
149
|
-
: { proxyAddress: value.proxyAddress as string }),
|
|
150
|
-
...(value.heloName === undefined
|
|
151
|
-
? {}
|
|
152
|
-
: { heloName: value.heloName as string }),
|
|
153
|
-
...(value.tls === undefined ? {} : { tls: value.tls }),
|
|
154
|
-
...(value.authUsername === undefined
|
|
155
|
-
? {}
|
|
156
|
-
: { authUsername: value.authUsername as string }),
|
|
157
|
-
...(value.authenticatedCredentialId === undefined
|
|
158
|
-
? {}
|
|
159
|
-
: {
|
|
160
|
-
authenticatedCredentialId:
|
|
161
|
-
value.authenticatedCredentialId as string,
|
|
162
|
-
}),
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export const normalizeCoreMailGatewayMessage = (
|
|
167
|
-
valueArg: unknown,
|
|
168
|
-
): plugins.serveZoneInterfaces.data.ICoreMailGatewayMessageDescriptor => {
|
|
169
|
-
const value = requirePlainRecord(valueArg, 'gatewayMessage');
|
|
170
|
-
requireExactKeys(value, ['envelope', 'rawMime'], [], 'gatewayMessage');
|
|
171
|
-
const rawMime = requirePlainRecord(value.rawMime, 'gatewayMessage.rawMime');
|
|
172
|
-
requireExactKeys(
|
|
173
|
-
rawMime,
|
|
174
|
-
['sha256', 'lengthBytes', 'contentType'],
|
|
175
|
-
[],
|
|
176
|
-
'gatewayMessage.rawMime',
|
|
177
|
-
);
|
|
178
|
-
if (
|
|
179
|
-
rawMime.contentType !== 'message/rfc822'
|
|
180
|
-
|| !Number.isSafeInteger(rawMime.lengthBytes)
|
|
181
|
-
|| Number(rawMime.lengthBytes) < 1
|
|
182
|
-
|| Number(rawMime.lengthBytes)
|
|
183
|
-
> plugins.serveZoneInterfaces.data.coreMailLimits.serializedMimeBytes
|
|
184
|
-
) {
|
|
185
|
-
throw new Error('CoreMail gateway MIME descriptor is invalid.');
|
|
96
|
+
/**
|
|
97
|
+
* The single sender-authorization rule every outbound path applies: the
|
|
98
|
+
* envelope sender must be an exact member of the binding's allowed senders.
|
|
99
|
+
*/
|
|
100
|
+
export const assertCoreMailSenderAllowed = (
|
|
101
|
+
senderAddressArg: string,
|
|
102
|
+
bindingArg: plugins.serveZoneInterfaces.data.ICoreMailBindingDesiredState,
|
|
103
|
+
): void => {
|
|
104
|
+
if (!bindingArg.allowedSenders.includes(senderAddressArg)) {
|
|
105
|
+
throw new Error('CoreMail sender is not authorized by the binding.');
|
|
186
106
|
}
|
|
187
|
-
return {
|
|
188
|
-
envelope: normalizeCoreMailEnvelope(value.envelope),
|
|
189
|
-
rawMime: {
|
|
190
|
-
sha256: plugins.serveZoneInterfaces.data.normalizeCoreMailSha256(
|
|
191
|
-
rawMime.sha256,
|
|
192
|
-
),
|
|
193
|
-
lengthBytes: Number(rawMime.lengthBytes),
|
|
194
|
-
contentType: 'message/rfc822',
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
107
|
};
|
|
198
108
|
|
|
199
109
|
export const normalizeCoreMailOutboundMessage = (
|
|
@@ -202,12 +112,30 @@ export const normalizeCoreMailOutboundMessage = (
|
|
|
202
112
|
): TMessage => {
|
|
203
113
|
const message = plugins.serveZoneInterfaces.data
|
|
204
114
|
.normalizeCoreMailOutboundMessageDescriptor(valueArg);
|
|
205
|
-
|
|
206
|
-
throw new Error('CoreMail sender is not authorized by the binding.');
|
|
207
|
-
}
|
|
115
|
+
assertCoreMailSenderAllowed(message.sender.address, bindingArg);
|
|
208
116
|
return message;
|
|
209
117
|
};
|
|
210
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Build the outbound message descriptor of an exact-byte SMTP submission.
|
|
121
|
+
*
|
|
122
|
+
* An SMTP submission has no CoreMail-composed body: the bytes are opaque and
|
|
123
|
+
* are owned by the immutable MIME tuple, so the descriptor carries only the
|
|
124
|
+
* SMTP envelope. `parts` is therefore empty, which is why such a descriptor is
|
|
125
|
+
* deliberately not routed through
|
|
126
|
+
* `normalizeCoreMailOutboundMessageDescriptor` (it requires at least one part).
|
|
127
|
+
*/
|
|
128
|
+
export const createCoreMailRawOutboundMessage = (
|
|
129
|
+
envelopeArg: plugins.serveZoneInterfaces.data.ICoreMailEnvelope,
|
|
130
|
+
): TMessage => ({
|
|
131
|
+
sender: { address: envelopeArg.mailFrom },
|
|
132
|
+
recipients: {
|
|
133
|
+
to: envelopeArg.rcptTo.map((recipientArg) => ({ address: recipientArg })),
|
|
134
|
+
},
|
|
135
|
+
subject: '',
|
|
136
|
+
parts: [],
|
|
137
|
+
});
|
|
138
|
+
|
|
211
139
|
export const createCoreMailSubmissionDigest = (
|
|
212
140
|
idempotencyKeyArg: string,
|
|
213
141
|
messageArg: TMessage,
|
package/ts/interfaces.ts
CHANGED
|
@@ -70,6 +70,24 @@ export interface ITransferAuthority {
|
|
|
70
70
|
bindingId: string;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* How an outbound submission entered CoreMail. Records persisted before this
|
|
75
|
+
* discriminator existed carry no value and are read as 'api'.
|
|
76
|
+
*/
|
|
77
|
+
export type TCoreMailSubmissionSource = 'api' | 'smtp';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Exact-byte SMTP submission handed to CoreMail by an authenticated MSA
|
|
81
|
+
* session. CoreMail never parses or rewrites `rawMime`.
|
|
82
|
+
*/
|
|
83
|
+
export interface ICoreMailRawSubmissionInput {
|
|
84
|
+
envelope: {
|
|
85
|
+
from: string;
|
|
86
|
+
to: string[];
|
|
87
|
+
};
|
|
88
|
+
rawMime: Uint8Array;
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
export interface ISubmissionPartRecord {
|
|
74
92
|
partId: string;
|
|
75
93
|
kind: plugins.serveZoneInterfaces.data.TCoreMailPartKind;
|
|
@@ -91,6 +109,8 @@ export interface ICoreMailReadiness {
|
|
|
91
109
|
exactPurge: boolean;
|
|
92
110
|
controlBootstrap: boolean;
|
|
93
111
|
gateway: boolean;
|
|
112
|
+
/** False whenever the desired state asks for an SMTP listener that is not accepting submissions. */
|
|
113
|
+
smtpSubmission: boolean;
|
|
94
114
|
}
|
|
95
115
|
|
|
96
116
|
export interface ICoreMailAuthLimiterSnapshot {
|
package/ts/plugins.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * as projectinfo from '@push.rocks/projectinfo';
|
|
|
6
6
|
export * as smartbucket from '@push.rocks/smartbucket';
|
|
7
7
|
export * as smartdata from '@push.rocks/smartdata';
|
|
8
8
|
export * as smartlog from '@push.rocks/smartlog';
|
|
9
|
+
export * as smartmta from '@push.rocks/smartmta';
|
|
9
10
|
export * as serveZoneInterfaces from '@serve.zone/interfaces';
|
|
10
11
|
export * as argon2 from 'argon2';
|
|
11
12
|
export * as nodeCrypto from 'node:crypto';
|