@plusscommunities/pluss-core-aws 2.0.20 → 2.0.22

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.
@@ -0,0 +1,25 @@
1
+ const crypto = require("crypto");
2
+ const { getRef } = require("../../db/common/getRef");
3
+
4
+ module.exports = async (token) => {
5
+ if (!token) return false;
6
+
7
+ try {
8
+ // Create hash of token for TokenId lookup
9
+ const tokenHash = crypto.createHash("sha256").update(token).digest("hex");
10
+
11
+ // Check if token exists in blacklist
12
+ const blacklistedToken = await getRef(
13
+ "invalidTokens",
14
+ "TokenId",
15
+ tokenHash
16
+ );
17
+
18
+ // Return true if found (blacklisted), false if not found
19
+ return !!blacklistedToken;
20
+ } catch (error) {
21
+ // If error occurs during lookup, assume token is not blacklisted
22
+ // This ensures authentication doesn't fail due to blacklist issues
23
+ return false;
24
+ }
25
+ };
@@ -1,9 +1,21 @@
1
1
  const https = require("https");
2
2
  const jose = require("node-jose");
3
3
  const { getConfig } = require("../../config");
4
+ const checkTokenBlacklist = require("./checkTokenBlacklist");
4
5
 
5
6
  module.exports = async (token) => {
6
- return new Promise((resolve, reject) => {
7
+ return new Promise(async (resolve, reject) => {
8
+ if (!token) {
9
+ return resolve(null);
10
+ }
11
+
12
+ // Check if token is blacklisted before expensive verification
13
+ const isBlacklisted = await checkTokenBlacklist(token);
14
+ if (isBlacklisted) {
15
+ reject("Token has been invalidated");
16
+ return;
17
+ }
18
+
7
19
  var sections = token.split(".");
8
20
  // get the kid from the headers prior to verification
9
21
  var header = jose.util.base64url.decode(sections[0]);
@@ -1,26 +1,52 @@
1
- const nodemailer = require("nodemailer");
1
+ const axios = require("axios");
2
+ const moment = require("moment");
2
3
  const sendEmail = require("../aws/sendEmail");
3
4
  const getEmailServiceInfo = require("../aws/getEmailServiceInfo");
4
5
  const { getConfig } = require("../config");
6
+ const { log } = require("./");
7
+ const getRef = require("../db/common/getRef");
8
+ const updateRef = require("../db/common/updateRef");
5
9
 
6
- const sendFallback = (mailOptions, emailConfig) => {
10
+ const sendFallback = (mailOptions, emailConfig, accessConfig) => {
7
11
  return new Promise((resolve, reject) => {
8
- console.log("Cannot use Amazon SES, fall back to nodemailer", mailOptions);
9
- const transporter = nodemailer.createTransport({
10
- //host: 'smtp.ethereal.email',
11
- service: "gmail",
12
- //port: 587,
13
- auth: emailConfig.fallbackAuth,
14
- });
15
- transporter.sendMail(mailOptions, (error) => {
16
- if (error) {
17
- console.log("Email failed", mailOptions.to, mailOptions.subject, error);
12
+ const logId = log("sendFallback", "Email", mailOptions);
13
+ if (emailConfig.isFallback) {
14
+ log(
15
+ "sendFallback",
16
+ "isFallback",
17
+ "Not allowed to use this account as it is the fallback itself",
18
+ logId
19
+ );
20
+ return reject(
21
+ new Error(
22
+ "Not allowed to use this account as it is the fallback itself"
23
+ )
24
+ );
25
+ }
26
+
27
+ // Calling organisation account as the fallback
28
+ log("sendFallback", "Fallback", emailConfig.fallbackUrl, logId);
29
+ axios({
30
+ method: "POST",
31
+ url: emailConfig.fallbackUrl,
32
+ headers: {
33
+ Authorization: accessConfig.authSecret,
34
+ },
35
+ data: {
36
+ to: mailOptions.to,
37
+ subject: mailOptions.subject,
38
+ body: mailOptions.html,
39
+ from: mailOptions.from,
40
+ },
41
+ })
42
+ .then((response) => {
43
+ log("sendFallback", "response", response.data, logId);
44
+ resolve(response.data);
45
+ })
46
+ .catch((error) => {
47
+ log("sendFallback", "error", error, logId);
18
48
  reject(error);
19
- } else {
20
- console.log("Email success", mailOptions.to, mailOptions.subject);
21
- resolve();
22
- }
23
- });
49
+ });
24
50
  });
25
51
  };
26
52
 
@@ -34,9 +60,37 @@ module.exports = async (
34
60
  excludeClientBranding = false,
35
61
  brandingImage = null,
36
62
  bcc = null,
63
+ rateLimitId = null,
37
64
  } = {}
38
65
  ) => {
39
- const { communityConfig, serverlessConfig, emailConfig } = getConfig();
66
+ const { communityConfig, serverlessConfig, emailConfig, accessConfig } =
67
+ getConfig();
68
+ const now = moment.utc();
69
+ let rateLimitIdToUse;
70
+
71
+ const logId = log("sendEmail", "Email", {
72
+ toEmail,
73
+ subject,
74
+ });
75
+
76
+ if (rateLimitId) {
77
+ try {
78
+ rateLimitIdToUse = `email_${rateLimitId}`;
79
+ const item = await getRef("ratelimit", "Id", rateLimitIdToUse);
80
+ const dayAgo = moment(now).add(-1, "d");
81
+ if (item && item.LastSent && item.LastSent > dayAgo.valueOf()) {
82
+ log(
83
+ "sendEmail",
84
+ "RateLimit",
85
+ `Rate limit reached for ${rateLimitIdToUse}`,
86
+ logId
87
+ );
88
+ return { RateLimited: true };
89
+ }
90
+ } catch (e) {
91
+ // continue
92
+ }
93
+ }
40
94
 
41
95
  if (useTemplate) {
42
96
  content = `<div style="padding: 24px; padding-top: 0px; background-color: #f2f4f8;margin: 0px;">
@@ -48,28 +102,31 @@ module.exports = async (
48
102
  : `
49
103
  <div style='margin-top: 32px; border-top: 2px solid #e0e0e0;'></div>
50
104
  <div style='padding-top: 24px; display: block;'>
51
- <div style='clear: both; height: 30px;'>
52
- <img height="30" width="auto" src="${communityConfig.emailBrandingAbovePoweredBy.image}" style="float: left; height: 30px; width: auto; background-repeat: no-repeat; background-size: contain;"></img>
53
- </div>
105
+ <div style='clear: both; height: 30px;'>
106
+ <img height="30" width="auto" src="${communityConfig.emailBrandingAbovePoweredBy.image}" style="float: left; height: 30px; width: auto; background-repeat: no-repeat; background-size: contain;"></img>
107
+ </div>
54
108
  </div>
55
109
  `
56
110
  }
57
111
  </div>
58
112
  <div style='margin-top: 30px;'>
59
- <div style='margin: 0 auto; width: fit-content;'>
60
- <div style='vertical-align: top; height: 20px; margin-right: 10px; color: #828282; display: inline-block;'>
61
- <span style=' font-size: 13px; line-height: 20px;'>powered by</span>
62
- </div>
63
- <img height="20" width="142" src="https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:80aecdcb-9955-493e-a341-2f2263f64777/public/9c785b064c6abbec00bd4ef12b/logoplusscommunitieslong.png" style="display: inline-block; height: 20px; width: 142px; background-repeat: no-repeat; background-size:contain;"></img>
113
+ <div style='margin: 0 auto; width: fit-content;'>
114
+ <div style='vertical-align: top; height: 20px; margin-right: 10px; color: #828282; display: inline-block;'>
115
+ <span style=' font-size: 13px; line-height: 20px;'>powered by</span>
64
116
  </div>
117
+ <img height="20" width="142" src="https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:80aecdcb-9955-493e-a341-2f2263f64777/public/9c785b064c6abbec00bd4ef12b/logoplusscommunitieslong.png" style="display: inline-block; height: 20px; width: 142px; background-repeat: no-repeat; background-size:contain;"></img>
118
+ </div>
65
119
  </div>
66
120
  </div>`;
67
121
  }
68
122
 
123
+ const sesInfo = await getEmailServiceInfo(
124
+ serverlessConfig.key,
125
+ serverlessConfig.secret
126
+ );
127
+
69
128
  const mailOptions = {
70
- from:
71
- fromEmail ||
72
- `"${communityConfig.name}" noreply@${communityConfig.subdomain}.plusscommunities.com'`,
129
+ from: fromEmail || `${communityConfig.name} <${sesInfo.sender}>`,
73
130
  to: toEmail,
74
131
  bcc,
75
132
  subject,
@@ -77,16 +134,9 @@ module.exports = async (
77
134
  html: content,
78
135
  };
79
136
 
80
- const sesInfo = await getEmailServiceInfo(
81
- serverlessConfig.key,
82
- serverlessConfig.secret
83
- );
84
-
85
137
  if (sesInfo.enabled) {
86
138
  try {
87
- console.log("Using Amazon SES", mailOptions);
88
- mailOptions.from =
89
- fromEmail || `${communityConfig.name} <${sesInfo.sender}>`;
139
+ log("sendEmail", "SES", { toEmail, subject }, logId);
90
140
  const result = await sendEmail(
91
141
  mailOptions.from,
92
142
  mailOptions.to,
@@ -95,12 +145,19 @@ module.exports = async (
95
145
  serverlessConfig,
96
146
  mailOptions.bcc
97
147
  );
98
- console.log("Email success", toEmail, subject, result);
148
+ log("sendEmail", "Success", { toEmail, subject }, logId);
99
149
  } catch (error) {
100
- console.log("Email failed", toEmail, subject, error);
101
- await sendFallback(mailOptions, emailConfig);
150
+ log("sendEmail", "Error:SES", { toEmail, subject, error }, logId);
151
+ await sendFallback(mailOptions, emailConfig, accessConfig);
102
152
  }
103
153
  } else {
104
- await sendFallback(mailOptions, emailConfig);
154
+ await sendFallback(mailOptions, emailConfig, accessConfig);
155
+ }
156
+
157
+ if (rateLimitIdToUse) {
158
+ await updateRef("ratelimit", {
159
+ Id: rateLimitIdToUse,
160
+ LastSent: now.valueOf(),
161
+ });
105
162
  }
106
163
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-aws",
3
- "version": "2.0.20",
3
+ "version": "2.0.22",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "scripts": {
6
6
  "betapatch": "npm version prepatch --preid=beta",