Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to protect AWS API gateway URL from unauthorized access?

I want to host my static website in AWS S3. But it contains "contact us" page which is for sending emails. When the user clicks the submit button it sends the request to AWS API gateway, API gateway triggers the lambda function; the lambda function sends the mail to Admin.

But it has some problem . when we host the website at server we can use captcha to prevent the submit request automation and fraudulent activities . in this case the anybody can misuse (sending more request by passing a query string in the URL ) my API gateway URL who know it.

So my question is how to know that the submit request is requested from my website only and is there any way to use instead of Captcha?.

like image 700
Arun Kumar Avatar asked Oct 28 '22 22:10

Arun Kumar


1 Answers

Validate reCAPTCHA using API Gateway and Lambda

For Contact Forms on serverless SPA sites on S3 static sites, I use AngularJS with the "angular-recaptcha" service (https://github.com/VividCortex/angular-recaptcha), though you can use any way of getting and POSTing the recaptcha value.

On the Backend you can use API Gateway which calls a Lambda function to validate the recaptcha value and do something. Using NodeJS and Recaptcha2 (https://www.npmjs.com/package/recaptcha2) to validate the token.

var reCAPTCHA = require('recaptcha2')

module.exports.sendemail = (event, context, callback) => {

    // parse the data that was sent from API Gateway
    var eventData = JSON.parse(event.body);

    // Prepare the recaptcha connection to Google
    var recaptcha = new reCAPTCHA({
      siteKey: process.env.RECAPTCHA_KEY,
      secretKey: process.env.RECAPTCHA_SECRET
    })

    // Validate the recaptcha value
    recaptcha.validate(eventData.recaptcha)
        .then(function(){
            // validated ok
            console.log("ReCaptcha Valid")


            ... DO STUFF HERE ...


        })
        .catch(function(errorCodes){

            // invalid recaptcha
            console.log("ReCaptcha Not Valid")

            // translate error codes to human readable text
            console.log(recaptcha.translateErrors(errorCodes));

            // send a fail message with cors headers back to the UI
            var response = {
                statusCode: 500,
                headers: {
                    "Access-Control-Allow-Origin" : "*",
                    "Access-Control-Allow-Credentials" : true
                },
                body: JSON.stringify({"message":"Error: Invalid Recaptcha"})
            }
            callback(null, response);
        });
};

Example Templated Emails using Handlebars and SES

As a bonus, here is some code I reuse for sending emails using html/text templates:

  • Handlebars to inject values to the template SES to send the email.
  • Make sure that you have set up SES with your domain name, and permissioned the Lambda to "ses:SendEmail" and "ses:SendEmailRaw".
  • Add the environment variables when deploying the function, this makes the code reusable and keeps the secrets out of the source code.
  • I highly recommend using the Serverless Framework to deploy your serverless applications. https://serverless.com

Works a treat for me :)

'use strict';

var AWS = require('aws-sdk');
var ses = new AWS.SES();
var reCAPTCHA = require('recaptcha2')
var fs = require('fs');
var Handlebars = require('handlebars');

module.exports.sendemail = (event, context, callback) => {

    // parse the data that was sent from API Gateway
    var eventData = JSON.parse(event.body);

    // Prepare the recaptcha connection to Google
    var recaptcha = new reCAPTCHA({
      siteKey: process.env.RECAPTCHA_KEY,
      secretKey: process.env.RECAPTCHA_SECRET
    })

    // Validate the recaptcha value
    recaptcha.validate(eventData.recaptcha)
        .then(function(){
            // validated ok
            console.log("reCAPTCHA Valid")

            // Read the HTML template from the package root
            fs.readFile('./contact/email_template.html', function (err, emailHtmlTemplate) {
                if (err) {
                    console.log("Unable to load HTML Template");
                    throw err;
                }
                // Read the TEXT template from the package root
                fs.readFile('./contact/email_template.txt', function (err, emailTextTemplate) {
                    if (err) {
                        console.log("Unable to load TEXT Template");
                        throw err;
                    }

                    // Gather data to be injected to the templates
                    var emailData = {
                        "websiteaddress": process.env.WEBSITEADDRESS,
                        "websitename": process.env.WEBSITENAME,
                        "content": null,
                        "email": process.env.EMAIL_TO,
                        "event": eventData
                    };

                    // Use Handlebars to compile the template and inject values into the title (used in subject and body of email)
                    var templateTitle = Handlebars.compile(process.env.EMAIL_TITLE);
                    var titleText = templateTitle(emailData);
                    console.log(titleText);

                    // Add title to the values object
                    emailData.title = titleText;

                    // Use Handlebars to compile email plaintext body
                    var templateText = Handlebars.compile(emailTextTemplate.toString());
                    var bodyText = templateText(emailData);
                    console.log(bodyText);

                    // Use Handlebars to compile email html body
                    var templateHtml = Handlebars.compile(emailHtmlTemplate.toString());
                    var bodyHtml = templateHtml(emailData);
                    console.log(bodyHtml);

                    // Prepare the SES payload
                    var params = {
                        Destination: {
                            ToAddresses: [
                                process.env.EMAIL_TO
                            ]
                        },
                        Message: {
                            Body: {
                                Text: {
                                    Data: bodyText,
                                    Charset: 'UTF-8'
                                },
                                Html: {
                                    Data: bodyHtml
                                },
                            },
                            Subject: {
                                Data: titleText,
                                Charset: 'UTF-8'
                            }
                        },
                        Source: process.env.EMAIL_FROM
                    }
                    console.log(JSON.stringify(params,null,4));

                    // Send SES Email
                    ses.sendEmail(params, function(err,data){
                        if(err) {
                            console.log(err,err.stack); // error

                            // Handle SES send errors
                            var response = {
                                statusCode: 500,
                                headers: {
                                    "Access-Control-Allow-Origin" : "*",
                                    "Access-Control-Allow-Credentials" : true
                                },
                                body: JSON.stringify({"message":"Error: Unable to Send Message"})
                            }
                            callback(null, response);
                        }
                        else {
                            console.log(data); // success

                            // SES send was successful
                            var response = {
                                statusCode: 200,
                                headers: {
                                    "Access-Control-Allow-Origin" : "*",
                                    "Access-Control-Allow-Credentials" : true
                                },
                                body: JSON.stringify({"message":"Message Sent"})
                            }
                            callback(null, response);
                        }
                  });

              }); //end of load text template
          }); //end of load html template


      })
      .catch(function(errorCodes){

          // invalid recaptcha
          console.log("reCAPTCHA Not Valid")

          // translate error codes to human readable text
          console.log(recaptcha.translateErrors(errorCodes));

          // send a fail message with cors headers back to the UI
          var response = {
              statusCode: 500,
              headers: {
                  "Access-Control-Allow-Origin" : "*",
                  "Access-Control-Allow-Credentials" : true
              },
              body: JSON.stringify({"message":"Error: Invalid Recaptcha"})
          }
          callback(null, response);
     });
 };

Edit: fixed syntax error

like image 73
Matt D Avatar answered Nov 15 '22 08:11

Matt D