@stacksjs/email 0.70.258 → 0.70.259

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.
@@ -1,148 +1,2 @@
1
- import { createHmac, createVerify, timingSafeEqual } from "node:crypto";
2
- import { Buffer } from "node:buffer";
3
- export function verifyMailgunSignature(input) {
4
- if (!input.signingKey)
5
- return { ok: !1, reason: "missing-config" };
6
- if (!input.timestamp || !input.token || !input.signature)
7
- return { ok: !1, reason: "missing-signature" };
8
- const tolerance = input.toleranceSeconds ?? 300, ts = Number(input.timestamp);
9
- if (!Number.isFinite(ts))
10
- return { ok: !1, reason: "bad-signature" };
11
- const now = Math.floor(Date.now() / 1000);
12
- if (Math.abs(now - ts) > tolerance)
13
- return { ok: !1, reason: "expired" };
14
- const expected = createHmac("sha256", input.signingKey).update(`${input.timestamp}${input.token}`).digest("hex");
15
- let provided;
16
- try {
17
- provided = Buffer.from(input.signature, "hex");
18
- } catch {
19
- return { ok: !1, reason: "bad-signature" };
20
- }
21
- const expectedBuf = Buffer.from(expected, "hex");
22
- if (provided.length !== expectedBuf.length)
23
- return { ok: !1, reason: "bad-signature" };
24
- if (!timingSafeEqual(provided, expectedBuf))
25
- return { ok: !1, reason: "bad-signature" };
26
- return { ok: !0 };
27
- }
28
- export function verifyPostmarkAuth(input) {
29
- if (!input.expectedUsername || !input.expectedPassword)
30
- return { ok: !1, reason: "missing-config" };
31
- if (!input.authorizationHeader || !input.authorizationHeader.startsWith("Basic "))
32
- return { ok: !1, reason: "missing-signature" };
33
- let decoded;
34
- try {
35
- decoded = Buffer.from(input.authorizationHeader.slice(6), "base64").toString("utf8");
36
- } catch {
37
- return { ok: !1, reason: "bad-signature" };
38
- }
39
- const [user, pass] = decoded.split(":");
40
- if (!user || pass === void 0)
41
- return { ok: !1, reason: "bad-signature" };
42
- const userMatch = safeStringEquals(user, input.expectedUsername), passMatch = safeStringEquals(pass, input.expectedPassword);
43
- if (!userMatch || !passMatch)
44
- return { ok: !1, reason: "bad-signature" };
45
- if (input.ipAllowlist && input.ipAllowlist.length > 0) {
46
- if (!input.sourceIp || !input.ipAllowlist.includes(input.sourceIp))
47
- return { ok: !1, reason: "bad-signature" };
48
- }
49
- return { ok: !0 };
50
- }
51
- function safeStringEquals(a, b) {
52
- const aBuf = Buffer.from(a, "utf8"), bBuf = Buffer.from(b, "utf8");
53
- if (aBuf.length !== bBuf.length) {
54
- const dummy = Buffer.alloc(Math.max(aBuf.length, bBuf.length));
55
- timingSafeEqual(dummy, dummy);
56
- return !1;
57
- }
58
- return timingSafeEqual(aBuf, bBuf);
59
- }
60
- const DEFAULT_SNS_HOST_ALLOWLIST = /^sns\.[a-z0-9-]+\.amazonaws\.com$/;
61
- async function defaultCertFetcher(url) {
62
- const res = await fetch(url, { redirect: "error" });
63
- if (!res.ok)
64
- throw Error(`SNS cert fetch returned ${res.status}`);
65
- return await res.text();
66
- }
67
- export async function verifySesSnsSignature(input) {
68
- const msg = input.message;
69
- if (!msg || !msg.Signature || !msg.SigningCertURL)
70
- return { ok: !1, reason: "missing-signature" };
71
- const allowlist = input.certUrlHostAllowlist ?? DEFAULT_SNS_HOST_ALLOWLIST;
72
- let certUrl;
73
- try {
74
- certUrl = new URL(msg.SigningCertURL);
75
- } catch {
76
- return { ok: !1, reason: "untrusted-cert-url" };
77
- }
78
- if (certUrl.protocol !== "https:")
79
- return { ok: !1, reason: "untrusted-cert-url" };
80
- if (!allowlist.test(certUrl.host))
81
- return { ok: !1, reason: "untrusted-cert-url" };
82
- let certPem;
83
- try {
84
- certPem = await (input.fetchCert ?? defaultCertFetcher)(msg.SigningCertURL);
85
- } catch {
86
- return { ok: !1, reason: "cert-fetch-failed" };
87
- }
88
- const canonical = canonicalSnsString(msg);
89
- if (!canonical)
90
- return { ok: !1, reason: "bad-signature" };
91
- let sigBuf;
92
- try {
93
- sigBuf = Buffer.from(msg.Signature, "base64");
94
- } catch {
95
- return { ok: !1, reason: "bad-signature" };
96
- }
97
- const algo = msg.SignatureVersion === "2" ? "SHA256" : "SHA1", verifier = createVerify(`RSA-${algo}`);
98
- verifier.update(canonical, "utf8");
99
- return verifier.verify(certPem, sigBuf) ? { ok: !0 } : { ok: !1, reason: "bad-signature" };
100
- }
101
- function canonicalSnsString(msg) {
102
- const fields = [];
103
- if (msg.Type === "Notification") {
104
- fields.push("Message", msg.Message);
105
- fields.push("MessageId", msg.MessageId);
106
- if (msg.Subject !== void 0)
107
- fields.push("Subject", msg.Subject);
108
- fields.push("Timestamp", msg.Timestamp);
109
- fields.push("TopicArn", msg.TopicArn);
110
- fields.push("Type", msg.Type);
111
- } else if (msg.Type === "SubscriptionConfirmation" || msg.Type === "UnsubscribeConfirmation") {
112
- fields.push("Message", msg.Message);
113
- fields.push("MessageId", msg.MessageId);
114
- if (!msg.SubscribeURL || !msg.Token)
115
- return null;
116
- fields.push("SubscribeURL", msg.SubscribeURL);
117
- fields.push("Timestamp", msg.Timestamp);
118
- fields.push("Token", msg.Token);
119
- fields.push("TopicArn", msg.TopicArn);
120
- fields.push("Type", msg.Type);
121
- } else
122
- return null;
123
- let out = "";
124
- for (const f of fields)
125
- out += `${f}
126
- `;
127
- return out;
128
- }
129
- export function verifySendgridSignature(input) {
130
- if (!input.publicKeyPem)
131
- return { ok: !1, reason: "missing-config" };
132
- if (!input.signature || !input.timestamp)
133
- return { ok: !1, reason: "missing-signature" };
134
- const tolerance = input.toleranceSeconds ?? 300, ts = Number(input.timestamp);
135
- if (!Number.isFinite(ts))
136
- return { ok: !1, reason: "bad-signature" };
137
- if (Math.abs(Math.floor(Date.now() / 1000) - ts) > tolerance)
138
- return { ok: !1, reason: "expired" };
139
- let sigBuf;
140
- try {
141
- sigBuf = Buffer.from(input.signature, "base64");
142
- } catch {
143
- return { ok: !1, reason: "bad-signature" };
144
- }
145
- const verifier = createVerify("SHA256");
146
- verifier.update(`${input.timestamp}${input.body}`, "utf8");
147
- return verifier.verify(input.publicKeyPem, sigBuf) ? { ok: !0 } : { ok: !1, reason: "bad-signature" };
148
- }
1
+ import{createHmac,createVerify,timingSafeEqual}from"node:crypto";import{Buffer}from"node:buffer";export function verifyMailgunSignature(input){if(!input.signingKey)return{ok:!1,reason:"missing-config"};if(!input.timestamp||!input.token||!input.signature)return{ok:!1,reason:"missing-signature"};const tolerance=input.toleranceSeconds??300,ts=Number(input.timestamp);if(!Number.isFinite(ts))return{ok:!1,reason:"bad-signature"};const now=Math.floor(Date.now()/1000);if(Math.abs(now-ts)>tolerance)return{ok:!1,reason:"expired"};const expected=createHmac("sha256",input.signingKey).update(`${input.timestamp}${input.token}`).digest("hex");let provided;try{provided=Buffer.from(input.signature,"hex")}catch{return{ok:!1,reason:"bad-signature"}}const expectedBuf=Buffer.from(expected,"hex");if(provided.length!==expectedBuf.length)return{ok:!1,reason:"bad-signature"};if(!timingSafeEqual(provided,expectedBuf))return{ok:!1,reason:"bad-signature"};return{ok:!0}}export function verifyPostmarkAuth(input){if(!input.expectedUsername||!input.expectedPassword)return{ok:!1,reason:"missing-config"};if(!input.authorizationHeader||!input.authorizationHeader.startsWith("Basic "))return{ok:!1,reason:"missing-signature"};let decoded;try{decoded=Buffer.from(input.authorizationHeader.slice(6),"base64").toString("utf8")}catch{return{ok:!1,reason:"bad-signature"}}const[user,pass]=decoded.split(":");if(!user||pass===void 0)return{ok:!1,reason:"bad-signature"};const userMatch=safeStringEquals(user,input.expectedUsername),passMatch=safeStringEquals(pass,input.expectedPassword);if(!userMatch||!passMatch)return{ok:!1,reason:"bad-signature"};if(input.ipAllowlist&&input.ipAllowlist.length>0){if(!input.sourceIp||!input.ipAllowlist.includes(input.sourceIp))return{ok:!1,reason:"bad-signature"}}return{ok:!0}}function safeStringEquals(a,b){const aBuf=Buffer.from(a,"utf8"),bBuf=Buffer.from(b,"utf8");if(aBuf.length!==bBuf.length){const dummy=Buffer.alloc(Math.max(aBuf.length,bBuf.length));timingSafeEqual(dummy,dummy);return!1}return timingSafeEqual(aBuf,bBuf)}const DEFAULT_SNS_HOST_ALLOWLIST=/^sns\.[a-z0-9-]+\.amazonaws\.com$/;async function defaultCertFetcher(url){const res=await fetch(url,{redirect:"error"});if(!res.ok)throw Error(`SNS cert fetch returned ${res.status}`);return await res.text()}export async function verifySesSnsSignature(input){const msg=input.message;if(!msg||!msg.Signature||!msg.SigningCertURL)return{ok:!1,reason:"missing-signature"};const allowlist=input.certUrlHostAllowlist??DEFAULT_SNS_HOST_ALLOWLIST;let certUrl;try{certUrl=new URL(msg.SigningCertURL)}catch{return{ok:!1,reason:"untrusted-cert-url"}}if(certUrl.protocol!=="https:")return{ok:!1,reason:"untrusted-cert-url"};if(!allowlist.test(certUrl.host))return{ok:!1,reason:"untrusted-cert-url"};let certPem;try{certPem=await(input.fetchCert??defaultCertFetcher)(msg.SigningCertURL)}catch{return{ok:!1,reason:"cert-fetch-failed"}}const canonical=canonicalSnsString(msg);if(!canonical)return{ok:!1,reason:"bad-signature"};let sigBuf;try{sigBuf=Buffer.from(msg.Signature,"base64")}catch{return{ok:!1,reason:"bad-signature"}}const algo=msg.SignatureVersion==="2"?"SHA256":"SHA1",verifier=createVerify(`RSA-${algo}`);verifier.update(canonical,"utf8");return verifier.verify(certPem,sigBuf)?{ok:!0}:{ok:!1,reason:"bad-signature"}}function canonicalSnsString(msg){const fields=[];if(msg.Type==="Notification"){fields.push("Message",msg.Message);fields.push("MessageId",msg.MessageId);if(msg.Subject!==void 0)fields.push("Subject",msg.Subject);fields.push("Timestamp",msg.Timestamp);fields.push("TopicArn",msg.TopicArn);fields.push("Type",msg.Type)}else if(msg.Type==="SubscriptionConfirmation"||msg.Type==="UnsubscribeConfirmation"){fields.push("Message",msg.Message);fields.push("MessageId",msg.MessageId);if(!msg.SubscribeURL||!msg.Token)return null;fields.push("SubscribeURL",msg.SubscribeURL);fields.push("Timestamp",msg.Timestamp);fields.push("Token",msg.Token);fields.push("TopicArn",msg.TopicArn);fields.push("Type",msg.Type)}else return null;let out="";for(const f of fields)out+=`${f}
2
+ `;return out}export function verifySendgridSignature(input){if(!input.publicKeyPem)return{ok:!1,reason:"missing-config"};if(!input.signature||!input.timestamp)return{ok:!1,reason:"missing-signature"};const tolerance=input.toleranceSeconds??300,ts=Number(input.timestamp);if(!Number.isFinite(ts))return{ok:!1,reason:"bad-signature"};if(Math.abs(Math.floor(Date.now()/1000)-ts)>tolerance)return{ok:!1,reason:"expired"};let sigBuf;try{sigBuf=Buffer.from(input.signature,"base64")}catch{return{ok:!1,reason:"bad-signature"}}const verifier=createVerify("SHA256");verifier.update(`${input.timestamp}${input.body}`,"utf8");return verifier.verify(input.publicKeyPem,sigBuf)?{ok:!0}:{ok:!1,reason:"bad-signature"}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/email",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.258",
5
+ "version": "0.70.259",
6
6
  "description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -35,9 +35,16 @@
35
35
  "default": "./dist/index.js"
36
36
  },
37
37
  "./*": {
38
- "bun": "./dist/*",
39
- "import": "./dist/*",
40
- "default": "./dist/*"
38
+ "types": "./dist/*.d.ts",
39
+ "bun": "./dist/*.js",
40
+ "import": "./dist/*.js",
41
+ "default": "./dist/*.js"
42
+ },
43
+ "./*.js": {
44
+ "types": "./dist/*.d.ts",
45
+ "bun": "./dist/*.js",
46
+ "import": "./dist/*.js",
47
+ "default": "./dist/*.js"
41
48
  }
42
49
  },
43
50
  "module": "dist/index.js",
@@ -56,10 +63,10 @@
56
63
  "@stacksjs/ts-cloud": "^0.7.94"
57
64
  },
58
65
  "devDependencies": {
59
- "@stacksjs/cli": "0.70.258",
60
- "@stacksjs/config": "0.70.258",
66
+ "@stacksjs/cli": "0.70.259",
67
+ "@stacksjs/config": "0.70.259",
61
68
  "better-dx": "^0.2.17",
62
- "@stacksjs/error-handling": "0.70.258",
63
- "@stacksjs/types": "0.70.258"
69
+ "@stacksjs/error-handling": "0.70.259",
70
+ "@stacksjs/types": "0.70.259"
64
71
  }
65
72
  }