I have a Vue.js website on an S3 bucket, that uses API Gateway to run a Lambda function, all behind Cloudfront. Nothing special!
This was live and working circa 2019 and has been left on the backburner since then.
Jump forward to now, the Lambda function no longer works, it gets a referer that is different to what I used to get back in 2019.
2019: referer = https://xxxxxxxxxxxxxx.cloudfront.net/machine/12345
2022: referer = https://xxxxxxxxxxxxxx.cloudfront.net/
the "/machine/12345" is no longer part of the referer, is there any way to configure Cloudfront, Lambda or API Gateway to pass this through as before?
Edit:
My Lambda function is in node.js
Here's the code up until the failure point on 'split'
// Load the SDK for JavaScript
const AWS = require('aws-sdk');
// Set the region
AWS.config.update({region: 'eu-west-1'});
const ddb = new AWS.DynamoDB.DocumentClient();
const cognitoClient = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
//Check for lambda invoked from pre-flight CORS OPTION request
console.log('event = ', event);
if(event.httpMethod == 'OPTIONS') {
callback(null, {
statusCode: 201,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Amz-Security-Token,Content-Type,X-Amz-Date,Authorization,X-Api-Key'
},
});
}
else
{
//Normal invocation
console.log('referer =', event.params.header.Referer);
//get machine name from event.params.header.Referer
//e.g. event.params.header.Referer = "http://localhost:8080/machine/12345/2";
var referer = event.params.header.Referer.split("machine/")[1].split("/");
//e.g. referer = [ '12345', '2'];
var selectedmachine = referer[0];
var pagenum = Number(referer[1]);
Related
I am struggle with AWS amplify to get the users list of a specific cognito group.
I always get this issue : localhost/:1 Access to XMLHttpRequest at 'https://ixa37ulou3.execute-api.eu-central-1.amazonaws.com/dev/users?groupName=xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
I tried a lot of things, see my last try below :
First Step: creatation of my Lambda function using "amplify add function":
const AWS = require('aws-sdk');
const cognito = new AWS.CognitoIdentityServiceProvider({region:"eu-central-1"});
exports.handler = async (event) => {
const params = {
GroupName: event.groupName,
UserPoolId: 'eu-central-1_xqIZx0wkT',
};
const cognitoResponse = await cognito.listUsersInGroup(params).promise()
const response = {
statusCode:200,
headers:{
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Headers":"Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Credentials": true
},
body: JSON.stringify(cognitoResponse),
};
return response;
};
Second Step: Creation of my REST Api using "amplify add api" (pathname: /users)
Third Step: create in API Gateway a new authorizer "using cognito type and link to my user pool, and for the token source : Authorization. And for /users - ANY - Method Request => i added my authorizer in Authorization field.
Fourth Step: creation of my Calling API Function:
async function callApi(){
const user = await Auth.currentAuthenticatedUser()
const token = user.signInUserSession.idToken.jwtToken
console.log({token})
const requestInfo = {
headers:{
Authorization: token
},
queryStringParameters:{
groupName:"MyuserGroup"
}
}
const data = await API.get('apilistusers','/users',requestInfo)
console.log({data})
}
I would very much appreciate any help on this topic, thank you very much.
the current version of amplify has an option to create a lambda function for administrating cognito users, see "amplify add auth" and this page https://docs.amplify.aws/cli/auth/admin/
I am using AWS CDK (v1.87.1 (build 9eeaa93)) to define my infrastructure as code. I use C# to define my CDK stack(s).
I have my data stored in DynamoDb and an API gateway backed by Lambda functions to read/write to the DynamoDb. This is my backend.
My frontend is a simple static website (HTML + JS) hosted on AWS S3 distributed through CloudFront.
My API works fine when I test it independently with curl or in the AWS console. However, when I call the API using the fetch() browser API from within my static website page, I get the following error (in the browser):
Access to fetch at
'https://xxxxxxxx.execute-api.ap-south-1.amazonaws.com/prod/Account'
from origin 'https://abcdefg.cloudfront.net' has been blocked by
CORS policy: No 'Access-Control-Allow-Origin' header is present on the
requested resource. If an opaque response serves your needs, set the
request's mode to 'no-cors' to fetch the resource with CORS disabled.
My CorsOptions are defined as follows:
var defaultCorsPreflightOptions = new CorsOptions() {
AllowOrigins = Cors.ALL_ORIGINS,
AllowMethods = Cors.ALL_METHODS,
AllowHeaders = new [] {"*"},
AllowCredentials = true,
MaxAge = Duration.Days(0)
};
My API is as follows:
var api = new RestApi(this, "my-api", new RestApiProps {
RestApiName = "My Service",
Description = "This is the service API"
});
My resource creation adds the CorsOption for preflight (In the above error message 'Account' would be a resource added to the root):
var resourceType = api.Root.AddResource(ent);
resourceType.AddCorsPreflight(defaultCorsPreflightOptions);
My lambda handler also has
if(method == "OPTIONS") {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,PUT,GET,DELETE"
}
};
return response;
} else if(method === "POST") {
// ... omitted
}
The JS client code that calls the REST API is:
const response = await fetch(`${api_url}/${entity}`,{
method: 'POST',
mode: 'cors',
body: item,
headers: {
'Content-Type': 'application/json'
}
});
The Behavior that is attached to the CloudFront distribution:
// The cloudfront distribution for the website
var behavior = new Behavior() {
IsDefaultBehavior = true,
AllowedMethods = CloudFrontAllowedMethods.ALL,
MaxTtl = Duration.Seconds(0),
MinTtl = Duration.Seconds(0),
DefaultTtl = Duration.Seconds(0),
Compress = false,
ForwardedValues = new CfnDistribution.ForwardedValuesProperty() {
QueryString = true,
Headers = new [] {"Authorization", "Access-Control-Allow-Origin"}
}
};
My CloudFront distribution is as follows:
var distribution = new CloudFrontWebDistribution(this, "StaticWebsiteDistribution", new CloudFrontWebDistributionProps() {
OriginConfigs = new [] {
new SourceConfiguration() {
S3OriginSource = new S3OriginConfig() {
S3BucketSource = bucket
},
Behaviors = new [] {
behavior
}
}
}
});
My S3 Bucket deployment code is:
// The S3 bucket deployment for the website
var deployment = new BucketDeployment(this, "WebsiteDeployment", new BucketDeploymentProps(){
Sources = new [] {Source.Asset("./website")},
DestinationBucket = bucket,
Distribution = distribution
});
I have tried looking into the AWS CDK documentation. I have tried adding the default CORS option at the API level also, but without any success. What am I missing? Please help.
I figured it out. The error was was in the Lambda handler for the POST / GET / DELETE / PUT methods. I needed to return the headers (example given below):
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*", // I was missing this
"Content-Type": "application/json" // and this
},
body: JSON.stringify({"id":`${id}`}) // and this was a string earlier
};
I had another error in the client side with the fetch() response handling. I was using response.json() whereas it should have been response.text() (since I was sending text in the response body earlier).
I was misled by the curl response (in my testing) which was just the plain text whereas there was a JSON parse issue with handling the fetch() response.
Key takeaway: Check your Lambda handler responses.
I want to get real-time data from a stock API (IEX API) and load it to DynamoDB. Most of the tutorials I've watched so far (like this around 15:10) show how to create Lambda functions and integrate with API gateway. However, they are still manually entering the data to load to the table. How do I use the API Gateway and Lambda to get data from the IEX API and load it to DynamoDB rather than writing the data myself?
Following some tutorials, my Lambda function for loading data to the table is:
const AWS = require('aws-sdk');
exports.handler = async (event, context) => {
const documentClient = new AWS.DynamoDB.DocumentClient();
let responseBody = "";
let statusCode = 0;
const { id, stocksymbol} = JSON.parse(event.body);
const params = {
TableName: "StockData",
Item: {
id: id,
stocksymbol: stocksymbol
}
};
try {
const data = await documentClient.put(params).promise();
responseBody = JSON.stringify(data);
statusCode = 201;
} catch(err) {
responseBody = `Unable to put item: ${err}`;
statusCode = 403;
}
const response = {
statusCode: statusCode,
headers: {
"Content-Type": "application/json"
},
body: responseBody
};
return response
};
I would be getting more data from the API (opening price, closing price etc.) but this is what I have for now.
Assuming you need to utilize pull mechanism (you need to get data from API yourself), you can use AWS EventBridge rule to trigger your lambda periodically with interval of your preference - https://docs.aws.amazon.com/eventbridge/latest/userguide/create-eventbridge-scheduled-rule.html. In lambda you download the API data and store them in DynamoDb.
If you can use push mechanism (you can get data pushed to you, e.g. https://iexcloud.io/docs/api/?gclid=CjwKCAiA_9r_BRBZEiwAHZ_v17o9kJuPyF5Do_E3_mwC0uHEh2yXqqOdtVgqvc34yEk5RR8W8028HRoC0HUQAvD_BwE#webhooks), you can set the your API gateway resource URL as a target path, while having your lambda function as the handler for the API Gateway resource URL, storing the pushed data to the DynamoDb.
I have a Cloudfront distribution pointing at a custom url.
In that distribution, I have setup 2 lambda function associations.
1 on the Cloudfront event of viewer-request to query parameter store and redirect to the correct url
The other on the Cloudfront event of origin-response to gather response stats from the call.
The viewer-reqest function is as follows - note dummy urls for purpose of question
exports.handler = async (event, context) => {
const origin = "main";
const primaryUrl = "https://www.google.com";
const drUrl = "https://www.yahoo.com";
let url = primaryUrl;
if (origin != "main") {
url = drUrl;
} else {
url = primaryUrl;
}
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: url,
}],
},
};
return response;
};
The origin response lambda is very simple for now
var AWS = require('aws-sdk');
AWS.config.update({ region: 'us-east-1' });
var cloudwatch = new AWS.CloudWatch();
exports.handler = async (event, context) => {
console.log("Custom Metrics Event");
console.log(JSON.stringify(event));
const response = {
status: '200',
};
return response;
};
When I call the cloudfront distribution domain, the viewer-request lambda#edge kicks in but the origin-response lambda does not trigger and I see nothing in cloudwatch
If I remove the viewer-request lambda - the origin-response lambda does trigger
Am I doing anything wrong here or does anyone have any recommendations?
Thank you
Damien
#ChrisWilliams comment was accurate
I changed my code to perform the redirect in the origin response and all worked fine then
I have a simple AWS Lambda function which makes an S3.getObject() call as follows:
const AWS = require('aws-sdk');
AWS.config.logger = console;
const s3 = new AWS.S3();
exports.handler = async (event) => {
return await getObject({
Bucket: "<MY-BUCKET>",
Key: "<MY-KEY>"
}).then( (res) => {
console.log('Retrieved object from S3');
console.log(res);
return res.Body.toString('ascii');
})
};
async function getObject(params){
return await s3.getObject(params).promise();
}
I've enabled logging SDK calls as per this document.
How do I get response headers for the s3.getObject() SDK call that was made? I am basically trying to retrieve the S3 request ID and extended request ID.
The in-built logger added via the "AWS.config.logger = console;" line does not seem to log response headers. How else do I get response headers for AWS JavaScript SDK calls?
P.S: Bonus points if you can let me know whether or not I need two await keywords in the code above.
Listen to httpHeaders event.
var requestObject = s3.getObject(params);
requestObject.on('httpHeaders', (statusCode, headers, response, statusMessage) => {
// your code here.
});
requestObject.promise()
.then(response => { ... })