@atlasauth/emulator-core 0.2.0 → 0.3.1
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/LICENSE +21 -0
- package/dist/custom-html.d.ts +57 -0
- package/dist/custom-html.js +491 -0
- package/dist/custom-html.js.map +1 -0
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +5 -1
- package/dist/engine.js.map +1 -1
- package/dist/error-catalog.js +8 -0
- package/dist/error-catalog.js.map +1 -1
- package/dist/event-gate.js +60 -2
- package/dist/event-gate.js.map +1 -1
- package/dist/generic.js +5 -1
- package/dist/generic.js.map +1 -1
- package/dist/handlers/access-signals.d.ts +48 -0
- package/dist/handlers/access-signals.js +76 -0
- package/dist/handlers/access-signals.js.map +1 -0
- package/dist/handlers/approvals.js +54 -1
- package/dist/handlers/approvals.js.map +1 -1
- package/dist/handlers/authz-data.d.ts +2 -0
- package/dist/handlers/authz-data.js +627 -0
- package/dist/handlers/authz-data.js.map +1 -0
- package/dist/handlers/authz-layers.d.ts +229 -0
- package/dist/handlers/authz-layers.js +786 -0
- package/dist/handlers/authz-layers.js.map +1 -0
- package/dist/handlers/authz-state.d.ts +80 -0
- package/dist/handlers/authz-state.js +175 -0
- package/dist/handlers/authz-state.js.map +1 -0
- package/dist/handlers/authz.js +240 -177
- package/dist/handlers/authz.js.map +1 -1
- package/dist/handlers/billing.d.ts +99 -1
- package/dist/handlers/billing.js +556 -77
- package/dist/handlers/billing.js.map +1 -1
- package/dist/handlers/cedar-ext.d.ts +35 -0
- package/dist/handlers/cedar-ext.js +174 -0
- package/dist/handlers/cedar-ext.js.map +1 -0
- package/dist/handlers/cedar.d.ts +146 -0
- package/dist/handlers/cedar.js +689 -0
- package/dist/handlers/cedar.js.map +1 -0
- package/dist/handlers/connections.d.ts +4 -1
- package/dist/handlers/connections.js +267 -38
- package/dist/handlers/connections.js.map +1 -1
- package/dist/handlers/control.js +6 -1
- package/dist/handlers/control.js.map +1 -1
- package/dist/handlers/device.js +18 -1
- package/dist/handlers/device.js.map +1 -1
- package/dist/handlers/emu3-misc.d.ts +38 -0
- package/dist/handlers/emu3-misc.js +1056 -0
- package/dist/handlers/emu3-misc.js.map +1 -0
- package/dist/handlers/emu4-actions-access.d.ts +46 -0
- package/dist/handlers/emu4-actions-access.js +107 -0
- package/dist/handlers/emu4-actions-access.js.map +1 -0
- package/dist/handlers/emu4-actions-catalog-extra.d.ts +3 -0
- package/dist/handlers/emu4-actions-catalog-extra.js +204 -0
- package/dist/handlers/emu4-actions-catalog-extra.js.map +1 -0
- package/dist/handlers/emu4-actions-catalog.d.ts +3 -0
- package/dist/handlers/emu4-actions-catalog.js +143 -0
- package/dist/handlers/emu4-actions-catalog.js.map +1 -0
- package/dist/handlers/emu4-actions-def.d.ts +50 -0
- package/dist/handlers/emu4-actions-def.js +250 -0
- package/dist/handlers/emu4-actions-def.js.map +1 -0
- package/dist/handlers/emu4-actions-sandbox.d.ts +36 -0
- package/dist/handlers/emu4-actions-sandbox.js +318 -0
- package/dist/handlers/emu4-actions-sandbox.js.map +1 -0
- package/dist/handlers/emu4-actions-typegen.d.ts +5 -0
- package/dist/handlers/emu4-actions-typegen.js +73 -0
- package/dist/handlers/emu4-actions-typegen.js.map +1 -0
- package/dist/handlers/emu4-actions.d.ts +2 -0
- package/dist/handlers/emu4-actions.js +506 -0
- package/dist/handlers/emu4-actions.js.map +1 -0
- package/dist/handlers/emu4-authz-trust.d.ts +40 -0
- package/dist/handlers/emu4-authz-trust.js +488 -0
- package/dist/handlers/emu4-authz-trust.js.map +1 -0
- package/dist/handlers/emu4-authz.d.ts +2 -0
- package/dist/handlers/emu4-authz.js +752 -0
- package/dist/handlers/emu4-authz.js.map +1 -0
- package/dist/handlers/emu4-billing-core.d.ts +23 -0
- package/dist/handlers/emu4-billing-core.js +22 -0
- package/dist/handlers/emu4-billing-core.js.map +1 -0
- package/dist/handlers/emu4-billing-x.d.ts +2 -0
- package/dist/handlers/emu4-billing-x.js +489 -0
- package/dist/handlers/emu4-billing-x.js.map +1 -0
- package/dist/handlers/emu4-billing.d.ts +14 -0
- package/dist/handlers/emu4-billing.js +846 -0
- package/dist/handlers/emu4-billing.js.map +1 -0
- package/dist/handlers/emu4-clones.d.ts +2 -0
- package/dist/handlers/emu4-clones.js +322 -0
- package/dist/handlers/emu4-clones.js.map +1 -0
- package/dist/handlers/emu4-compliance.d.ts +8 -0
- package/dist/handlers/emu4-compliance.js +39 -0
- package/dist/handlers/emu4-compliance.js.map +1 -0
- package/dist/handlers/emu4-config.d.ts +14 -0
- package/dist/handlers/emu4-config.js +148 -0
- package/dist/handlers/emu4-config.js.map +1 -0
- package/dist/handlers/emu4-cookies.d.ts +8 -0
- package/dist/handlers/emu4-cookies.js +248 -0
- package/dist/handlers/emu4-cookies.js.map +1 -0
- package/dist/handlers/emu4-erasure.d.ts +2 -0
- package/dist/handlers/emu4-erasure.js +587 -0
- package/dist/handlers/emu4-erasure.js.map +1 -0
- package/dist/handlers/emu4-federation.d.ts +8 -0
- package/dist/handlers/emu4-federation.js +18 -0
- package/dist/handlers/emu4-federation.js.map +1 -0
- package/dist/handlers/emu4-journey-math.d.ts +42 -0
- package/dist/handlers/emu4-journey-math.js +99 -0
- package/dist/handlers/emu4-journey-math.js.map +1 -0
- package/dist/handlers/emu4-messaging.d.ts +21 -0
- package/dist/handlers/emu4-messaging.js +595 -0
- package/dist/handlers/emu4-messaging.js.map +1 -0
- package/dist/handlers/emu4-minors.d.ts +7 -0
- package/dist/handlers/emu4-minors.js +408 -0
- package/dist/handlers/emu4-minors.js.map +1 -0
- package/dist/handlers/emu4-oauth-audit.d.ts +5 -0
- package/dist/handlers/emu4-oauth-audit.js +17 -0
- package/dist/handlers/emu4-oauth-audit.js.map +1 -0
- package/dist/handlers/emu4-oauth-auth.d.ts +83 -0
- package/dist/handlers/emu4-oauth-auth.js +363 -0
- package/dist/handlers/emu4-oauth-auth.js.map +1 -0
- package/dist/handlers/emu4-oauth-delivery.d.ts +68 -0
- package/dist/handlers/emu4-oauth-delivery.js +207 -0
- package/dist/handlers/emu4-oauth-delivery.js.map +1 -0
- package/dist/handlers/emu4-oauth-keys.d.ts +11 -0
- package/dist/handlers/emu4-oauth-keys.js +268 -0
- package/dist/handlers/emu4-oauth-keys.js.map +1 -0
- package/dist/handlers/emu4-oauth-pub.d.ts +36 -0
- package/dist/handlers/emu4-oauth-pub.js +420 -0
- package/dist/handlers/emu4-oauth-pub.js.map +1 -0
- package/dist/handlers/emu4-oauth.d.ts +10 -0
- package/dist/handlers/emu4-oauth.js +23 -0
- package/dist/handlers/emu4-oauth.js.map +1 -0
- package/dist/handlers/emu4-policies.d.ts +2 -0
- package/dist/handlers/emu4-policies.js +408 -0
- package/dist/handlers/emu4-policies.js.map +1 -0
- package/dist/handlers/emu4-region.d.ts +2 -0
- package/dist/handlers/emu4-region.js +189 -0
- package/dist/handlers/emu4-region.js.map +1 -0
- package/dist/handlers/emu4-saml.d.ts +12 -0
- package/dist/handlers/emu4-saml.js +532 -0
- package/dist/handlers/emu4-saml.js.map +1 -0
- package/dist/handlers/emu4-shared.d.ts +44 -0
- package/dist/handlers/emu4-shared.js +90 -0
- package/dist/handlers/emu4-shared.js.map +1 -0
- package/dist/handlers/emu4-snapshots.d.ts +38 -0
- package/dist/handlers/emu4-snapshots.js +231 -0
- package/dist/handlers/emu4-snapshots.js.map +1 -0
- package/dist/handlers/emu4-sns.d.ts +9 -0
- package/dist/handlers/emu4-sns.js +21 -0
- package/dist/handlers/emu4-sns.js.map +1 -0
- package/dist/handlers/emu4-trust-binding.d.ts +27 -0
- package/dist/handlers/emu4-trust-binding.js +344 -0
- package/dist/handlers/emu4-trust-binding.js.map +1 -0
- package/dist/handlers/emu4-trust.d.ts +35 -0
- package/dist/handlers/emu4-trust.js +543 -0
- package/dist/handlers/emu4-trust.js.map +1 -0
- package/dist/handlers/fga-engine.d.ts +78 -0
- package/dist/handlers/fga-engine.js +663 -0
- package/dist/handlers/fga-engine.js.map +1 -0
- package/dist/handlers/governance.js +3 -2
- package/dist/handlers/governance.js.map +1 -1
- package/dist/handlers/hooks.js +13 -0
- package/dist/handlers/hooks.js.map +1 -1
- package/dist/handlers/index.js +14 -1
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/machines.js +6 -0
- package/dist/handlers/machines.js.map +1 -1
- package/dist/handlers/messaging.d.ts +3 -1
- package/dist/handlers/messaging.js +455 -48
- package/dist/handlers/messaging.js.map +1 -1
- package/dist/handlers/oauth-adv.d.ts +191 -0
- package/dist/handlers/oauth-adv.js +1305 -0
- package/dist/handlers/oauth-adv.js.map +1 -0
- package/dist/handlers/oauth-flows.d.ts +25 -1
- package/dist/handlers/oauth-flows.js +111 -50
- package/dist/handlers/oauth-flows.js.map +1 -1
- package/dist/handlers/oauth.d.ts +24 -3
- package/dist/handlers/oauth.js +245 -60
- package/dist/handlers/oauth.js.map +1 -1
- package/dist/handlers/orgs.js +6 -0
- package/dist/handlers/orgs.js.map +1 -1
- package/dist/handlers/platform.d.ts +5 -0
- package/dist/handlers/platform.js +33 -15
- package/dist/handlers/platform.js.map +1 -1
- package/dist/handlers/scim.d.ts +2 -0
- package/dist/handlers/scim.js +198 -11
- package/dist/handlers/scim.js.map +1 -1
- package/dist/handlers/sessions.d.ts +2 -0
- package/dist/handlers/sessions.js +24 -16
- package/dist/handlers/sessions.js.map +1 -1
- package/dist/handlers/users.js +3 -0
- package/dist/handlers/users.js.map +1 -1
- package/dist/jwe.d.ts +18 -0
- package/dist/jwe.js +101 -0
- package/dist/jwe.js.map +1 -0
- package/dist/jws.js +2 -0
- package/dist/jws.js.map +1 -1
- package/dist/messaging-callbacks.d.ts +100 -0
- package/dist/messaging-callbacks.js +767 -0
- package/dist/messaging-callbacks.js.map +1 -0
- package/dist/openapi.d.ts +1 -0
- package/dist/openapi.js +44 -2
- package/dist/openapi.js.map +1 -1
- package/dist/parity-emu3-authz.d.ts +3 -0
- package/dist/parity-emu3-authz.js +61 -0
- package/dist/parity-emu3-authz.js.map +1 -0
- package/dist/parity-emu3-billing.d.ts +3 -0
- package/dist/parity-emu3-billing.js +25 -0
- package/dist/parity-emu3-billing.js.map +1 -0
- package/dist/parity-emu3-messaging.d.ts +3 -0
- package/dist/parity-emu3-messaging.js +27 -0
- package/dist/parity-emu3-messaging.js.map +1 -0
- package/dist/parity-emu3-misc.d.ts +2 -0
- package/dist/parity-emu3-misc.js +63 -0
- package/dist/parity-emu3-misc.js.map +1 -0
- package/dist/parity-emu3-oauth.d.ts +2 -0
- package/dist/parity-emu3-oauth.js +39 -0
- package/dist/parity-emu3-oauth.js.map +1 -0
- package/dist/parity-emu4-actions.d.ts +2 -0
- package/dist/parity-emu4-actions.js +38 -0
- package/dist/parity-emu4-actions.js.map +1 -0
- package/dist/parity-emu4-authz.d.ts +3 -0
- package/dist/parity-emu4-authz.js +68 -0
- package/dist/parity-emu4-authz.js.map +1 -0
- package/dist/parity-emu4-billing.d.ts +6 -0
- package/dist/parity-emu4-billing.js +44 -0
- package/dist/parity-emu4-billing.js.map +1 -0
- package/dist/parity-emu4-compliance.d.ts +2 -0
- package/dist/parity-emu4-compliance.js +92 -0
- package/dist/parity-emu4-compliance.js.map +1 -0
- package/dist/parity-emu4-federation.d.ts +2 -0
- package/dist/parity-emu4-federation.js +33 -0
- package/dist/parity-emu4-federation.js.map +1 -0
- package/dist/parity-emu4-messaging.d.ts +3 -0
- package/dist/parity-emu4-messaging.js +42 -0
- package/dist/parity-emu4-messaging.js.map +1 -0
- package/dist/parity-emu4-oauth.d.ts +2 -0
- package/dist/parity-emu4-oauth.js +55 -0
- package/dist/parity-emu4-oauth.js.map +1 -0
- package/dist/parity-emu4-trust.d.ts +2 -0
- package/dist/parity-emu4-trust.js +41 -0
- package/dist/parity-emu4-trust.js.map +1 -0
- package/dist/parity-routes.js +19 -1
- package/dist/parity-routes.js.map +1 -1
- package/dist/sns-cert.d.ts +9 -0
- package/dist/sns-cert.js +101 -0
- package/dist/sns-cert.js.map +1 -0
- package/dist/tokens.d.ts +69 -4
- package/dist/tokens.js +164 -38
- package/dist/tokens.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SNS_CERT_URL = exports.receiverToken = exports.sha256Hex = void 0;
|
|
4
|
+
exports.testKeys = testKeys;
|
|
5
|
+
exports.testSecret = testSecret;
|
|
6
|
+
exports.idVariants = idVariants;
|
|
7
|
+
exports.verifySvix = verifySvix;
|
|
8
|
+
exports.snsStringToSign = snsStringToSign;
|
|
9
|
+
exports.presentedTokens = presentedTokens;
|
|
10
|
+
exports.verifyCallback = verifyCallback;
|
|
11
|
+
exports.prepareCallback = prepareCallback;
|
|
12
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
13
|
+
/**
|
|
14
|
+
* Provider delivery callbacks: how each provider signs them, how the emulator verifies them, what they say, and the
|
|
15
|
+
* helper that builds a correctly signed (or deliberately tampered) callback for a test. Pure functions on Web Crypto
|
|
16
|
+
* (no node: imports, so the engine still runs in a browser worker). The route glue and the ledger live in
|
|
17
|
+
* handlers/messaging.ts.
|
|
18
|
+
*
|
|
19
|
+
* resend Svix signature (HMAC-SHA256 over id.timestamp.body) with a whsec_ secret, 5 minute tolerance
|
|
20
|
+
* mailgun HMAC-SHA256 of timestamp+token with the HTTP webhook signing key
|
|
21
|
+
* ses Amazon SNS message signature + the instance URL token; the signature is checked against the X.509 certificate
|
|
22
|
+
* SigningCertURL names (an sns.<region>.amazonaws.com HTTPS URL), served by the emulator (see sns-cert.ts)
|
|
23
|
+
* sendgrid ECDSA P-256 over timestamp+body with the account's public key, else the URL token
|
|
24
|
+
* twilio X-Twilio-Signature: base64 HMAC-SHA1 over the URL and the sorted form fields, with the SMS provider's auth token
|
|
25
|
+
* vonage `sig` over the sorted parameters (md5hash, md5, sha1, sha256, sha512), else the URL token
|
|
26
|
+
* messagebird MessageBird-Signature: base64 HMAC-SHA256 over timestamp, sorted query and sha256(body), else the URL token
|
|
27
|
+
* brevo, smtp2go, postmark a token derived for this instance (bearer, basic-auth password or ?t=)
|
|
28
|
+
*/
|
|
29
|
+
const sns_cert_1 = require("./sns-cert");
|
|
30
|
+
const enc = new TextEncoder();
|
|
31
|
+
const FIVE_MINUTES = 5 * 60;
|
|
32
|
+
// ── bytes and hashes ──
|
|
33
|
+
const toB64 = (b) => {
|
|
34
|
+
let s = '';
|
|
35
|
+
for (const x of b)
|
|
36
|
+
s += String.fromCharCode(x);
|
|
37
|
+
return btoa(s);
|
|
38
|
+
};
|
|
39
|
+
const fromB64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
40
|
+
const hex = (b) => [...b].map((x) => x.toString(16).padStart(2, '0')).join('');
|
|
41
|
+
const cat = (...parts) => {
|
|
42
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
43
|
+
let o = 0;
|
|
44
|
+
for (const p of parts) {
|
|
45
|
+
out.set(p, o);
|
|
46
|
+
o += p.length;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
const bytes = (v) => (typeof v === 'string' ? enc.encode(v) : v);
|
|
51
|
+
const subtle = () => crypto.subtle;
|
|
52
|
+
const sha256Hex = async (v) => hex(new Uint8Array(await subtle().digest('SHA-256', bytes(v))));
|
|
53
|
+
exports.sha256Hex = sha256Hex;
|
|
54
|
+
/** Constant-time string comparison. */
|
|
55
|
+
const eq = (a, b) => {
|
|
56
|
+
if (a.length !== b.length)
|
|
57
|
+
return false;
|
|
58
|
+
let d = 0;
|
|
59
|
+
for (let i = 0; i < a.length; i++)
|
|
60
|
+
d |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
61
|
+
return d === 0;
|
|
62
|
+
};
|
|
63
|
+
/** MD5 (Web Crypto has none; Vonage's `md5` and `md5hash` signatures need it). */
|
|
64
|
+
function md5(input) {
|
|
65
|
+
const K = Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32) >>> 0);
|
|
66
|
+
const S = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21];
|
|
67
|
+
const total = (((input.length + 8) >>> 6) + 1) * 64;
|
|
68
|
+
const padded = new Uint8Array(total);
|
|
69
|
+
padded.set(input);
|
|
70
|
+
padded[input.length] = 0x80;
|
|
71
|
+
const view = new DataView(padded.buffer);
|
|
72
|
+
view.setUint32(total - 8, (input.length * 8) >>> 0, true);
|
|
73
|
+
view.setUint32(total - 4, Math.floor(input.length / 2 ** 29), true);
|
|
74
|
+
let [a0, b0, c0, d0] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476];
|
|
75
|
+
for (let off = 0; off < total; off += 64) {
|
|
76
|
+
const M = Array.from({ length: 16 }, (_, i) => view.getUint32(off + i * 4, true));
|
|
77
|
+
let [A, B, C, D] = [a0, b0, c0, d0];
|
|
78
|
+
for (let i = 0; i < 64; i++) {
|
|
79
|
+
let F;
|
|
80
|
+
let g;
|
|
81
|
+
if (i < 16)
|
|
82
|
+
((F = (B & C) | (~B & D)), (g = i));
|
|
83
|
+
else if (i < 32)
|
|
84
|
+
((F = (D & B) | (~D & C)), (g = (5 * i + 1) % 16));
|
|
85
|
+
else if (i < 48)
|
|
86
|
+
((F = B ^ C ^ D), (g = (3 * i + 5) % 16));
|
|
87
|
+
else
|
|
88
|
+
((F = C ^ (B | ~D)), (g = (7 * i) % 16));
|
|
89
|
+
F = (F + A + K[i] + M[g]) >>> 0;
|
|
90
|
+
A = D;
|
|
91
|
+
D = C;
|
|
92
|
+
C = B;
|
|
93
|
+
const s = S[(i >> 4) * 4 + (i % 4)];
|
|
94
|
+
B = (B + ((F << s) | (F >>> (32 - s)))) >>> 0;
|
|
95
|
+
}
|
|
96
|
+
a0 = (a0 + A) >>> 0;
|
|
97
|
+
b0 = (b0 + B) >>> 0;
|
|
98
|
+
c0 = (c0 + C) >>> 0;
|
|
99
|
+
d0 = (d0 + D) >>> 0;
|
|
100
|
+
}
|
|
101
|
+
const out = new Uint8Array(16);
|
|
102
|
+
const ov = new DataView(out.buffer);
|
|
103
|
+
[a0, b0, c0, d0].forEach((w, i) => ov.setUint32(i * 4, w, true));
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
const hmacMd5 = (key, data) => {
|
|
107
|
+
const k = key.length > 64 ? md5(key) : key;
|
|
108
|
+
const ip = new Uint8Array(64 + data.length);
|
|
109
|
+
const op = new Uint8Array(80);
|
|
110
|
+
for (let i = 0; i < 64; i++) {
|
|
111
|
+
ip[i] = (k[i] ?? 0) ^ 0x36;
|
|
112
|
+
op[i] = (k[i] ?? 0) ^ 0x5c;
|
|
113
|
+
}
|
|
114
|
+
ip.set(data, 64);
|
|
115
|
+
op.set(md5(ip), 64);
|
|
116
|
+
return md5(op);
|
|
117
|
+
};
|
|
118
|
+
const SUBTLE_HASH = { sha1: 'SHA-1', sha256: 'SHA-256', sha512: 'SHA-512' };
|
|
119
|
+
async function hmac(alg, key, data) {
|
|
120
|
+
if (alg === 'md5')
|
|
121
|
+
return hmacMd5(bytes(key), bytes(data));
|
|
122
|
+
const k = await subtle().importKey('raw', bytes(key), { name: 'HMAC', hash: SUBTLE_HASH[alg] }, false, ['sign']);
|
|
123
|
+
return new Uint8Array(await subtle().sign('HMAC', k, bytes(data)));
|
|
124
|
+
}
|
|
125
|
+
// ── ECDSA signature encodings (SendGrid sends ASN.1 DER, Web Crypto wants r||s) ──
|
|
126
|
+
function derToRaw(der) {
|
|
127
|
+
let p = der[1] & 0x80 ? 2 + (der[1] & 0x7f) : 2;
|
|
128
|
+
const ints = [];
|
|
129
|
+
for (let n = 0; n < 2; n++) {
|
|
130
|
+
if (der[p] !== 2)
|
|
131
|
+
throw new Error('bad DER');
|
|
132
|
+
const len = der[p + 1];
|
|
133
|
+
let v = der.slice(p + 2, p + 2 + len);
|
|
134
|
+
while (v.length > 32 && v[0] === 0)
|
|
135
|
+
v = v.slice(1);
|
|
136
|
+
if (v.length > 32)
|
|
137
|
+
throw new Error('bad DER');
|
|
138
|
+
ints.push(cat(new Uint8Array(32 - v.length), v));
|
|
139
|
+
p += 2 + len;
|
|
140
|
+
}
|
|
141
|
+
return cat(ints[0], ints[1]);
|
|
142
|
+
}
|
|
143
|
+
function rawToDer(raw) {
|
|
144
|
+
const int = (x) => {
|
|
145
|
+
let i = 0;
|
|
146
|
+
while (i < 31 && x[i] === 0)
|
|
147
|
+
i++;
|
|
148
|
+
let v = x.slice(i);
|
|
149
|
+
if (v[0] & 0x80)
|
|
150
|
+
v = cat([0], v);
|
|
151
|
+
return cat([2, v.length], v);
|
|
152
|
+
};
|
|
153
|
+
const body = cat(int(raw.slice(0, 32)), int(raw.slice(32)));
|
|
154
|
+
return cat([0x30, body.length], body);
|
|
155
|
+
}
|
|
156
|
+
function testKeys() {
|
|
157
|
+
let ec;
|
|
158
|
+
let rsa;
|
|
159
|
+
return {
|
|
160
|
+
ec: () => (ec ??= (async () => {
|
|
161
|
+
const p = (await subtle().generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']));
|
|
162
|
+
return { publicB64: toB64(new Uint8Array(await subtle().exportKey('spki', p.publicKey))), privateKey: p.privateKey };
|
|
163
|
+
})()),
|
|
164
|
+
rsa: () => (rsa ??= (async () => {
|
|
165
|
+
const p = (await subtle().generateKey({ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' }, true, ['sign', 'verify']));
|
|
166
|
+
const spki = new Uint8Array(await subtle().exportKey('spki', p.publicKey));
|
|
167
|
+
const pkcs8 = new Uint8Array(await subtle().exportKey('pkcs8', p.privateKey));
|
|
168
|
+
return { publicB64: toB64(spki), pkcs8, certPem: await (0, sns_cert_1.selfSignedCertificate)(spki, pkcs8) };
|
|
169
|
+
})()),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** The test signing secret the emulator uses for a provider when none was stored (stable per instance). */
|
|
173
|
+
async function testSecret(instanceId, provider) {
|
|
174
|
+
const h = await (0, exports.sha256Hex)(`atlas-emulator:${instanceId}:${provider}`);
|
|
175
|
+
if (provider === 'resend')
|
|
176
|
+
return `whsec_${toB64(fromHex(h).slice(0, 24))}`;
|
|
177
|
+
if (provider === 'vonage')
|
|
178
|
+
return `sha256:${h.slice(0, 32)}`;
|
|
179
|
+
return h.slice(0, 32);
|
|
180
|
+
}
|
|
181
|
+
const fromHex = (h) => Uint8Array.from(h.match(/../g) ?? [], (x) => parseInt(x, 16));
|
|
182
|
+
/** The URL token of the providers that cannot sign (and the fallback when no signing secret is stored). */
|
|
183
|
+
const receiverToken = async (instanceId, provider) => toB64(fromHex(await (0, exports.sha256Hex)(`atlas:message-receiver:${provider}:v1:${instanceId}`))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
184
|
+
exports.receiverToken = receiverToken;
|
|
185
|
+
// ── parsing helpers ──
|
|
186
|
+
const str = (v) => (typeof v === 'string' && v ? v : undefined);
|
|
187
|
+
const obj = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {});
|
|
188
|
+
let NOW = 0;
|
|
189
|
+
function when(v, fallback = NOW) {
|
|
190
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
191
|
+
return v > 1e12 ? v : v * 1000;
|
|
192
|
+
if (typeof v === 'string' && v) {
|
|
193
|
+
const n = Number(v);
|
|
194
|
+
const t = Number.isFinite(n) && /^\d+(\.\d+)?$/.test(v) ? (n > 1e12 ? n : n * 1000) : Date.parse(v);
|
|
195
|
+
if (!Number.isNaN(t))
|
|
196
|
+
return t;
|
|
197
|
+
}
|
|
198
|
+
return fallback;
|
|
199
|
+
}
|
|
200
|
+
function idVariants(id) {
|
|
201
|
+
const s = typeof id === 'string' ? id.trim() : '';
|
|
202
|
+
if (!s)
|
|
203
|
+
return [];
|
|
204
|
+
const bare = s.replace(/^<|>$/g, '');
|
|
205
|
+
return [...new Set([s, bare, `<${bare}>`])];
|
|
206
|
+
}
|
|
207
|
+
const stringify = (v) => Object.fromEntries(Object.entries(obj(v)).filter(([, x]) => typeof x === 'string' || typeof x === 'number').map(([k, x]) => [k, String(x)]));
|
|
208
|
+
const form = (raw) => Object.fromEntries(new URLSearchParams(raw));
|
|
209
|
+
// ── verification primitives ──
|
|
210
|
+
async function verifySvix(i) {
|
|
211
|
+
const ts = Number(i.timestamp);
|
|
212
|
+
if (!Number.isFinite(ts) || Math.abs(i.now - ts) > FIVE_MINUTES)
|
|
213
|
+
return false;
|
|
214
|
+
try {
|
|
215
|
+
const expected = toB64(await hmac('sha256', fromB64(i.secret.replace(/^whsec_/, '')), `${i.id}.${i.timestamp}.${i.body}`));
|
|
216
|
+
return i.signatureHeader.split(' ').some((part) => {
|
|
217
|
+
const [v, sig] = part.split(',');
|
|
218
|
+
return v === 'v1' && !!sig && eq(sig, expected);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const mailgunSig = async (key, ts, token) => hex(await hmac('sha256', key, ts + token));
|
|
226
|
+
async function verifyMailgun(key, sig, now) {
|
|
227
|
+
const timestamp = str(sig.timestamp) ?? (typeof sig.timestamp === 'number' ? String(sig.timestamp) : undefined);
|
|
228
|
+
const token = str(sig.token);
|
|
229
|
+
const signature = str(sig.signature);
|
|
230
|
+
if (!timestamp || !token || !signature)
|
|
231
|
+
return false;
|
|
232
|
+
if (Math.abs(now - Number(timestamp)) > FIVE_MINUTES)
|
|
233
|
+
return false;
|
|
234
|
+
return eq(await mailgunSig(key, timestamp, token), signature.toLowerCase());
|
|
235
|
+
}
|
|
236
|
+
async function verifySendgrid(publicKey, signature, timestamp, body, now) {
|
|
237
|
+
if (!Number.isFinite(Number(timestamp)) || Math.abs(now - Number(timestamp)) > FIVE_MINUTES)
|
|
238
|
+
return false;
|
|
239
|
+
try {
|
|
240
|
+
const key = await subtle().importKey('spki', fromB64(publicKey.replace(/\s+/g, '')), { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
241
|
+
return await subtle().verify({ name: 'ECDSA', hash: 'SHA-256' }, key, derToRaw(fromB64(signature)), enc.encode(timestamp + body));
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const twilioSig = async (token, url, params) => toB64(await hmac('sha1', token, url + Object.keys(params).sort().map((k) => k + params[k]).join('')));
|
|
248
|
+
const vonageBase = (params) => Object.keys(params).filter((k) => k !== 'sig').sort().map((k) => `&${k}=${String(params[k]).replace(/[&=]/g, '_')}`).join('');
|
|
249
|
+
async function vonageSig(spec, params) {
|
|
250
|
+
const m = /^(md5hash|md5|sha1|sha256|sha512):(.*)$/s.exec(spec);
|
|
251
|
+
const algorithm = m?.[1] ?? 'sha256';
|
|
252
|
+
const secret = m ? m[2] : spec;
|
|
253
|
+
const base = vonageBase(params);
|
|
254
|
+
return hex(algorithm === 'md5hash' ? md5(enc.encode(base + secret)) : await hmac(algorithm, secret, base));
|
|
255
|
+
}
|
|
256
|
+
async function verifyVonage(spec, params, now) {
|
|
257
|
+
if (!params.sig)
|
|
258
|
+
return false;
|
|
259
|
+
if (params.timestamp && Math.abs(now - Number(params.timestamp)) > FIVE_MINUTES)
|
|
260
|
+
return false;
|
|
261
|
+
return eq((await vonageSig(spec, params)).toLowerCase(), params.sig.toLowerCase());
|
|
262
|
+
}
|
|
263
|
+
const messageBirdSig = async (key, ts, query, body) => {
|
|
264
|
+
const qs = Object.keys(query).sort().map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`).join('&');
|
|
265
|
+
return toB64(await hmac('sha256', key, `${ts}\n${qs}\n${await (0, exports.sha256Hex)(body)}`));
|
|
266
|
+
};
|
|
267
|
+
async function verifyMessageBird(key, signature, timestamp, query, body, now) {
|
|
268
|
+
if (!Number.isFinite(Number(timestamp)) || Math.abs(now - Number(timestamp)) > FIVE_MINUTES)
|
|
269
|
+
return false;
|
|
270
|
+
return eq(await messageBirdSig(key, timestamp, query, body), signature);
|
|
271
|
+
}
|
|
272
|
+
// ── Amazon SNS ──
|
|
273
|
+
const SNS_HOST = /^sns\.[a-z0-9-]+\.amazonaws\.com(\.cn)?$/;
|
|
274
|
+
function snsUrlAllowed(url) {
|
|
275
|
+
if (typeof url !== 'string')
|
|
276
|
+
return false;
|
|
277
|
+
try {
|
|
278
|
+
const u = new URL(url);
|
|
279
|
+
return u.protocol === 'https:' && SNS_HOST.test(u.hostname) && !u.username && !u.password && (!u.port || u.port === '443');
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function snsStringToSign(m) {
|
|
286
|
+
const fields = m.Type === 'Notification' ? ['Message', 'MessageId', ...(m.Subject !== undefined ? ['Subject'] : []), 'Timestamp', 'TopicArn', 'Type'] : m.Type === 'SubscriptionConfirmation' || m.Type === 'UnsubscribeConfirmation' ? ['Message', 'MessageId', 'SubscribeURL', 'Timestamp', 'Token', 'TopicArn', 'Type'] : null;
|
|
287
|
+
if (!fields)
|
|
288
|
+
return null;
|
|
289
|
+
let out = '';
|
|
290
|
+
for (const f of fields) {
|
|
291
|
+
if (typeof m[f] !== 'string')
|
|
292
|
+
return null;
|
|
293
|
+
out += `${f}\n${m[f]}\n`;
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
/** Where the emulator's SNS signing key is "served"; any other SNS host fails like an unreachable certificate. */
|
|
298
|
+
exports.SNS_CERT_URL = 'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-emulator.pem';
|
|
299
|
+
/** Same rules as the real receiver: the URL must be an SNS host over HTTPS, the certificate is fetched from it (a URL that serves none fails) and the signature is checked against its public key. */
|
|
300
|
+
async function verifySns(m, fetchCertificate) {
|
|
301
|
+
const toSign = snsStringToSign(m);
|
|
302
|
+
if (!toSign || typeof m.Signature !== 'string' || !snsUrlAllowed(m.SigningCertURL))
|
|
303
|
+
return false;
|
|
304
|
+
if (m.SignatureVersion !== '1' && m.SignatureVersion !== '2')
|
|
305
|
+
return false;
|
|
306
|
+
try {
|
|
307
|
+
const pem = await fetchCertificate(m.SigningCertURL);
|
|
308
|
+
const spki = pem ? (0, sns_cert_1.certificatePublicKey)(pem) : null;
|
|
309
|
+
if (!spki)
|
|
310
|
+
return false;
|
|
311
|
+
const key = await subtle().importKey('spki', spki, { name: 'RSASSA-PKCS1-v1_5', hash: m.SignatureVersion === '2' ? 'SHA-256' : 'SHA-1' }, false, ['verify']);
|
|
312
|
+
return await subtle().verify('RSASSA-PKCS1-v1_5', key, fromB64(m.Signature), enc.encode(toSign));
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function signSns(m, pkcs8, salt) {
|
|
319
|
+
const key = await subtle().importKey('pkcs8', pkcs8, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
|
|
320
|
+
return toB64(new Uint8Array(await subtle().sign('RSASSA-PKCS1-v1_5', key, enc.encode(snsStringToSign(m) + salt))));
|
|
321
|
+
}
|
|
322
|
+
// ── tokens ──
|
|
323
|
+
function presentedTokens(headers, query) {
|
|
324
|
+
const out = [];
|
|
325
|
+
const h = headers.get('authorization') ?? '';
|
|
326
|
+
if (h.startsWith('Bearer '))
|
|
327
|
+
out.push(h.slice(7).trim());
|
|
328
|
+
if (h.startsWith('Basic ')) {
|
|
329
|
+
try {
|
|
330
|
+
const d = atob(h.slice(6));
|
|
331
|
+
const i = d.indexOf(':');
|
|
332
|
+
out.push(i >= 0 ? d.slice(i + 1) : d);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
/* not base64: no credential */
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (const k of ['t', 'token'])
|
|
339
|
+
if (query[k])
|
|
340
|
+
out.push(query[k]);
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
// ── parsers: what each provider's callback says ──
|
|
344
|
+
const evBase = (provider, channel, ids, at, recipient) => ({ channel, provider, messageIds: idVariants(ids), at, ...(recipient ? { recipient } : {}) });
|
|
345
|
+
function parseSes(message) {
|
|
346
|
+
const m = obj(message);
|
|
347
|
+
const kind = str(m.notificationType) ?? str(m.eventType);
|
|
348
|
+
const mail = obj(m.mail);
|
|
349
|
+
const base = evBase('ses', 'email', mail.messageId, when(obj(m.bounce).timestamp ?? obj(m.complaint).timestamp ?? obj(m.delivery).timestamp ?? mail.timestamp));
|
|
350
|
+
const list = (v, key) => (Array.isArray(v) ? v : []).map((r) => str(obj(r)[key])).filter((x) => !!x);
|
|
351
|
+
if (kind === 'Bounce') {
|
|
352
|
+
const b = obj(m.bounce);
|
|
353
|
+
const permanent = str(b.bounceType) === 'Permanent';
|
|
354
|
+
const type = permanent ? 'hard' : str(b.bounceType) === 'Transient' ? 'soft' : 'unknown';
|
|
355
|
+
const detail = str(b.bounceSubType) ?? str(b.bounceType);
|
|
356
|
+
const rs = list(b.bouncedRecipients, 'emailAddress');
|
|
357
|
+
return (rs.length ? rs : [undefined]).map((recipient) => ({ ...base, status: 'bounced', bounceType: type, ...(recipient ? { recipient } : {}), ...(detail ? { detail } : {}), ...(permanent && recipient ? { suppress: 'hard_bounce' } : {}) }));
|
|
358
|
+
}
|
|
359
|
+
if (kind === 'Complaint') {
|
|
360
|
+
const c = obj(m.complaint);
|
|
361
|
+
const rs = list(c.complainedRecipients, 'emailAddress');
|
|
362
|
+
return (rs.length ? rs : [undefined]).map((recipient) => ({ ...base, status: 'complained', ...(recipient ? { recipient, suppress: 'complaint' } : {}), ...(str(c.complaintFeedbackType) ? { detail: str(c.complaintFeedbackType) } : {}) }));
|
|
363
|
+
}
|
|
364
|
+
if (kind === 'Delivery') {
|
|
365
|
+
const rs = (Array.isArray(obj(m.delivery).recipients) ? obj(m.delivery).recipients : []).filter((x) => typeof x === 'string');
|
|
366
|
+
return (rs.length ? rs : [undefined]).map((recipient) => ({ ...base, status: 'delivered', ...(recipient ? { recipient } : {}) }));
|
|
367
|
+
}
|
|
368
|
+
if (kind === 'Send')
|
|
369
|
+
return [{ ...base, status: 'sent' }];
|
|
370
|
+
if (kind === 'Reject' || kind === 'Rendering Failure')
|
|
371
|
+
return [{ ...base, status: 'failed', detail: str(obj(m.reject).reason) ?? kind }];
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
function parseBrevo(body) {
|
|
375
|
+
const out = [];
|
|
376
|
+
for (const raw of Array.isArray(body) ? body : [body]) {
|
|
377
|
+
const e = obj(raw);
|
|
378
|
+
const name = str(e.event);
|
|
379
|
+
if (!name)
|
|
380
|
+
continue;
|
|
381
|
+
const base = evBase('brevo', 'email', e['message-id'] ?? e.messageId, when(e.ts_event ?? e.ts ?? e.date), str(e.email));
|
|
382
|
+
const reason = str(e.reason);
|
|
383
|
+
const d = reason ? { detail: reason } : {};
|
|
384
|
+
if (name === 'request' || name === 'sent')
|
|
385
|
+
out.push({ ...base, status: 'sent' });
|
|
386
|
+
else if (name === 'delivered')
|
|
387
|
+
out.push({ ...base, status: 'delivered' });
|
|
388
|
+
else if (['hard_bounce', 'hardBounce', 'blocked', 'invalid_email', 'invalid'].includes(name))
|
|
389
|
+
out.push({ ...base, status: 'bounced', bounceType: 'hard', ...d, ...(base.recipient ? { suppress: name.startsWith('invalid') ? 'invalid' : 'hard_bounce' } : {}) });
|
|
390
|
+
else if (['soft_bounce', 'softBounce', 'deferred'].includes(name))
|
|
391
|
+
out.push({ ...base, status: 'bounced', bounceType: 'soft', ...d });
|
|
392
|
+
else if (name === 'spam' || name === 'complaint')
|
|
393
|
+
out.push({ ...base, status: 'complained', ...(base.recipient ? { suppress: 'complaint' } : {}) });
|
|
394
|
+
else if ((name === 'unsubscribe' || name === 'unsubscribed') && base.recipient)
|
|
395
|
+
out.push({ ...base, status: 'complained', detail: 'unsubscribe', suppress: 'unsubscribe' });
|
|
396
|
+
else if (name === 'error')
|
|
397
|
+
out.push({ ...base, status: 'failed', ...d });
|
|
398
|
+
}
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function parseMailgun(body) {
|
|
402
|
+
const data = obj(obj(body)['event-data']);
|
|
403
|
+
const event = str(data.event);
|
|
404
|
+
if (!event)
|
|
405
|
+
return [];
|
|
406
|
+
const base = evBase('mailgun', 'email', obj(obj(data.message).headers)['message-id'] ?? data.id, when(data.timestamp), str(data.recipient));
|
|
407
|
+
const ds = obj(data['delivery-status']);
|
|
408
|
+
const detail = str(ds.description) ?? str(ds.message) ?? str(data.reason);
|
|
409
|
+
if (event === 'accepted')
|
|
410
|
+
return [{ ...base, status: 'sent' }];
|
|
411
|
+
if (event === 'delivered')
|
|
412
|
+
return [{ ...base, status: 'delivered' }];
|
|
413
|
+
if (event === 'failed') {
|
|
414
|
+
const permanent = str(data.severity) === 'permanent';
|
|
415
|
+
return [{ ...base, status: 'bounced', bounceType: permanent ? 'hard' : 'soft', ...(detail ? { detail } : {}), ...(permanent && base.recipient ? { suppress: 'hard_bounce' } : {}) }];
|
|
416
|
+
}
|
|
417
|
+
if (event === 'complained')
|
|
418
|
+
return [{ ...base, status: 'complained', ...(base.recipient ? { suppress: 'complaint' } : {}) }];
|
|
419
|
+
if (event === 'unsubscribed')
|
|
420
|
+
return base.recipient ? [{ ...base, status: 'complained', detail: 'unsubscribe', suppress: 'unsubscribe' }] : [];
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
function parseSmtp2go(body) {
|
|
424
|
+
const out = [];
|
|
425
|
+
for (const raw of Array.isArray(body) ? body : [body]) {
|
|
426
|
+
const e = obj(raw);
|
|
427
|
+
const event = str(e.event);
|
|
428
|
+
if (!event)
|
|
429
|
+
continue;
|
|
430
|
+
const rcpt = str(e.rcpt) ?? str(e.email);
|
|
431
|
+
const base = evBase('smtp2go', 'email', e.email_id, when(e.sendtime ?? e.time), rcpt);
|
|
432
|
+
const reason = str(e.message) ?? str(e.reason);
|
|
433
|
+
if (event === 'processed')
|
|
434
|
+
out.push({ ...base, status: 'sent' });
|
|
435
|
+
else if (event === 'delivered')
|
|
436
|
+
out.push({ ...base, status: 'delivered' });
|
|
437
|
+
else if (event === 'bounce' || event === 'reject') {
|
|
438
|
+
const hard = event === 'reject' || str(e.bounce) === 'hard';
|
|
439
|
+
out.push({ ...base, status: 'bounced', bounceType: hard ? 'hard' : 'soft', ...(reason ? { detail: reason } : {}), ...(hard && rcpt ? { suppress: 'hard_bounce' } : {}) });
|
|
440
|
+
}
|
|
441
|
+
else if (event === 'spam')
|
|
442
|
+
out.push({ ...base, status: 'complained', ...(rcpt ? { suppress: 'complaint' } : {}) });
|
|
443
|
+
else if (event === 'unsubscribe' && rcpt)
|
|
444
|
+
out.push({ ...base, status: 'complained', detail: 'unsubscribe', suppress: 'unsubscribe' });
|
|
445
|
+
}
|
|
446
|
+
return out;
|
|
447
|
+
}
|
|
448
|
+
function parsePostmark(body) {
|
|
449
|
+
const e = obj(body);
|
|
450
|
+
const type = str(e.RecordType);
|
|
451
|
+
const recipient = str(e.Email) ?? str(e.Recipient);
|
|
452
|
+
const base = evBase('postmark', 'email', e.MessageID, when(e.BouncedAt ?? e.DeliveredAt ?? e.ReceivedAt), recipient);
|
|
453
|
+
if (type === 'Delivery')
|
|
454
|
+
return [{ ...base, status: 'delivered' }];
|
|
455
|
+
if (type === 'Bounce') {
|
|
456
|
+
const name = str(e.Type) ?? '';
|
|
457
|
+
const hard = ['HardBounce', 'BadEmailAddress', 'ManuallyDeactivated', 'Blocked'].includes(name);
|
|
458
|
+
return [{ ...base, status: 'bounced', bounceType: hard ? 'hard' : 'soft', ...(name ? { detail: name } : {}), ...(hard && recipient ? { suppress: 'hard_bounce' } : {}) }];
|
|
459
|
+
}
|
|
460
|
+
if (type === 'SpamComplaint')
|
|
461
|
+
return [{ ...base, status: 'complained', ...(recipient ? { suppress: 'complaint' } : {}) }];
|
|
462
|
+
if (type === 'SubscriptionChange' && e.SuppressSending === true && recipient)
|
|
463
|
+
return [{ ...base, status: 'complained', detail: 'unsubscribe', suppress: 'unsubscribe' }];
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
function parseSendgrid(body) {
|
|
467
|
+
const out = [];
|
|
468
|
+
for (const raw of Array.isArray(body) ? body : []) {
|
|
469
|
+
const e = obj(raw);
|
|
470
|
+
const event = str(e.event);
|
|
471
|
+
const recipient = str(e.email);
|
|
472
|
+
if (!event)
|
|
473
|
+
continue;
|
|
474
|
+
const base = { ...evBase('sendgrid', 'email', undefined, when(e.timestamp), recipient), messageIds: [...idVariants(str(e.sg_message_id)?.split('.')[0]), ...idVariants(e['smtp-id'])] };
|
|
475
|
+
const reason = str(e.reason);
|
|
476
|
+
if (event === 'processed')
|
|
477
|
+
out.push({ ...base, status: 'sent' });
|
|
478
|
+
else if (event === 'delivered')
|
|
479
|
+
out.push({ ...base, status: 'delivered' });
|
|
480
|
+
else if (event === 'bounce' || event === 'blocked') {
|
|
481
|
+
const hard = event === 'bounce' && str(e.type) !== 'blocked';
|
|
482
|
+
out.push({ ...base, status: 'bounced', bounceType: hard ? 'hard' : 'soft', ...(reason ? { detail: reason } : {}), ...(hard && recipient ? { suppress: 'hard_bounce' } : {}) });
|
|
483
|
+
}
|
|
484
|
+
else if (event === 'dropped')
|
|
485
|
+
out.push({ ...base, status: 'failed', ...(reason ? { detail: reason } : {}) });
|
|
486
|
+
else if (event === 'spamreport')
|
|
487
|
+
out.push({ ...base, status: 'complained', ...(recipient ? { suppress: 'complaint' } : {}) });
|
|
488
|
+
else if ((event === 'unsubscribe' || event === 'group_unsubscribe') && recipient)
|
|
489
|
+
out.push({ ...base, status: 'complained', detail: 'unsubscribe', suppress: 'unsubscribe' });
|
|
490
|
+
}
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
function parseResend(body) {
|
|
494
|
+
const e = obj(body);
|
|
495
|
+
const type = str(e.type);
|
|
496
|
+
const d = obj(e.data);
|
|
497
|
+
const to = Array.isArray(d.to) && typeof d.to[0] === 'string' ? d.to[0] : undefined;
|
|
498
|
+
const base = evBase('resend', 'email', d.email_id, when(e.created_at ?? d.created_at), to);
|
|
499
|
+
if (type === 'email.sent')
|
|
500
|
+
return [{ ...base, status: 'sent' }];
|
|
501
|
+
if (type === 'email.delivered')
|
|
502
|
+
return [{ ...base, status: 'delivered' }];
|
|
503
|
+
if (type === 'email.failed')
|
|
504
|
+
return [{ ...base, status: 'failed', ...(str(d.reason) ? { detail: str(d.reason) } : {}) }];
|
|
505
|
+
if (type === 'email.bounced') {
|
|
506
|
+
const bt = (str(obj(d.bounce).type) ?? '').toLowerCase();
|
|
507
|
+
const hard = bt === 'permanent' || bt === 'hard';
|
|
508
|
+
return [{ ...base, status: 'bounced', bounceType: hard ? 'hard' : bt === 'transient' || bt === 'soft' ? 'soft' : 'unknown', ...(str(obj(d.bounce).message) ? { detail: str(obj(d.bounce).message) } : {}), ...(hard && to ? { suppress: 'hard_bounce' } : {}) }];
|
|
509
|
+
}
|
|
510
|
+
if (type === 'email.complained')
|
|
511
|
+
return [{ ...base, status: 'complained', ...(to ? { recipient: to, suppress: 'complaint' } : {}) }];
|
|
512
|
+
return [];
|
|
513
|
+
}
|
|
514
|
+
function parseTwilio(p) {
|
|
515
|
+
const status = p.MessageStatus ?? p.SmsStatus;
|
|
516
|
+
const sid = p.MessageSid ?? p.SmsSid;
|
|
517
|
+
if (!status || !sid)
|
|
518
|
+
return [];
|
|
519
|
+
const to = (p.To ?? '').replace(/^whatsapp:/, '') || undefined;
|
|
520
|
+
const base = evBase('twilio', 'sms', sid, NOW, to);
|
|
521
|
+
const code = p.ErrorCode;
|
|
522
|
+
if (status === 'sent')
|
|
523
|
+
return [{ ...base, status: 'sent' }];
|
|
524
|
+
if (status === 'delivered' || status === 'read')
|
|
525
|
+
return [{ ...base, status: 'delivered' }];
|
|
526
|
+
if (status === 'failed' || status === 'undelivered') {
|
|
527
|
+
const invalid = code === '21211' || code === '21614' || code === '30006';
|
|
528
|
+
return [{ ...base, status: 'failed', detail: code ? `error ${code}` : status, ...(to && invalid ? { suppress: 'invalid' } : {}), ...(to && code === '21610' ? { suppress: 'unsubscribe' } : {}) }];
|
|
529
|
+
}
|
|
530
|
+
return [];
|
|
531
|
+
}
|
|
532
|
+
function parseVonage(p) {
|
|
533
|
+
const id = p.messageId ?? p['message-id'];
|
|
534
|
+
const status = p.status;
|
|
535
|
+
if (!id || !status)
|
|
536
|
+
return [];
|
|
537
|
+
const to = p.msisdn ? (p.msisdn.startsWith('+') ? p.msisdn : `+${p.msisdn}`) : undefined;
|
|
538
|
+
const base = evBase('vonage', 'sms', id, when(p['message-timestamp'] ?? p.scts), to);
|
|
539
|
+
const code = p['err-code'];
|
|
540
|
+
if (status === 'delivered')
|
|
541
|
+
return [{ ...base, status: 'delivered' }];
|
|
542
|
+
if (status === 'accepted' || status === 'buffered')
|
|
543
|
+
return [{ ...base, status: 'sent' }];
|
|
544
|
+
if (['failed', 'rejected', 'expired', 'unknown'].includes(status)) {
|
|
545
|
+
const invalid = code === '1' || code === '9' || code === '13';
|
|
546
|
+
return [{ ...base, status: 'failed', detail: `${status}${code && code !== '0' ? ` (error ${code})` : ''}`, ...(to && invalid ? { suppress: 'invalid' } : {}) }];
|
|
547
|
+
}
|
|
548
|
+
return [];
|
|
549
|
+
}
|
|
550
|
+
function parseMessageBird(p) {
|
|
551
|
+
const id = p.id;
|
|
552
|
+
const status = p.status;
|
|
553
|
+
if (!id || !status)
|
|
554
|
+
return [];
|
|
555
|
+
const to = p.recipient ? (p.recipient.startsWith('+') ? p.recipient : `+${p.recipient}`) : undefined;
|
|
556
|
+
const base = evBase('messagebird', 'sms', id, when(p.statusDatetime), to);
|
|
557
|
+
if (status === 'sent' || status === 'buffered' || status === 'scheduled')
|
|
558
|
+
return [{ ...base, status: 'sent' }];
|
|
559
|
+
if (status === 'delivered')
|
|
560
|
+
return [{ ...base, status: 'delivered' }];
|
|
561
|
+
if (status === 'delivery_failed' || status === 'expired') {
|
|
562
|
+
const reason = p.statusReason ?? status;
|
|
563
|
+
const invalid = p.statusErrorCode === '8' || p.statusErrorCode === '9' || reason === 'unknown subscriber';
|
|
564
|
+
return [{ ...base, status: 'failed', detail: reason, ...(to && invalid ? { suppress: 'invalid' } : {}) }];
|
|
565
|
+
}
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
/** Verify one callback with the provider's own scheme and say what it reports. Never throws. */
|
|
569
|
+
async function verifyCallback(i) {
|
|
570
|
+
NOW = i.now * 1000;
|
|
571
|
+
const bad = (why, message) => ({ ok: false, why, ...(message ? { message } : {}) });
|
|
572
|
+
const malformed = { ok: false, why: 'malformed body', malformed: true };
|
|
573
|
+
const json = () => {
|
|
574
|
+
try {
|
|
575
|
+
return JSON.parse(i.raw);
|
|
576
|
+
}
|
|
577
|
+
catch {
|
|
578
|
+
return undefined;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
const tokenOk = presentedTokens(i.headers, i.query).some((p) => eq(p, i.token));
|
|
582
|
+
const digest = async () => (await (0, exports.sha256Hex)(i.raw)).slice(0, 32);
|
|
583
|
+
const h = (n) => i.headers.get(n) ?? '';
|
|
584
|
+
const asParams = () => (i.raw ? (i.contentType.includes('json') ? stringify(json()) : form(i.raw)) : {});
|
|
585
|
+
switch (i.provider) {
|
|
586
|
+
case 'ses': {
|
|
587
|
+
if (!tokenOk)
|
|
588
|
+
return bad('bad token');
|
|
589
|
+
const sns = json();
|
|
590
|
+
if (!sns || typeof sns !== 'object')
|
|
591
|
+
return bad('unreadable body');
|
|
592
|
+
if (!(await verifySns(sns, i.snsCertificate)))
|
|
593
|
+
return bad('SNS signature did not verify');
|
|
594
|
+
const topics = i.secret?.split(',').map((t) => t.trim()).filter(Boolean) ?? [];
|
|
595
|
+
if (topics.length && (!sns.TopicArn || !topics.includes(sns.TopicArn)))
|
|
596
|
+
return bad('topic not accepted');
|
|
597
|
+
const id = String(sns.MessageId ?? (await digest()));
|
|
598
|
+
if (sns.Type === 'SubscriptionConfirmation')
|
|
599
|
+
return { ok: true, events: [], id, confirmed: true, ...(snsUrlAllowed(sns.SubscribeURL) ? { subscribeUrl: sns.SubscribeURL } : {}) };
|
|
600
|
+
if (sns.Type !== 'Notification' || typeof sns.Message !== 'string')
|
|
601
|
+
return { ok: true, events: [], id };
|
|
602
|
+
let inner;
|
|
603
|
+
try {
|
|
604
|
+
inner = JSON.parse(sns.Message);
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
inner = undefined;
|
|
608
|
+
}
|
|
609
|
+
return { ok: true, events: parseSes(inner), id };
|
|
610
|
+
}
|
|
611
|
+
case 'mailgun': {
|
|
612
|
+
const body = json();
|
|
613
|
+
if (!i.secret || !body || !(await verifyMailgun(i.secret, body.signature ?? {}, i.now)))
|
|
614
|
+
return bad('signature did not verify');
|
|
615
|
+
return { ok: true, events: parseMailgun(body), id: String(body.signature?.token ?? (await digest())) };
|
|
616
|
+
}
|
|
617
|
+
case 'smtp2go':
|
|
618
|
+
case 'postmark': {
|
|
619
|
+
if (!tokenOk)
|
|
620
|
+
return bad('bad token');
|
|
621
|
+
const body = json();
|
|
622
|
+
if (body === undefined)
|
|
623
|
+
return malformed;
|
|
624
|
+
return { ok: true, events: i.provider === 'smtp2go' ? parseSmtp2go(body) : parsePostmark(body), id: await digest() };
|
|
625
|
+
}
|
|
626
|
+
case 'sendgrid': {
|
|
627
|
+
if (i.secret) {
|
|
628
|
+
const sig = h('x-twilio-email-event-webhook-signature');
|
|
629
|
+
const ts = h('x-twilio-email-event-webhook-timestamp');
|
|
630
|
+
if (!sig || !ts || !(await verifySendgrid(i.secret, sig, ts, i.raw, i.now)))
|
|
631
|
+
return bad('signature did not verify');
|
|
632
|
+
}
|
|
633
|
+
else if (!tokenOk)
|
|
634
|
+
return bad('bad token');
|
|
635
|
+
const body = json();
|
|
636
|
+
if (body === undefined)
|
|
637
|
+
return malformed;
|
|
638
|
+
return { ok: true, events: parseSendgrid(body), id: await digest() };
|
|
639
|
+
}
|
|
640
|
+
case 'twilio': {
|
|
641
|
+
const sig = h('x-twilio-signature');
|
|
642
|
+
const params = form(i.raw);
|
|
643
|
+
let ok = false;
|
|
644
|
+
if (sig)
|
|
645
|
+
for (const t of i.twilioTokens)
|
|
646
|
+
for (const u of i.urls)
|
|
647
|
+
ok ||= eq(await twilioSig(t, u, params), sig);
|
|
648
|
+
if (!ok)
|
|
649
|
+
return bad('signature did not verify');
|
|
650
|
+
return { ok: true, events: parseTwilio(params), id: `${params.MessageSid ?? params.SmsSid ?? ''}:${params.MessageStatus ?? params.SmsStatus ?? ''}` };
|
|
651
|
+
}
|
|
652
|
+
case 'vonage': {
|
|
653
|
+
const params = { ...i.query, ...asParams() };
|
|
654
|
+
const verified = i.secret ? await verifyVonage(i.secret, params, i.now) : tokenOk;
|
|
655
|
+
if (!verified)
|
|
656
|
+
return bad(i.secret ? 'signature did not verify' : 'bad token');
|
|
657
|
+
delete params.t;
|
|
658
|
+
delete params.token;
|
|
659
|
+
return { ok: true, events: parseVonage(params), id: `${params.messageId ?? params['message-id'] ?? ''}:${params.status ?? ''}` };
|
|
660
|
+
}
|
|
661
|
+
case 'messagebird': {
|
|
662
|
+
let verified;
|
|
663
|
+
if (i.secret) {
|
|
664
|
+
const sig = h('messagebird-signature');
|
|
665
|
+
const ts = h('messagebird-request-timestamp');
|
|
666
|
+
verified = Boolean(sig && ts && (await verifyMessageBird(i.secret, sig, ts, i.query, i.raw, i.now)));
|
|
667
|
+
}
|
|
668
|
+
else
|
|
669
|
+
verified = tokenOk;
|
|
670
|
+
if (!verified)
|
|
671
|
+
return bad(i.secret ? 'signature did not verify' : 'bad token');
|
|
672
|
+
const params = { ...i.query, ...asParams() };
|
|
673
|
+
return { ok: true, events: parseMessageBird(params), id: `${params.id ?? ''}:${params.status ?? ''}` };
|
|
674
|
+
}
|
|
675
|
+
case 'resend': {
|
|
676
|
+
const pick = (n) => h(`svix-${n}`) || h(`webhook-${n}`);
|
|
677
|
+
const [id, ts, sig] = [pick('id'), pick('timestamp'), pick('signature')];
|
|
678
|
+
if (!i.secret || !id || !ts || !sig || !(await verifySvix({ secret: i.secret, id, timestamp: ts, signatureHeader: sig, body: i.raw, now: i.now })))
|
|
679
|
+
return bad('signature did not verify');
|
|
680
|
+
const body = json();
|
|
681
|
+
if (body === undefined)
|
|
682
|
+
return { ok: false, why: 'malformed body', malformed: true, message: 'Request body is not valid JSON.' };
|
|
683
|
+
return { ok: true, events: parseResend(body), id };
|
|
684
|
+
}
|
|
685
|
+
case 'brevo': {
|
|
686
|
+
const auth = h('authorization');
|
|
687
|
+
if (!eq(auth.startsWith('Bearer ') ? auth.slice(7).trim() : '', i.token))
|
|
688
|
+
return bad('bad token', 'Invalid webhook token.');
|
|
689
|
+
const body = json();
|
|
690
|
+
return { ok: true, events: parseBrevo(body ?? []), id: await digest() };
|
|
691
|
+
}
|
|
692
|
+
default:
|
|
693
|
+
return bad('unknown provider');
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
async function prepareCallback(p) {
|
|
697
|
+
const { event, messageId: id, recipient: to, now, salt } = p;
|
|
698
|
+
const hard = p.bounce === 'hard';
|
|
699
|
+
const iso = new Date(now * 1000).toISOString();
|
|
700
|
+
const json = (v, headers = {}, query = {}) => ({ content_type: 'application/json', body: JSON.stringify(v), headers, query });
|
|
701
|
+
const urlform = (v, headers = {}) => ({ content_type: 'application/x-www-form-urlencoded', body: new URLSearchParams(v).toString(), headers, query: {} });
|
|
702
|
+
const tokenAuth = (token = p.token) => ({ authorization: `Bearer ${token}`, ...(salt ? { authorization: `Bearer ${token}x` } : {}) });
|
|
703
|
+
const pick = (m) => m[event];
|
|
704
|
+
switch (p.provider) {
|
|
705
|
+
case 'resend': {
|
|
706
|
+
const body = JSON.stringify({ type: pick({ sent: 'email.sent', delivered: 'email.delivered', bounced: 'email.bounced', complained: 'email.complained', failed: 'email.failed' }), created_at: iso, data: { email_id: id, from: 'noreply@emulator.test', to: [to], bounce: { type: hard ? 'Permanent' : 'Transient', message: p.detail ?? 'bounced' }, reason: p.detail ?? 'failed' } });
|
|
707
|
+
const msgId = `msg_${(await (0, exports.sha256Hex)(`${id}:${event}:${now}:${Math.random()}`)).slice(0, 24)}`;
|
|
708
|
+
const sig = toB64(await hmac('sha256', fromB64(p.secret.replace(/^whsec_/, '')), `${msgId}.${now}.${body}${salt}`));
|
|
709
|
+
return { content_type: 'application/json', body, headers: { 'svix-id': msgId, 'svix-timestamp': String(now), 'svix-signature': `v1,${sig}` }, query: {} };
|
|
710
|
+
}
|
|
711
|
+
case 'mailgun': {
|
|
712
|
+
const token = (await (0, exports.sha256Hex)(`${id}:${event}:${Math.random()}`)).slice(0, 50);
|
|
713
|
+
const failed = event === 'bounced' || event === 'failed';
|
|
714
|
+
return json({ signature: { timestamp: String(now), token, signature: await mailgunSig(p.secret, String(now), token + salt) }, 'event-data': { event: pick({ sent: 'accepted', delivered: 'delivered', bounced: 'failed', complained: 'complained', failed: 'failed' }), id: `evt_${token.slice(0, 12)}`, recipient: to, timestamp: now, ...(failed ? { severity: event === 'bounced' && hard ? 'permanent' : 'temporary' } : {}), message: { headers: { 'message-id': id } }, 'delivery-status': { description: p.detail ?? event } } });
|
|
715
|
+
}
|
|
716
|
+
case 'brevo':
|
|
717
|
+
return json({ event: pick({ sent: 'request', delivered: 'delivered', bounced: hard ? 'hard_bounce' : 'soft_bounce', complained: 'spam', failed: 'error' }), 'message-id': id, email: to, ts_event: now, ...(p.detail ? { reason: p.detail } : {}) }, tokenAuth());
|
|
718
|
+
case 'postmark':
|
|
719
|
+
return json({ RecordType: pick({ sent: 'Delivery', delivered: 'Delivery', bounced: 'Bounce', complained: 'SpamComplaint', failed: 'Bounce' }), MessageID: id, Email: to, Type: event === 'bounced' && hard ? 'HardBounce' : 'Transient', BouncedAt: iso, DeliveredAt: iso }, { authorization: `Basic ${btoa(`atlas:${p.token}${salt ? 'x' : ''}`)}` });
|
|
720
|
+
case 'smtp2go':
|
|
721
|
+
return json({ event: pick({ sent: 'processed', delivered: 'delivered', bounced: 'bounce', complained: 'spam', failed: 'bounce' }), email_id: id, rcpt: to, bounce: event === 'bounced' && hard ? 'hard' : 'soft', sendtime: iso, ...(p.detail ? { message: p.detail } : {}) }, tokenAuth());
|
|
722
|
+
case 'sendgrid': {
|
|
723
|
+
const body = JSON.stringify([{ event: pick({ sent: 'processed', delivered: 'delivered', bounced: 'bounce', complained: 'spamreport', failed: 'dropped' }), email: to, sg_message_id: `${id}.filter0001`, timestamp: now, ...(event === 'bounced' && !hard ? { type: 'blocked' } : {}), ...(p.detail ? { reason: p.detail } : {}) }]);
|
|
724
|
+
if (p.secret === null)
|
|
725
|
+
return { content_type: 'application/json', body, headers: tokenAuth(), query: {} };
|
|
726
|
+
const { privateKey } = await p.keys.ec();
|
|
727
|
+
const raw = new Uint8Array(await subtle().sign({ name: 'ECDSA', hash: 'SHA-256' }, privateKey, enc.encode(String(now) + body + salt)));
|
|
728
|
+
return { content_type: 'application/json', body, headers: { 'x-twilio-email-event-webhook-signature': toB64(rawToDer(raw)), 'x-twilio-email-event-webhook-timestamp': String(now) }, query: {} };
|
|
729
|
+
}
|
|
730
|
+
case 'ses': {
|
|
731
|
+
const mail = { messageId: id, timestamp: iso };
|
|
732
|
+
const inner = pick({
|
|
733
|
+
sent: { notificationType: 'Send', mail },
|
|
734
|
+
delivered: { notificationType: 'Delivery', mail, delivery: { recipients: [to], timestamp: iso } },
|
|
735
|
+
bounced: { notificationType: 'Bounce', mail, bounce: { bounceType: hard ? 'Permanent' : 'Transient', bounceSubType: p.detail ?? 'General', bouncedRecipients: [{ emailAddress: to }], timestamp: iso } },
|
|
736
|
+
complained: { notificationType: 'Complaint', mail, complaint: { complainedRecipients: [{ emailAddress: to }], complaintFeedbackType: p.detail ?? 'abuse', timestamp: iso } },
|
|
737
|
+
failed: { notificationType: 'Reject', mail, reject: { reason: p.detail ?? 'Rejected' } },
|
|
738
|
+
});
|
|
739
|
+
const sns = { Type: 'Notification', MessageId: `sns_${(await (0, exports.sha256Hex)(`${id}:${event}:${Math.random()}`)).slice(0, 24)}`, TopicArn: p.topic, Message: JSON.stringify(inner), Timestamp: iso, SignatureVersion: '2', SigningCertURL: exports.SNS_CERT_URL };
|
|
740
|
+
sns.Signature = await signSns(sns, (await p.keys.rsa()).pkcs8, salt);
|
|
741
|
+
return json(sns, {}, { t: p.token });
|
|
742
|
+
}
|
|
743
|
+
case 'twilio': {
|
|
744
|
+
const params = { MessageSid: id, MessageStatus: pick({ sent: 'sent', delivered: 'delivered', bounced: 'undelivered', complained: 'failed', failed: 'failed' }), To: to };
|
|
745
|
+
if (event === 'bounced')
|
|
746
|
+
params.ErrorCode = '30006';
|
|
747
|
+
else if (event === 'complained')
|
|
748
|
+
params.ErrorCode = '21610';
|
|
749
|
+
return urlform(params, { 'x-twilio-signature': await twilioSig(p.secret, p.url + salt, params) });
|
|
750
|
+
}
|
|
751
|
+
case 'vonage': {
|
|
752
|
+
const params = { messageId: id, status: pick({ sent: 'accepted', delivered: 'delivered', bounced: 'failed', complained: 'failed', failed: 'failed' }), msisdn: to.replace(/^\+/, ''), 'message-timestamp': iso, 'err-code': event === 'delivered' || event === 'sent' ? '0' : event === 'failed' ? '5' : '1', timestamp: String(now) };
|
|
753
|
+
if (p.secret === null)
|
|
754
|
+
return urlform(params, tokenAuth());
|
|
755
|
+
return urlform({ ...params, sig: (await vonageSig(p.secret, params)).replace(/^./, (c) => (salt ? (c === '0' ? '1' : '0') : c)) });
|
|
756
|
+
}
|
|
757
|
+
case 'messagebird': {
|
|
758
|
+
const params = { id, status: pick({ sent: 'sent', delivered: 'delivered', bounced: 'delivery_failed', complained: 'delivery_failed', failed: 'delivery_failed' }), recipient: to.replace(/^\+/, ''), statusDatetime: iso, ...(event === 'bounced' || event === 'complained' ? { statusErrorCode: '9', statusReason: 'unknown subscriber' } : {}) };
|
|
759
|
+
const out = urlform(params);
|
|
760
|
+
out.headers = p.secret === null ? tokenAuth() : { 'messagebird-signature': await messageBirdSig(p.secret, String(now), {}, out.body + salt), 'messagebird-request-timestamp': String(now) };
|
|
761
|
+
return out;
|
|
762
|
+
}
|
|
763
|
+
default:
|
|
764
|
+
throw new Error(`unknown provider ${p.provider}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
//# sourceMappingURL=messaging-callbacks.js.map
|