@push.rocks/smartmta 9.1.0 → 9.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.
package/changelog.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-07-26 - 9.2.0
4
+
5
+ ### Features
6
+
7
+ - add SCRAM-SHA-256 verifier derivation helper (auth)
8
+ - `deriveScramVerifier(password, iterations = SCRAM_DEFAULT_ITERATIONS)` is the canonical embedder-facing RFC 5802 derivation (fresh random 16-byte salt), matching exactly what `UnifiedEmailServer` verifies at AUTH time; bounds are enforced at derivation.
9
+ - `SCRAM_DEFAULT_ITERATIONS` is 600,000 (OWASP-aligned for PBKDF2-HMAC-SHA256) — machine-generated high-entropy secrets gain nothing from high counts and callers may pass lower values within the accepted bounds.
10
+ - Covered by validation, reference-derivation, and end-to-end PLAIN authentication tests.
11
+ - Known follow-ups (not in this release): the private verify-side `computeScramCredentials` still duplicates the derivation formula and should converge onto the shared helper; the legacy plaintext-credential path also still derives SCRAM fields with a hardcoded 4096 iterations — one more reason for embedders to finish moving to stored verifiers.
12
+
13
+ ## 2026-07-26 - 9.1.1
14
+
15
+ ### Changes
16
+
17
+ - export the routing validation helpers (`assertValidAuthAccounts`, `assertValidRetentionDays`, `assertValidInboundPolicy`, `SCRAM_MIN_ITERATIONS`, `SCRAM_MAX_ITERATIONS`) from the package root so embedders can pre-validate DB-sourced `auth.accounts` with the exact same rules `UnifiedEmailServer` enforces at construction/`updateOptions` time (routing)
18
+
3
19
  ## 2026-07-26 - 9.1.0
4
20
 
5
21
  ### Features
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmta',
6
- version: '9.1.0',
6
+ version: '9.2.0',
7
7
  description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlIQUF5SDtDQUN2SSxDQUFBIn0=
@@ -1,4 +1,4 @@
1
- import type { ISmtpAuthAccount } from './interfaces.js';
1
+ import type { ISmtpAuthAccount, ISmtpScramVerifier } from './interfaces.js';
2
2
  /**
3
3
  * PBKDF2 iteration bounds for SCRAM verifiers. The lower bound is the RFC
4
4
  * 5802 recommended minimum; the upper bound caps per-attempt CPU so a single
@@ -16,6 +16,27 @@ export declare function assertValidAuthAccounts(accounts: ISmtpAuthAccount[] | u
16
16
  * Reject retention values that cannot be represented and scheduled reliably.
17
17
  */
18
18
  export declare function assertValidRetentionDays(retentionDays: unknown, owner: string): asserts retentionDays is number;
19
+ /**
20
+ * Default PBKDF2 iteration count for freshly derived SCRAM verifiers —
21
+ * OWASP-aligned for PBKDF2-HMAC-SHA256, protecting human-chosen passwords
22
+ * against offline attack on a leaked verifier store. Costs roughly 100-150ms
23
+ * per derivation and per PLAIN/LOGIN authentication on the bounded async
24
+ * pool, still far inside the Rust bridge's 5s callback window. Callers whose
25
+ * passwords are machine-generated high-entropy secrets gain nothing from
26
+ * high counts and may pass a lower value within the accepted bounds.
27
+ */
28
+ export declare const SCRAM_DEFAULT_ITERATIONS = 600000;
29
+ /**
30
+ * Derive an RFC 5802 SCRAM-SHA-256 verifier from a plaintext password with a
31
+ * fresh random 16-byte salt. This is the canonical embedder-facing derivation
32
+ * matching exactly what `UnifiedEmailServer` verifies at AUTH time — persist
33
+ * only the returned verifier, never the plaintext.
34
+ *
35
+ * SaltedPassword = PBKDF2-HMAC-SHA256(password, salt, iterations, 32);
36
+ * StoredKey = SHA256(HMAC-SHA256(SaltedPassword, 'Client Key'));
37
+ * ServerKey = HMAC-SHA256(SaltedPassword, 'Server Key').
38
+ */
39
+ export declare function deriveScramVerifier(password: string, iterations?: number): ISmtpScramVerifier;
19
40
  export declare function assertValidInboundPolicy(inboundPolicy: {
20
41
  defaultAction: unknown;
21
42
  retentionDays: unknown;
@@ -1,3 +1,4 @@
1
+ import * as crypto from 'node:crypto';
1
2
  /**
2
3
  * PBKDF2 iteration bounds for SCRAM verifiers. The lower bound is the RFC
3
4
  * 5802 recommended minimum; the upper bound caps per-attempt CPU so a single
@@ -104,6 +105,48 @@ export function assertValidRetentionDays(retentionDays, owner) {
104
105
  throw new Error(`${owner} retentionDays must be a finite positive integer`);
105
106
  }
106
107
  }
108
+ /**
109
+ * Default PBKDF2 iteration count for freshly derived SCRAM verifiers —
110
+ * OWASP-aligned for PBKDF2-HMAC-SHA256, protecting human-chosen passwords
111
+ * against offline attack on a leaked verifier store. Costs roughly 100-150ms
112
+ * per derivation and per PLAIN/LOGIN authentication on the bounded async
113
+ * pool, still far inside the Rust bridge's 5s callback window. Callers whose
114
+ * passwords are machine-generated high-entropy secrets gain nothing from
115
+ * high counts and may pass a lower value within the accepted bounds.
116
+ */
117
+ export const SCRAM_DEFAULT_ITERATIONS = 600_000;
118
+ /**
119
+ * Derive an RFC 5802 SCRAM-SHA-256 verifier from a plaintext password with a
120
+ * fresh random 16-byte salt. This is the canonical embedder-facing derivation
121
+ * matching exactly what `UnifiedEmailServer` verifies at AUTH time — persist
122
+ * only the returned verifier, never the plaintext.
123
+ *
124
+ * SaltedPassword = PBKDF2-HMAC-SHA256(password, salt, iterations, 32);
125
+ * StoredKey = SHA256(HMAC-SHA256(SaltedPassword, 'Client Key'));
126
+ * ServerKey = HMAC-SHA256(SaltedPassword, 'Server Key').
127
+ */
128
+ export function deriveScramVerifier(password, iterations = SCRAM_DEFAULT_ITERATIONS) {
129
+ if (typeof password !== 'string' || password.length === 0) {
130
+ throw new Error('Cannot derive a SCRAM verifier from an empty password');
131
+ }
132
+ if (!Number.isInteger(iterations)
133
+ || iterations < SCRAM_MIN_ITERATIONS
134
+ || iterations > SCRAM_MAX_ITERATIONS) {
135
+ throw new Error(`SCRAM verifier iterations must be an integer between ${SCRAM_MIN_ITERATIONS} and ${SCRAM_MAX_ITERATIONS}`);
136
+ }
137
+ const salt = crypto.randomBytes(16);
138
+ const saltedPassword = crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
139
+ const clientKey = crypto.createHmac('sha256', saltedPassword).update('Client Key').digest();
140
+ const storedKey = crypto.createHash('sha256').update(clientKey).digest();
141
+ const serverKey = crypto.createHmac('sha256', saltedPassword).update('Server Key').digest();
142
+ return {
143
+ algorithm: 'SCRAM-SHA-256',
144
+ salt: salt.toString('base64'),
145
+ iterations,
146
+ storedKey: storedKey.toString('base64'),
147
+ serverKey: serverKey.toString('base64'),
148
+ };
149
+ }
107
150
  export function assertValidInboundPolicy(inboundPolicy, domain) {
108
151
  if (inboundPolicy.defaultAction !== 'store'
109
152
  && inboundPolicy.defaultAction !== 'reject') {
@@ -111,4 +154,4 @@ export function assertValidInboundPolicy(inboundPolicy, domain) {
111
154
  }
112
155
  assertValidRetentionDays(inboundPolicy.retentionDays, `Inbound policy for domain ${domain}`);
113
156
  }
114
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnVuY3Rpb25zLnJvdXRpbmcudmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9mdW5jdGlvbnMucm91dGluZy52YWxpZGF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLENBQUM7QUFDekMsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsU0FBUyxDQUFDO0FBRTlDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFjLEVBQUUsVUFBbUIsRUFBVyxFQUFFO0lBQ3hFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ2xFLElBQUksQ0FBQztRQUNILE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzdDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDdkMsa0VBQWtFO1FBQ2xFLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxLQUFLLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDL0UsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBQ0QsT0FBTyxVQUFVLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssVUFBVSxDQUFDO0lBQ25FLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRjs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLHVCQUF1QixDQUNyQyxRQUF3QztJQUV4QyxJQUFJLFFBQVEsS0FBSyxTQUFTO1FBQUUsT0FBTztJQUNuQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQzdCLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztJQUNwRCxDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztJQUNwQyxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQy9CLE1BQU0sUUFBUSxHQUFHLE9BQU8sRUFBRSxRQUFRLENBQUM7UUFDbkMsSUFBSSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxDQUFDLENBQUM7UUFDeEUsQ0FBQztRQUNELE1BQU0sS0FBSyxHQUFHLGdCQUFnQixRQUFRLEVBQUUsQ0FBQztRQUN6QyxJQUFJLFNBQVMsQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksS0FBSyxDQUFDLEdBQUcsS0FBSywrQkFBK0IsQ0FBQyxDQUFDO1FBQzNELENBQUM7UUFDRCxTQUFTLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRXhCLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUVkLENBQUM7UUFDZCxNQUFNLFdBQVcsR0FBRyxVQUFVLEVBQUUsUUFBUSxLQUFLLFNBQVMsQ0FBQztRQUN2RCxNQUFNLFdBQVcsR0FBRyxVQUFVLEVBQUUsUUFBUSxLQUFLLFNBQVMsQ0FBQztRQUN2RCxJQUFJLFdBQVcsS0FBSyxXQUFXLEVBQUUsQ0FBQztZQUNoQyxNQUFNLElBQUksS0FBSyxDQUNiLEdBQUcsS0FBSyw4REFBOEQsQ0FDdkUsQ0FBQztRQUNKLENBQUM7UUFDRCxJQUFJLFdBQVcsSUFBSSxPQUFPLFVBQVcsQ0FBQyxRQUFRLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDNUQsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUssdUNBQXVDLENBQUMsQ0FBQztRQUNuRSxDQUFDO1FBQ0QsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUNoQixNQUFNLFFBQVEsR0FBRyxVQUFXLENBQUMsUUFBdUMsQ0FBQztZQUNyRSxJQUFJLFFBQVEsRUFBRSxTQUFTLEtBQUssZUFBZSxFQUFFLENBQUM7Z0JBQzVDLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxLQUFLLDJDQUEyQyxDQUFDLENBQUM7WUFDdkUsQ0FBQztZQUNELElBQ0UsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUM7bUJBQ25DLFFBQVEsQ0FBQyxVQUFXLEdBQUcsb0JBQW9CO21CQUMzQyxRQUFRLENBQUMsVUFBVyxHQUFHLG9CQUFvQixFQUM5QyxDQUFDO2dCQUNELE1BQU0sSUFBSSxLQUFLLENBQ2IsR0FBRyxLQUFLLG1EQUFtRCxvQkFBb0IsUUFBUSxvQkFBb0IsRUFBRSxDQUM5RyxDQUFDO1lBQ0osQ0FBQztZQUNELElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDckMsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUsseUNBQXlDLENBQUMsQ0FBQztZQUNyRSxDQUFDO1lBQ0QsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQztnQkFDOUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUsscURBQXFELENBQUMsQ0FBQztZQUNqRixDQUFDO1lBQ0QsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLEVBQUUsQ0FBQztnQkFDOUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUsscURBQXFELENBQUMsQ0FBQztZQUNqRixDQUFDO1FBQ0gsQ0FBQztRQUVELE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUM7UUFDNUIsSUFBSSxLQUFLLEtBQUssU0FBUztZQUFFLFNBQVM7UUFDbEMsSUFDRSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQztlQUMxQixLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO2VBQzFCLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLE9BQU8sS0FBSyxRQUFRLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUMsRUFDbEYsQ0FBQztZQUNELE1BQU0sSUFBSSxLQUFLLENBQ2IsR0FBRyxLQUFLLDhEQUE4RCxDQUN2RSxDQUFDO1FBQ0osQ0FBQztRQUNELElBQUksS0FBSyxDQUFDLFVBQVUsS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUNuQyxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBSSxLQUFLLEtBQUssSUFBSSxLQUFLLENBQUMsVUFBVSxDQUFDLElBQUksS0FBSyxVQUFVLEVBQUUsQ0FBQztnQkFDNUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUssZ0RBQWdELENBQUMsQ0FBQztZQUM1RSxDQUFDO1lBQ0QsSUFDRSxLQUFLLENBQUMsVUFBVSxDQUFDLElBQUksS0FBSyxVQUFVO21CQUNqQyxDQUNELENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQzt1QkFDdEMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUM7dUJBQ3RDLEtBQUssQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksQ0FDL0IsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLE9BQU8sT0FBTyxLQUFLLFFBQVEsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FDNUQsQ0FDRixFQUNELENBQUM7Z0JBQ0QsTUFBTSxJQUFJLEtBQUssQ0FDYixHQUFHLEtBQUssNEVBQTRFLENBQ3JGLENBQUM7WUFDSixDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7QUFDSCxDQUFDO0FBRUQ7O0dBRUc7QUFDSCxNQUFNLFVBQVUsd0JBQXdCLENBQ3RDLGFBQXNCLEVBQ3RCLEtBQWE7SUFFYixJQUNFLE9BQU8sYUFBYSxLQUFLLFFBQVE7V0FDOUIsQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLGFBQWEsQ0FBQztXQUNwQyxhQUFhLElBQUksQ0FBQyxFQUNyQixDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FBQyxHQUFHLEtBQUssa0RBQWtELENBQUMsQ0FBQztJQUM5RSxDQUFDO0FBQ0gsQ0FBQztBQUVELE1BQU0sVUFBVSx3QkFBd0IsQ0FDdEMsYUFHQyxFQUNELE1BQWM7SUFFZCxJQUNFLGFBQWEsQ0FBQyxhQUFhLEtBQUssT0FBTztXQUNwQyxhQUFhLENBQUMsYUFBYSxLQUFLLFFBQVEsRUFDM0MsQ0FBQztRQUNELE1BQU0sSUFBSSxLQUFLLENBQ2IsNkJBQTZCLE1BQU0sd0NBQXdDLENBQzVFLENBQUM7SUFDSixDQUFDO0lBQ0Qsd0JBQXdCLENBQ3RCLGFBQWEsQ0FBQyxhQUFhLEVBQzNCLDZCQUE2QixNQUFNLEVBQUUsQ0FDdEMsQ0FBQztBQUNKLENBQUMifQ==
157
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZnVuY3Rpb25zLnJvdXRpbmcudmFsaWRhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvcm91dGluZy9mdW5jdGlvbnMucm91dGluZy52YWxpZGF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxNQUFNLE1BQU0sYUFBYSxDQUFDO0FBSXRDOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLENBQUM7QUFDekMsTUFBTSxDQUFDLE1BQU0sb0JBQW9CLEdBQUcsU0FBUyxDQUFDO0FBRTlDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFjLEVBQUUsVUFBbUIsRUFBVyxFQUFFO0lBQ3hFLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ2xFLElBQUksQ0FBQztRQUNILE1BQU0sT0FBTyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzdDLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDdkMsa0VBQWtFO1FBQ2xFLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxLQUFLLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDL0UsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBQ0QsT0FBTyxVQUFVLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssVUFBVSxDQUFDO0lBQ25FLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7QUFDSCxDQUFDLENBQUM7QUFFRjs7OztHQUlHO0FBQ0gsTUFBTSxV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
@@ -5,3 +5,4 @@ export * from './interfaces.js';
5
5
  export * from './classes.domain.registry.js';
6
6
  export * from './classes.email.action.executor.js';
7
7
  export * from './classes.dkim.manager.js';
8
+ export * from './functions.routing.validation.js';
@@ -6,4 +6,5 @@ export * from './interfaces.js';
6
6
  export * from './classes.domain.registry.js';
7
7
  export * from './classes.email.action.executor.js';
8
8
  export * from './classes.dkim.manager.js';
9
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL3JvdXRpbmcvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsMkJBQTJCO0FBQzNCLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxtQ0FBbUMsQ0FBQztBQUNsRCxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLG9DQUFvQyxDQUFDO0FBQ25ELGNBQWMsMkJBQTJCLENBQUMifQ==
9
+ export * from './functions.routing.validation.js';
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL3JvdXRpbmcvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsMkJBQTJCO0FBQzNCLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxtQ0FBbUMsQ0FBQztBQUNsRCxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsaUJBQWlCLENBQUM7QUFDaEMsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLG9DQUFvQyxDQUFDO0FBQ25ELGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyxtQ0FBbUMsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartmta",
3
- "version": "9.1.0",
3
+ "version": "9.2.0",
4
4
  "description": "A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.",
5
5
  "keywords": [
6
6
  "mta",
package/readme.md CHANGED
@@ -355,6 +355,8 @@ Enforcement semantics:
355
355
 
356
356
  Configuration is validated loudly at construction and `updateOptions` time: unusable accounts (empty `scope.senders`, `mode: 'patterns'` without patterns, both or neither credential kind, malformed base64 keys, `iterations` outside `[4096, 1000000]`, duplicate usernames) throw instead of degrading into silent deny-all or silent auth failure. Note that `updateOptions({ auth })` replaces the whole `auth` block — include `users`, `required`, and `methods` again if they should persist.
357
357
 
358
+ Deriving verifiers: `deriveScramVerifier(password, iterations?)` is the canonical embedder-facing derivation, matching exactly what the server verifies at AUTH time — persist only the returned verifier. The exported `SCRAM_DEFAULT_ITERATIONS` (600,000) is OWASP-aligned to protect human-chosen passwords against offline attack on a leaked verifier store, at roughly 100-150ms per derivation and per `PLAIN`/`LOGIN` authentication on the bounded async pool. Machine-generated high-entropy secrets gain nothing from high counts — callers may pass any lower value within `[SCRAM_MIN_ITERATIONS, SCRAM_MAX_ITERATIONS]`. `assertValidAuthAccounts` is exported too, so embedders can pre-validate database-sourced account sets with the exact rules the server enforces.
359
+
358
360
  Compatibility probe: `smartMtaCapabilities.smtpAuthAccountsAndScopes`.
359
361
 
360
362
  ### 📧 Sending Emails (Automatic MX Discovery)
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmta',
6
- version: '9.1.0',
6
+ version: '9.2.0',
7
7
  description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
8
8
  }
@@ -1,3 +1,5 @@
1
+ import * as crypto from 'node:crypto';
2
+
1
3
  import type { ISmtpAuthAccount, ISmtpScramVerifier } from './interfaces.js';
2
4
 
3
5
  /**
@@ -134,6 +136,57 @@ export function assertValidRetentionDays(
134
136
  }
135
137
  }
136
138
 
139
+ /**
140
+ * Default PBKDF2 iteration count for freshly derived SCRAM verifiers —
141
+ * OWASP-aligned for PBKDF2-HMAC-SHA256, protecting human-chosen passwords
142
+ * against offline attack on a leaked verifier store. Costs roughly 100-150ms
143
+ * per derivation and per PLAIN/LOGIN authentication on the bounded async
144
+ * pool, still far inside the Rust bridge's 5s callback window. Callers whose
145
+ * passwords are machine-generated high-entropy secrets gain nothing from
146
+ * high counts and may pass a lower value within the accepted bounds.
147
+ */
148
+ export const SCRAM_DEFAULT_ITERATIONS = 600_000;
149
+
150
+ /**
151
+ * Derive an RFC 5802 SCRAM-SHA-256 verifier from a plaintext password with a
152
+ * fresh random 16-byte salt. This is the canonical embedder-facing derivation
153
+ * matching exactly what `UnifiedEmailServer` verifies at AUTH time — persist
154
+ * only the returned verifier, never the plaintext.
155
+ *
156
+ * SaltedPassword = PBKDF2-HMAC-SHA256(password, salt, iterations, 32);
157
+ * StoredKey = SHA256(HMAC-SHA256(SaltedPassword, 'Client Key'));
158
+ * ServerKey = HMAC-SHA256(SaltedPassword, 'Server Key').
159
+ */
160
+ export function deriveScramVerifier(
161
+ password: string,
162
+ iterations: number = SCRAM_DEFAULT_ITERATIONS,
163
+ ): ISmtpScramVerifier {
164
+ if (typeof password !== 'string' || password.length === 0) {
165
+ throw new Error('Cannot derive a SCRAM verifier from an empty password');
166
+ }
167
+ if (
168
+ !Number.isInteger(iterations)
169
+ || iterations < SCRAM_MIN_ITERATIONS
170
+ || iterations > SCRAM_MAX_ITERATIONS
171
+ ) {
172
+ throw new Error(
173
+ `SCRAM verifier iterations must be an integer between ${SCRAM_MIN_ITERATIONS} and ${SCRAM_MAX_ITERATIONS}`,
174
+ );
175
+ }
176
+ const salt = crypto.randomBytes(16);
177
+ const saltedPassword = crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
178
+ const clientKey = crypto.createHmac('sha256', saltedPassword).update('Client Key').digest();
179
+ const storedKey = crypto.createHash('sha256').update(clientKey).digest();
180
+ const serverKey = crypto.createHmac('sha256', saltedPassword).update('Server Key').digest();
181
+ return {
182
+ algorithm: 'SCRAM-SHA-256',
183
+ salt: salt.toString('base64'),
184
+ iterations,
185
+ storedKey: storedKey.toString('base64'),
186
+ serverKey: serverKey.toString('base64'),
187
+ };
188
+ }
189
+
137
190
  export function assertValidInboundPolicy(
138
191
  inboundPolicy: {
139
192
  defaultAction: unknown;
@@ -6,4 +6,5 @@ export * from './interfaces.js';
6
6
  export * from './classes.domain.registry.js';
7
7
  export * from './classes.email.action.executor.js';
8
8
  export * from './classes.dkim.manager.js';
9
+ export * from './functions.routing.validation.js';
9
10