зеркало из
1
0
Форкнуть 0
fxa-email-event-proxy/index.js

140 строки
3.7 KiB
JavaScript

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
'use strict'
const crypto = require('crypto')
const qs = require('qs')
const Promise = require('bluebird')
const sqs = require('sqs')
const { AUTH, SQS_SUFFIX, PROVIDER } = process.env
if (! AUTH || ! SQS_SUFFIX || ! PROVIDER) {
throw new Error('Missing config')
}
const ACCEPTED_PROVIDERS = [
'sendgrid',
'socketlabs'
]
if (! ACCEPTED_PROVIDERS.includes(PROVIDER)) {
throw new Error(`Only the following providers are supported: ${ACCEPTED_PROVIDERS.join(', ')}`)
}
const provider = require(`./${PROVIDER}`)
const AUTH_HASH = createHash(AUTH).split('')
const QUEUES = {
Bounce: `fxa-email-bounce-${SQS_SUFFIX}`,
Complaint: `fxa-email-complaint-${SQS_SUFFIX}`,
Delivery: `fxa-email-delivery-${SQS_SUFFIX}`
}
// env vars: SQS_ACCESS_KEY, SQS_SECRET_KEY, SQS_REGION
const SQS_CLIENT = sqs()
SQS_CLIENT.pushAsync = Promise.promisify(SQS_CLIENT.push)
module.exports = { main }
async function main (data) {
try {
// If there's a body, it's a request from the API gateway
if (data.body) {
// Requests from the API gateway must be authenticated
if (! data.queryStringParameters || ! authenticate(data.queryStringParameters.auth)) {
const errorResponse = {
error: 'Unauthorized',
errno: 999,
code: 401,
message: 'Request must provide a valid auth query param.'
}
return {
statusCode: 401,
body: JSON.stringify(errorResponse),
isBase64Encoded: false
}
}
if (data.headers && data.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
data = qs.parse(data.body)
} else {
data = JSON.parse(data.body)
}
}
if (! Array.isArray(data)) {
data = [ data ]
}
if (PROVIDER === 'socketlabs' && provider.shouldValidate(data[0])) {
return provider.validationResponse()
}
let results = await processEvents(data)
let response = {
result: `Processed ${results.length} events`
}
return {
statusCode: 200,
body: JSON.stringify(response),
isBase64Encoded: false
}
} catch(error) {
const errorResponse = {
error: 'Internal Server Error',
errno: 999,
code: 500,
message: error && error.message ? error.message : 'Unspecified error'
}
return {
statusCode: 500,
body: JSON.stringify(errorResponse),
isBase64Encoded: false
}
}
}
function authenticate (auth) {
const authHash = createHash(auth)
return AUTH_HASH
.reduce((equal, char, index) => equal && char === authHash[index], true)
}
function createHash (value) {
const hash = crypto.createHash('sha256')
hash.update(value)
return hash.digest('base64')
}
async function processEvents (events) {
return Promise.all(
events.map(provider.marshallEvent)
.filter(event => !! event)
.map(sendEvent)
)
}
function sendEvent (event) {
// The message we send to SQS has to have this structure to
// mimic exactly the message SNS sends to it in the SES -> SNS -> SQS flow.
// See documentation: https://docs.aws.amazon.com/sns/latest/dg/SendMessageToSQS.html
return SQS_CLIENT.pushAsync(
QUEUES[event.notificationType],
{
Message: JSON.stringify(event),
Type: 'Notification',
Timestamp: event.mail && event.mail.timestamp ? event.mail.timestamp : new Date().toISOString()
}
)
.then(() => console.log('Sent:', event.notificationType))
.catch(error => {
console.error('Failed to send event:', event)
console.error(error.stack)
throw error
})
}