Using AWS API Gateway to POST email via AWS SES - amazon-web-services

I'm setting up an API from an App which allows the App user to contact someone for follow-up, but hides the email address from the sender (and allows it to be changed without a new release of the App).
I've managed to setup an AWS API Gateway GET method which sends email directly via SES
https://...aws.com/em/send/en?body=The+email+is+here
Using a path override of
Action=SendEmail
&Source=source%40mydomain.org
&Destination.ToAddresses.member.1=follow.up%40anydomain.com
&Message.Subject.Data=Request+For+Followup
&Message.Body.Text.Data={body}
I would much prefer to use a POST method - but am really struggling to work out what should go in the Action parameter and how to create the mapping template - or even if it is possible to create an application/x-www-form-urlencoded request without having to resort to a lambda function - although the AWS documentation does include a $util.urlEncode() function.
http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
Edit:
I am trying to use a POST method to AWS Gateway and a POST to SES with the contents in the request body, but it is just not clear to me how to create the mapping template and I can't find any examples for SES.
Testing it with the mapping template (as per the example in the documentation)
Source=source%40mydomain.org
&Destination.ToAddresses.member.1=follow.up%40anydomain.com
&Message.Subject.Data=Request+For+Followup
&Message.Body.Text.Data=The+message+goes+here
And a content type application/x-www-form-urlencoded AWS gateway gives the error:
{
"message": "Unsupported Media Type"
}
If I use a mapping template
{
"Action":"SendEmail",
"Source":"source#mydomain.org",
"Destination":{
"ToAddresses":{
"member":["follow.up#anydomain.com"]
}
},
"Message":{
"Subject": {
"Data":"Request For Followup"
},
"Body":{
"Text":{
"Data":"The message goes here"
}
}
}
}
and a content type of application/json AWS gateway gives the error
{
"Output": {
"__type": "com.amazon.coral.service#UnknownOperationException",
"message": null
},
"Version": "1.0"
}

Your mapping template configuration must be in a POST format, not in the JSON, since SES service doesn't support json.
For example, this config has worked out for me:
Action=SendEmail&
Message.Body.Text.Data=Some+text&
Message.Subject.Data=New+message&
ReturnPath=some#mail.io
&Destination.ToAddresses.member.1=some#destination.com
&Source=your#sender.email
Also, your path override must be SendEmail only for the case above

Related

How to set the api version being called in API Gateway when integrating with AWS Service CloudWatch?

I get the following error message when calling actions for CloudWatch in API Gateway.
"Error": {
"Code": "InvalidAction",
"Message": "Could not find operation DescribeAlarms for version 2009-05-15",
"Type": "Sender"
}
I've been using DescribeAlarms for testing. My setup is as follows.
Integration Type = AWS Service
AWS Service = CloudWatch
HTTP method = POST
Action = DescribeAlarms
The error references the API Version 2009-05-15, which only has ListMetrics and GetMetricStatistics according to it's documentation on page 54. ListMetrics does indeed work as expected with my setup.
The current version is 2010-08-01 but I don't see anyway to reference that in API Gateway. In an example of a POST request in the documentation it shows a header labeled x-amz-target with a value of GraniteServiceVersion20100801.API_Name.
My interpretation is I can put Name = x-amz-target and value 'GraniteServiceVersion20100801.DescribeAlarms' in my http header for the Integration Request in API Gateway.
This doesn't change the response and gives the same error message.
I also used the --debug in CLI when calling describe-alarms, and in the body it shows...
"body": {
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
So I also set http headers to include Content-Type with a value of 'application/x-amz-json-1.1' and then put in
{
"Action":"DescribeAlarms",
"Version":"2010-08-01"
}
but nothing changed with that either.
Any help or guidance would be greatly appreciated.
Under Method Integration -> URL Query String Parameters
I added Version as the Name and '2010-08-01' under Mapped From.
All actions are now working as expected.
I'm trying to PutMetrics directly from Api Gateway -> Cloudwatch using PutMetricData, Version in the query string params didn't work for me.
These 3 HTTP headers in the Integration Request solved it for me:
Content-Type 'application/json'
X-Amz-Target 'GraniteServiceVersion20100801.PutMetricData'
Content-Encoding 'amz-1.0'

How can an aws lambda know what endpoint called it from API Gateway?

If two different endpoints use one lambda, how can the lambda know about the parts of the URL path?
How can one lambda know it was called from /zips vs /zip?zip_code=02140 ?
I can use event["queryStringParameters"]['zip_Code'] to get the URLs query string - /zip?zip_code=02140 - from within the lambda,
but how can I know if I am called from the /zips endpoint?
I tried using event["pathStringParameters"]['zips'] which I created a test event for but that didn't work, not recognized.
I can use one lambda per specific resource but I'd like to also know other approaches and how those that use the same endpoint can have their path revealed.
If I am following what you're asking for, namely that you have one Lambda function servicing two API Gateway endpoints, then I think you have two options:
Use the path parameter
Set a custom header and check that in headers
From the AWS documentation:
In Lambda proxy integration, API Gateway maps the entire client request to the input event parameter of the backend Lambda function as follows:
So given this HTTP request:
POST /testStage/hello/world?name=me HTTP/1.1
Host: gy415nuibc.execute-api.us-east-1.amazonaws.com
Content-Type: application/json
headerName: headerValue
{
"a": 1
}
You'll have available:
"message": "Hello me!",
"input": {
"resource": "/{proxy+}",
"path": "/hello/world",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
...
Here both path and headers will serve your need.
Personally, I would recommend setting a custom header. That way, no matter if your API routing changes, your Lambda will still pick it up.
You can get the path that invoked your Lambda in the event object, under event["requestContext"]["path"]
You can see more details on what the event object contains in the documentation for using AWS Lambda with API Gateway

Woocommerce webhook to AWS API-Gateway : CORS/Header issue

I am trying to send a Woocommerce webhook to AWS API Gateway. When i put in my API Gateway URL on Amazon I get the following error:
Error: Delivery URL returned response code: 415
I think this is related to headers, there is an option to Create CORS in API-Gateway that I have now done. Which then adds an OPTION method but I still get undefined in Cloudwatch
I created a POST method and used the mapping template below with application/json and the setting When there are no templates defined (recommended)
{
"body" : $input.json('$'),
"headers": {
#foreach($header in $input.params().header.keySet())
"$header": "$util.escapeJavaScript($input.params().header.get($header))" #if ($foreach.hasNext), #end
#end
}
}
Here is a mini Lambda Node function that just outputs a Woocommerce order number to the console and shows in Cloudwatch whether the API passthrough is working.
exports.handler = (event, context, callback, err) => {
//callback(null, event.meta_data);
if (err) console.log('JSON Pass Fail'); // an error occurred
else console.log(event.order_key); // successful response
};
If anyone did want to have a go at re-creating this, you can knock up a quick Wordpress install on cPanel and install the Woocommerce Plugin. Setup a dummy product and then just use the Cash on Delivery as a payment method to get your "Order Created" webhooks firing. Only takes 2 minutes.
You can use https://requestbin.com/ or webhook.site to test out webhook outputs.
Can anyone help sorting out the Headers so i can pass a Woocommerce payload to API Gateway?

API Gateway and Swagger UI

I currently have an API with multiple resources within our API Gateway, I'm working on a a Swagger UI page for the same.
A lot of the swagger ui definition I have written myself, although I know there is export functionality - it doesn't seem 100% ready yet.
When using the Try it out button from swagger, if I get back a 200, this is handled perfectly and the result shows as expected. I am trying to get the error codes to display and this is where I am stuck.
In API Gateway I have create a 401 in my Method response, including some allowed headers, this is being used in my Integration Response for HTTP status regex of Unauthorized.* - the content of this is just being passed through.
My swagger UI definition response looks like this (for the 401 in particular):
"401": {
"description": "Unauthorized",
"headers": {
"Access-Control-Allow-Origin": {
"type": "string"
}
}
},
This is just expecting a string response. My `produces' has everything needed, just in case;
"produces": ["application/json",
"text/json",
"application/xml",
"text/xml"],
The result through the inspector is correct -
However my swagger still produces this:
I have tried a multitude of things, including assigning an object to the response type, changing my regex expression to 4\d{2} to catch any 400 error, updated the definition produces - all with no luck.
Let me know if there is any other information needed to help.
I'm assuming you're using a custom authorizer or Cognito authorizer and this is what is producing the 401?
Unfortunately there is a known limitation where error methods, such as 401 or 403 from an authorizer, will not contain configured header mappings.

Is it possible to add an HTTP header from AWS Custom Auth on API gateway?

I am using Custom Auth on AWS API Gateway, but I would like to add an extra HTTP header depending on the result. Does anyone know if this is possible, or how to do it. If it is not, is there an idea of if or when this will be possible?
Many thanks.
We recently added support for this. Docs should be up soon.
Now you can return an object like this from the authorizer function:
{
"principalId": "xxxxxxxx", // The principal user identification associated with the token send by the client.
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow|Deny",
"Resource": "arn:aws:execute-api:<regionId>:<accountId>:<appId>/<stage>/<httpVerb>/[<resource>/<httpVerb>/[...]]"
}
]
},
"context" : {
"key" : "value",
"numKey" : 1,
"boolKey" : true
}
}
Arrays and objects aren't allowed, only string/number/boolean as valid JSON. The root key must be named context.
You can access those values in the request $context like so:
$context.authorizer.key -> value
$context.authorizer.numKey -> 1
$context.authorizer.boolKey -> true
So to answer your question, you wont' be able to conditionally add the header, but you could set the header value to $context.authorizer.yourKey and if yourKey isn't set in the authorizer response, the header value would be blank (but the header would still be sent).
Edit:
Docs are live http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html
I was able to get this working after a day of pulling my hair out and hopefully I can save someone from that. It adds a bit more to Jacks' response.
Basically, you can dynamically add headers by using Body Mapping Templates
Create and Authorizer Lambda (you can use the authorizer blueprint lambda to get started), do your business logic to create the AuthPolicy and populate the context object with the key/values.
On the API Gateway, select the resource, click on Method Request and set the Auth to your Authorizer lambda
Open Method Execution, select the Integration type and make sure to unselect Use Lambda Proxy integration (if your request points to a lambda)
Add a Body Mapping Template - create one from the template and this is where you have access to $context.authorizer.key
Add the following to your template (just under "body-json" : $input.json('$')," is fine)
"headers": {
"key-header" : "$util.escapeJavaScript($context.authorizer.key)",
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))"
#if($foreach.hasNext),#end
#end },
This will add a new header called "key-header" and also forward all the original headers allowing you to append additional information like user_id, user_role, etc to the upstream service.
I tried Emanuel Canha's answer above, but that no longer seems to work. The way I got it to work yesterday (5 June 2019) is to
Create the lambda
Add the lambda under Method Request as your authorizer
Open Integration Request
Open HTTP Headers
Add a header with the name you want (key-header in the above example)
Use context.authorizer.yourKey as the mapped from entry. (Note that you do not use the $ in this field.)
You can only get PrincipalId from authorizer result, in your integration request, you can map a header value using context.authorizer.principalId
These answers seem to be specific to HTTP API Gateways. We're trying to user RestAPI API gateway which would work with both Lambdas (that take pricnipalID as part of the event object forwarded by Lambda Authorizer) but also, we would like to Authorize non-Lambda endpoints (deployed in private VPC and accessed via Network Load Balancer) so we want to put the principalId into HTTP headers the endpoint would parse.