From b97501242b570977b033376e1108b285571a2b40 Mon Sep 17 00:00:00 2001 From: Pei-Yi Lin Date: Wed, 2 Apr 2025 17:26:35 -0400 Subject: [PATCH 1/4] email - Capitalize product name in email subject and body --- modules/email.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/email.js b/modules/email.js index c593055..6b66569 100644 --- a/modules/email.js +++ b/modules/email.js @@ -228,7 +228,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => `
- ${product.toUpperCase()} (${company}) Login Magic Link + ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company}) Login Magic Link
@@ -259,7 +259,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => ` @@ -298,7 +298,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => `
-

Welcome to ${product.toUpperCase()} (${company})

+

Welcome to ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company})


-

Having an issue? Contact Us

+

Having an issue? Contact Us

@@ -321,7 +321,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => ` ` module.exports.otpText = ({ link, otp, ttl, company, product }) => ` - Welcome to ${product.toUpperCase()} (${company})\n + Welcome to ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company})\n ${link ? `Please login with the magic link ${link}\n` : ''} ${otp && ttl ? `Or manually enter: ${otp} \n This will expire after ${ttl}, and all previous email should be discarded.` : ''} From c586631ce34e7894cef499cac14e4ddb28a7f025 Mon Sep 17 00:00:00 2001 From: Pei-Yi Lin Date: Wed, 2 Apr 2025 17:43:16 -0400 Subject: [PATCH 2/4] email - Add customizable sender for clearlake --- modules/auth.js | 13 ++++++++----- serverless.yml | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/auth.js b/modules/auth.js index 2bb7740..52362d1 100644 --- a/modules/auth.js +++ b/modules/auth.js @@ -12,7 +12,7 @@ const { sendMail, magicLinkHTML, otpText } = require('./email.js') const { updateUser, selectUser, getUserWL } = require('./db') const { claimOTP, redeemOTP } = require('./auth-otp') const { AuthorizationError, APIError, LOG_LEVEL_ERROR } = require('./errors') -const { PREFIX_APP_REVIEWER, PREFIX_DEV, PREFIX_MOBILE_SDK, PRODUCT_ATOM, PRODUCT_LOCUS } = require('../constants.js') +const { PREFIX_APP_REVIEWER, PREFIX_DEV, PREFIX_MOBILE_SDK, PRODUCT_ATOM, PRODUCT_LOCUS, PRODUCT_CLEARLAKE } = require('../constants.js') const { @@ -117,10 +117,13 @@ const loginUser = async ({ user, redirect, zone='utc', product = PRODUCT_ATOM, n // get user WL info const { rows = [] } = await getUserWL(user) + const DEFAULT_SENDER = 'dev@eqworks.com' + const productSender = product === PRODUCT_CLEARLAKE + ? (process.env.CLEARLAKE_SENDER || DEFAULT_SENDER) + : DEFAULT_SENDER + // TODO: add logo in when email template has logo - let { sender, company } = rows[0] || {} - sender = sender || 'dev@eqworks.com' - company = company || 'EQ Works' + const { sender = productSender, company = 'EQ Works' } = rows[0] || {} const { prefix: userPrefix, api_access } = await getUserInfo({ email: user }) // Check if user has access to the requested product @@ -164,7 +167,7 @@ const loginUser = async ({ user, redirect, zone='utc', product = PRODUCT_ATOM, n return sendMail({ from: sender, to: user, - subject: `${product} (${company}) Login`, + subject: `${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company}) Login`, ...message, }) } diff --git a/serverless.yml b/serverless.yml index c7eb547..173a762 100644 --- a/serverless.yml +++ b/serverless.yml @@ -32,6 +32,7 @@ provider: OTP_TTL: ${env:OTP_TTL} KEYWARDEN_VER: ${env:KEYWARDEN_VER} STAGE: ${opt:stage, self:provider.stage} + CLEARLAKE_SENDER: ${env:CLEARLAKE_SENDER} REDIS_URI: # cloudformation functions to form redis://: "Fn::Join": [ From 4072d57cc60675154252d651ab53114cb9296b64 Mon Sep 17 00:00:00 2001 From: Pei-Yi Lin Date: Tue, 8 Apr 2025 11:49:12 -0400 Subject: [PATCH 3/4] email - make support email configurable in magic link emails --- modules/auth.js | 11 +++++++---- modules/email.js | 4 ++-- serverless.yml | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/auth.js b/modules/auth.js index 52362d1..ebb40ea 100644 --- a/modules/auth.js +++ b/modules/auth.js @@ -117,10 +117,13 @@ const loginUser = async ({ user, redirect, zone='utc', product = PRODUCT_ATOM, n // get user WL info const { rows = [] } = await getUserWL(user) - const DEFAULT_SENDER = 'dev@eqworks.com' + const DEFAULT_EMAIL = 'dev@eqworks.com' const productSender = product === PRODUCT_CLEARLAKE - ? (process.env.CLEARLAKE_SENDER || DEFAULT_SENDER) - : DEFAULT_SENDER + ? (process.env.CLEARLAKE_SENDER || DEFAULT_EMAIL) + : DEFAULT_EMAIL + const supportEmail = product === PRODUCT_CLEARLAKE + ? (process.env.CLEARLAKE_SUPPORT_EMAIL || DEFAULT_EMAIL) + : DEFAULT_EMAIL // TODO: add logo in when email template has logo const { sender = productSender, company = 'EQ Works' } = rows[0] || {} @@ -162,7 +165,7 @@ const loginUser = async ({ user, redirect, zone='utc', product = PRODUCT_ATOM, n text: otpText({ otp, ttl, company, product }), } : { text: otpText({ link, otp, ttl, company, product }), - html: magicLinkHTML({ link, otp, ttl, company, product }), + html: magicLinkHTML({ link, otp, ttl, company, product, supportEmail }), } return sendMail({ from: sender, diff --git a/modules/email.js b/modules/email.js index 6b66569..0453463 100644 --- a/modules/email.js +++ b/modules/email.js @@ -27,7 +27,7 @@ module.exports.sendMail = async message => { return transport.sendMail(message) } -module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => ` +module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail }) => ` @@ -298,7 +298,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product }) => `
-

Having an issue? Contact Us

+

Having an issue? Contact Us

diff --git a/serverless.yml b/serverless.yml index 173a762..d9f1521 100644 --- a/serverless.yml +++ b/serverless.yml @@ -33,6 +33,7 @@ provider: KEYWARDEN_VER: ${env:KEYWARDEN_VER} STAGE: ${opt:stage, self:provider.stage} CLEARLAKE_SENDER: ${env:CLEARLAKE_SENDER} + CLEARLAKE_SUPPORT_EMAIL: ${env:CLEARLAKE_SUPPORT_EMAIL} REDIS_URI: # cloudformation functions to form redis://: "Fn::Join": [ From 8ea0a8b11181d8fcbb8326f065978b1e6bc8111b Mon Sep 17 00:00:00 2001 From: Pei-Yi Lin Date: Tue, 8 Apr 2025 12:59:25 -0400 Subject: [PATCH 4/4] refactor - extract string capitalization logic into utility function --- modules/auth.js | 3 ++- modules/email.js | 11 ++++++----- modules/utils.js | 13 +++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 modules/utils.js diff --git a/modules/auth.js b/modules/auth.js index ebb40ea..b571f3c 100644 --- a/modules/auth.js +++ b/modules/auth.js @@ -13,6 +13,7 @@ const { updateUser, selectUser, getUserWL } = require('./db') const { claimOTP, redeemOTP } = require('./auth-otp') const { AuthorizationError, APIError, LOG_LEVEL_ERROR } = require('./errors') const { PREFIX_APP_REVIEWER, PREFIX_DEV, PREFIX_MOBILE_SDK, PRODUCT_ATOM, PRODUCT_LOCUS, PRODUCT_CLEARLAKE } = require('../constants.js') +const { capitalizeFirstLetter } = require('./utils') const { @@ -170,7 +171,7 @@ const loginUser = async ({ user, redirect, zone='utc', product = PRODUCT_ATOM, n return sendMail({ from: sender, to: user, - subject: `${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company}) Login`, + subject: `${capitalizeFirstLetter(product)} (${company}) Login`, ...message, }) } diff --git a/modules/email.js b/modules/email.js index 0453463..18c46a8 100644 --- a/modules/email.js +++ b/modules/email.js @@ -1,5 +1,6 @@ const nodemailer = require('nodemailer') const AWS = require('@aws-sdk/client-ses') +const { capitalizeFirstLetter } = require('./utils') module.exports.sendMail = async message => { @@ -35,7 +36,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail - + ${capitalizeFirstLetter(product)} (${company}) Login Magic Link @@ -228,7 +229,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail
- ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company}) Login Magic Link + ${capitalizeFirstLetter(product)} (${company}) Login Magic Link
@@ -259,7 +260,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail @@ -298,7 +299,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail
-

Welcome to ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company})

+

Welcome to ${capitalizeFirstLetter(product)} (${company})


-

Having an issue? Contact Us

+

Having an issue? Contact Us

@@ -321,7 +322,7 @@ module.exports.magicLinkHTML = ({ link, otp, ttl, company, product, supportEmail ` module.exports.otpText = ({ link, otp, ttl, company, product }) => ` - Welcome to ${product[0].toUpperCase() + product.slice(1).toLowerCase()} (${company})\n + Welcome to ${capitalizeFirstLetter(product)} (${company})\n ${link ? `Please login with the magic link ${link}\n` : ''} ${otp && ttl ? `Or manually enter: ${otp} \n This will expire after ${ttl}, and all previous email should be discarded.` : ''} diff --git a/modules/utils.js b/modules/utils.js new file mode 100644 index 0000000..dce2a86 --- /dev/null +++ b/modules/utils.js @@ -0,0 +1,13 @@ +/** + * Capitalizes the first letter of a string and makes the rest lowercase + * @param {string} str - The string to capitalize + * @returns {string} The capitalized string + */ +function capitalizeFirstLetter(str) { + if (!str) return '' + return str[0].toUpperCase() + str.slice(1).toLowerCase() +} + +module.exports = { + capitalizeFirstLetter, +}