Skip to content
This repository was archived by the owner on Oct 24, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/scripts/aws/marketplace/startup_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ sudo -u dshop yarn build:dist
cd $BACKEND_PATH
truncate -s 0 .env

# Env var indicating it is a AWS deployment.
echo "AWS_MARKETPLACE_DEPLOYMENT=true" >> .env
# Env var indicating it is a AWS deployment. Append this to the env files in both the 'backend' and 'shop' folders
echo "AWS_MARKETPLACE_DEPLOYMENT=true" | tee -a .env ../shop/.env

# Encryption key for storing data securely in the DB.
echo "ENCRYPTION_KEY=`openssl rand -base64 48`" >> .env
Expand Down
4 changes: 3 additions & 1 deletion backend/utils/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ const {
IPFS_GATEWAY, // IPFS gateway override
BUCKET_PREFIX = DEFAULT_BUCKET_PREFIX,
SERVICE_PREFIX = DEFAULT_SERVICE_PREFIX,
EXTERNAL_IP
EXTERNAL_IP,
AWS_MARKETPLACE_DEPLOYMENT //bool indicating whether the app is running from an AWS EC2 instance launched via the marketplace
} = process.env

/**
Expand Down Expand Up @@ -128,6 +129,7 @@ module.exports = {
BUCKET_PREFIX,
SERVICE_PREFIX,
EXTERNAL_IP,
AWS_MARKETPLACE_DEPLOYMENT,
EXTERNAL_IP_SERVICE_URL,
DEFAULT_INFRA_RESOURCES,
DEFAULT_AWS_REGION,
Expand Down
26 changes: 19 additions & 7 deletions backend/utils/emails/_getTransport.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const nodemailer = require('nodemailer')
const aws = require('aws-sdk')
// const { IS_TEST } = require('../const')
const { AWS_MARKETPLACE_DEPLOYMENT, DEFAULT_AWS_REGION } = require('../const')
const encConf = require('../encryptedConfig')

function getTransportFromConfig(config) {
Expand All @@ -24,12 +24,24 @@ function getTransportFromConfig(config) {
}
})
} else if (config.email === 'aws') {
const SES = new aws.SES({
apiVersion: '2010-12-01',
region: config.awsRegion,
accessKeyId: config.awsAccessKey,
secretAccessKey: config.awsAccessSecret
})
let SES

if (AWS_MARKETPLACE_DEPLOYMENT) {
//Use the credentials from the EC2 Instance metadata
SES = new aws.SES({
apiVersion: '2010-12-01',
region: DEFAULT_AWS_REGION
})
} else {
//Look up the shop admin's AWS credentials from 'config'
SES = new aws.SES({
apiVersion: '2010-12-01',
region: config.awsRegion,
accessKeyId: config.awsAccessKey,
secretAccessKey: config.awsAccessSecret
})
}

return nodemailer.createTransport({ SES })
}
}
Expand Down
4 changes: 2 additions & 2 deletions backend/utils/emails/newOrder.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ async function sendNewOrderEmail({
...varsOverride
}

const htmlOutputVendor = mjml2html(vendor(vars), { minify: true })
const htmlOutput = mjml2html(email(vars), { minify: true })
const htmlOutputVendor = mjml2html(vendor(vars))
const htmlOutput = mjml2html(email(vars))
const txtOutput = emailTxt(vars)

const message = {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/emails/passwordReset.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function sendPasswordResetEmail({ network, seller, verifyUrl, skip }) {
fromEmail: from
}

const htmlOutput = mjml2html(forgotPassEmail(vars), { minify: true })
const htmlOutput = mjml2html(forgotPassEmail(vars))
const txtOutput = forgotPassEmailTxt(vars)

const message = {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/emails/printfulOrderFailed.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async function sendPrintfulOrderFailedEmail(shopId, orderData, opts, skip) {
fromEmail: from
}

const htmlOutput = mjml2html(printfulOrderFailed(vars), { minify: true })
const htmlOutput = mjml2html(printfulOrderFailed(vars))
const txtOutput = printfulOrderFailedTxt(vars)

const message = {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/emails/sellerContact.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function sellerContactEmail({ network, seller, data, skip }) {
fromEmail: from
}

const htmlOutput = mjml2html(sellerContact(vars), { minify: true })
const htmlOutput = mjml2html(sellerContact(vars))
const txtOutput = sellerContactTxt(vars)

const message = {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/emails/stripeWebhookError.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async function stripeWebhookErrorEmail(shopId, errorData, skip) {
...errorData
}

const htmlOutput = mjml2html(stripeWebhookError(vars), { minify: true })
const htmlOutput = mjml2html(stripeWebhookError(vars))
const txtOutput = stripeWebhookErrorTxt(vars)

const message = {
Expand Down
2 changes: 1 addition & 1 deletion backend/utils/emails/verifyEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function sendVerifyEmail({ network, seller, verifyUrl, skip }) {
const { name, email } = seller
const vars = { head, name, verifyUrl, fromEmail: from }

const htmlOutput = mjml2html(verifyEmail(vars), { minify: true })
const htmlOutput = mjml2html(verifyEmail(vars))
const txtOutput = verifyEmailTxt(vars)

const message = {
Expand Down
3 changes: 2 additions & 1 deletion shop/src/pages/admin/settings/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ const AdminSettings = ({ shop }) => {
</div>
</>
)}
{state.email !== 'aws' ? null : (
{state.email !== 'aws' &&
!process.env.AWS_MARKETPLACE_DEPLOYMENT ? null : (
<>
<div className="form-group">
<label>AWS Region</label>
Expand Down
22 changes: 21 additions & 1 deletion shop/src/pages/admin/settings/apps/List.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react'
import fbt, { FbtParam } from 'fbt'

import useShopConfig from 'utils/useShopConfig'
import useBackendApi from 'utils/useBackendApi'
import useEmailAppsList from 'utils/useEmailAppsList'
import maskSecret from 'utils/maskSecret'

Expand All @@ -19,6 +20,7 @@ const AppSettings = () => {
const { shopConfig, refetch } = useShopConfig()
const [connectModal, setShowConnectModal] = useState(false)
const { emailAppsList } = useEmailAppsList({ shopConfig })
const { post } = useBackendApi({ authToken: true })

const appsList = useMemo(() => {
if (!shopConfig) return []
Expand Down Expand Up @@ -69,7 +71,25 @@ const AppSettings = () => {
<button
className="btn btn-outline-primary px-4"
type="button"
onClick={() => setShowConnectModal(processor.id)}
onClick={async () => {
if (
process.env.AWS_MARKETPLACE_DEPLOYMENT &&
processor.id == 'aws'
) {
try {
await post('/shop/config', {
method: 'PUT',
body: JSON.stringify({ email: 'aws' }),
suppressError: true
})
} catch (err) {
console.error(err)
}
refetch()
} else {
setShowConnectModal(processor.id)
}
}}
>
<fbt desc="Connect">Connect</fbt>
</button>
Expand Down
75 changes: 56 additions & 19 deletions shop/src/pages/super-admin/networks/_Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,32 @@ const NetworkForm = ({ onSave, network, feedback, className }) => {
shopConfig: state.fallbackShopConfig
})

/*
* The Networks form should ask the shop admin for their AWS Credentials only when the DShop DApp is not deployed on an EC2 instance launched via AWS Marketplace.
* Reason: When DShop is deployed with the help of the Marketplace solution, the shop admin's AWS credentials can be obtained programatically.
* Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#instance-metadata-security-credentials
*/
const conditionallyRequestAWSCreds = () => {
if (process.env.AWS_MARKETPLACE_DEPLOYMENT) {
return
} else {
return (
<div className="form-row">
<div className="form-group col-md-6">
<label>AWS Access Key ID</label>
<input {...input('awsAccessKeyId')} />
{Feedback('awsAccessKeyId')}
</div>
<div className="form-group col-md-6">
<label>AWS Secret Access Key</label>
<PasswordField field="awsSecretAccessKey" input={input} />
{Feedback('awsSecretAccessKey')}
</div>
</div>
)
}
}

const ProcessorIdToEmailComp = {
sendgrid: SendgridModal,
aws: AWSModal,
Expand Down Expand Up @@ -229,7 +255,21 @@ const NetworkForm = ({ onSave, network, feedback, className }) => {
<button
className="btn btn-outline-primary mr-2"
type="button"
onClick={() => setConfigureEmailModal(processor.id)}
onClick={async () => {
if (
process.env.AWS_MARKETPLACE_DEPLOYMENT &&
processor.id == 'aws'
) {
setState({
fallbackShopConfig: {
...state.fallbackShopConfig,
email: 'aws'
}
})
} else {
setConfigureEmailModal(processor.id)
}
}}
>
Connect
</button>
Expand All @@ -244,13 +284,21 @@ const NetworkForm = ({ onSave, network, feedback, className }) => {
const selection = Object.keys(newState.infra).filter(
(k) => newState.infra[k]
)
const validRes = validateSelection({ networkConfig: newState, selection })

if (!validRes.success) {
setState({ ...stateUpdate, infraErrors: validRes.errors })
return false
const awsResources = ['aws-files', 'aws-cdn', 'aws-dns', 'aws-email']

//Validate selection only if it is something other than an AWS Resource, and DShop is not running on Amazon EC2.
//Reason: If DShop is running on EC2, it can connect to an[other] AWS resource automatically
if (
!awsResources.includes(selection) &&
!process.env.AWS_MARKETPLACE_DEPLOYMENT
) {
const validRes = validateSelection({ networkConfig: newState, selection })

if (!validRes.success) {
setState({ ...stateUpdate, infraErrors: validRes.errors })
return false
}
}

setState({ ...stateUpdate, infraErrors: [] })
return true
}
Expand Down Expand Up @@ -408,18 +456,7 @@ const NetworkForm = ({ onSave, network, feedback, className }) => {
{Feedback('gcpCredentials')}
</div>
</div>
<div className="form-row">
<div className="form-group col-md-6">
<label>AWS Access Key ID</label>
<input {...input('awsAccessKeyId')} />
{Feedback('awsAccessKeyId')}
</div>
<div className="form-group col-md-6">
<label>AWS Secret Access Key</label>
<PasswordField field="awsSecretAccessKey" input={input} />
{Feedback('awsSecretAccessKey')}
</div>
</div>
{conditionallyRequestAWSCreds()}
<div className="form-row">
<div className="form-group col-md-6">
<label>Discord Webhook</label>
Expand Down
7 changes: 4 additions & 3 deletions shop/src/utils/useEmailAppsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ const useEmailAppsList = ({ shopConfig }) => {
{
id: 'aws',
title: 'AWS SES',
description: awsEnabled
? `AWS SES Access Key: ${maskSecret(awsAccessKey, 12)}`
: 'Send emails using AWS SES',
description:
awsEnabled && !process.env.AWS_MARKETPLACE_DEPLOYMENT
? `AWS SES Access Key: ${maskSecret(awsAccessKey, 12)}`
: 'Send emails using AWS SES',
icon: <img src="images/aws-ses.png" width="60%" />,
enabled: awsEnabled
},
Expand Down
3 changes: 2 additions & 1 deletion shop/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ const webpackConfig = {
DATA_DIR: process.env.DATA_DIR || '',
CONTENT_CDN: process.env.CONTENT_CDN || '',
CONTENT_HASH: process.env.CONTENT_HASH || '',
ABSOLUTE: process.env.ABSOLUTE || ''
ABSOLUTE: process.env.ABSOLUTE || '',
AWS_MARKETPLACE_DEPLOYMENT: null //use null by default. Otherwise, use the value for process.env.AWS_MARKETPLACE_DEPLOYMENT
}),
new MiniCssExtractPlugin({ filename: '[name].[contenthash:8].css' })
],
Expand Down