I am using the aws-sdk to get a pre-signed url for S3. I have the function wrapped in a lambda.
const aws = require('aws-sdk');
module.exports = CreateRecord => {
CreateRecord.controllers.createSignature = (event, context, callback) => {
const s3 = new aws.S3({
signatureVersion: 'v4',
});
const params = {
Bucket: 'random-test-bucket002',
Key: 'test-key',
Expires: 100
};
s3.getSignedUrl('putObject', params, function(err, signedUrl) {
let response;
if (err) {
response = {
statusCode: 500,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Did not receive signed url'
}),
};
} else {
response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
},
body: JSON.stringify({
message: `Url successfully created`,
signedUrl,
})
};
}
callback(null, response);
});
};
};
This code works perfectly fine and I get back my pre-signed url. When I run this code on my front end:
postImage(uuid) {
const getSignature = 'https://xyz.execute-api.us-east-1.amazonaws.com/dev/v1/createSignature';
axios.get(getSignature)
.then(res => {
const signatureUrl = res.data.signedUrl;
// I have a blob that I store in file
// uuid is passed from another function
const file = new File([this.state.cover], uuid);
axios.post(signatureUrl, file)
.then(s3Res => {
console.log(s3Res);
});
});
}
The error I keep getting is: The request signature we calculated does not match the signature you provided. Check your key and signing method. I tried messing around with a few content-type headers but that did nothing. Can I pass the pre-signed url to a function in the aws-sdk? I've looked at a lot of posts on this but can't seem to resolve the issue.
When using pre-signed PutObject URLs for uploads to S3, you should upload files using the HTTP PUT method, rather than the HTTP POST method. You can POST objects to S3 but that's designed for browser-based uploads.
Related
new to AWS and just not sure how to define the relevant authenitcation to get my lambda function to be able to call my graphQL endpoint for a post req. Assuming I need to put an API key somewhere in this function but just am a bit lost. Any help at all would be great. Have put the function below - created it using the amplify cli and the generategraphqlpermissions flag is set to true if thats any help narrowing it down.
import crypto from '#aws-crypto/sha256-js';
import { defaultProvider } from '#aws-sdk/credential-provider-node';
import { SignatureV4 } from '#aws-sdk/signature-v4';
import { HttpRequest } from '#aws-sdk/protocol-http';
import { default as fetch, Request } from 'node-fetch';
const GRAPHQL_ENDPOINT = <myEndpoint>;
const AWS_REGION = process.env.AWS_REGION || 'us-east-1';
const { Sha256 } = crypto;
const query = /* GraphQL */ `mutation CreateCalendarEvent($input: CreateCalendarEventInput!, $condition: ModelCalendarEventConditionInput) {
createCalendarEvent(input: $input, condition: $condition) {
__typename
id
start
end
title
actions
allDay
resizable
draggable
colour
createdAt
updatedAt
}
}`;
/**
* #type {import('#types/aws-lambda').APIGatewayProxyHandler}
*/
export const handler = async (event) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
console.log(GRAPHQL_ENDPOINT);
const endpoint = new URL(GRAPHQL_ENDPOINT);
const signer = new SignatureV4({
credentials: defaultProvider(),
region: AWS_REGION,
service: 'appsync',
sha256: Sha256
});
const requestToBeSigned = new HttpRequest({
method: 'POST',
headers: {
'Content-Type': 'application/json',
host: endpoint.host
},
hostname: endpoint.host,
body: JSON.stringify({ query }),
path: endpoint.pathname
});
const signed = await signer.sign(requestToBeSigned);
const request = new Request(endpoint, signed);
let statusCode = 200;
let body;
let response;
try {
response = await fetch(request);
body = await response.json();
if (body.errors) statusCode = 400;
} catch (error) {
statusCode = 500;
body = {
errors: [
{
message: error.message
}
]
};
}
return {
statusCode,
// Uncomment below to enable CORS requests
// headers: {
// "Access-Control-Allow-Origin": "*",
// "Access-Control-Allow-Headers": "*"
// },
body: JSON.stringify(body)
};
};
WHen invoking an AWS Service from Lambda, you do not need the keys. Instead, you can give the IAM role that the Lambda function runs under the permissions to invoke that service. In your case, give the role permission to invoke app sync.
More information can be found here:
https://docs.aws.amazon.com/lambda/latest/dg/lambda-permissions.html
Im trying to run the United States Postal Service's Web Tools, for converting ZIP Codes into State and City. I created an AWS Lambda function inside the AWS Amplify.
But the Lambda function is always giving me the return message {"message":"Internal Server Error"}
Here is my Lambda FUnction Code.
const axios = require("axios");
const BASE_URI =
"http://production.shippingapis.com/ShippingAPITest.dll?API=CityStateLookup&XML=";
const config = {
headers: {
"Content-Type": "text/xml",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Methods": "GET",
},
method: "get",
};
exports.handler = async function (event, context, callback) {
// The zipcode is sent by the frontend application.
// This is where we use it.
const zipcode = event.queryStringParameters.zipcode;
// The xml variable is the string we are going to send to the
// USPS to request the information
const xml = `<CityStateLookupRequest USERID="400000000"><ZipCode ID="0"><Zip5>${zipcode}</Zip5></ZipCode></CityStateLookupRequest>`;
try {
// Using syntactic sugar (async/await) we send a fetch request
// with all the required information to the USPS.
const response = await axios(`${BASE_URI}${xml}`, config);
// We first check if we got a good response. response.ok is
// saying "hey backend API, did we receive a good response?"
if (!response.ok) {
// If we did get a good response we store the response
// object in the variable
return { statusCode: response.status, body: response };
}
// Format the response as text because the USPS response is
// not JSON but XML
const data = await response.text();
// Return the response to the frontend where it will be used.
return {
statusCode: 200,
body: data,
};
// Error checking is very important because if we don't get a
// response this is what we will use to troubleshoot problems
} catch (err) {
console.log("Error: ", err);
return {
statusCode: 500,
body: JSON.stringify({ msg: err.message }),
};
}
};
The axios is working fine I think.
Any help would be appreciated as I'm trying to solve this for days now.
By default Lambda functions does not have outbound access to internet.
You can add a Nat Gateway to your VPC but it's not cheap.
I'm trying to know how to use cookies with AWS-Lambda with the serverless framework as per this blogpost
and following is my serverless.yml code
functions:
hello:
handler: handler.hello
events:
- http:
path: /post
method: post
cors:
origin : 'https://xyz.netlify.app'
and Lambda function as per following
"use strict";
const cookie = require("cookie");
module.exports.hello = async (event) => {
const body = JSON.parse(event.body);
const name = body.name;
const value = body.value;
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "https://xyz.netlify.app",
"Access-Control-Allow-Credentials": true,
"Set-Cookie": cookie.serialize(name, value, {
expires: new Date(new Date().getTime() + 10 * 1000),
}),
},
body: JSON.stringify(
{
input: event,
},
null,
2
),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
As you can notice, I already have configured the code to avoid any cors issue.
While try to send a post request as per following,
const name = document.getElementById('name')
const value = document.getElementById('value')
const post_btn = document.getElementById('post_btn')
post_btn.addEventListener('click', () => {
console.log(name.value, value.value)
const post_url = 'https://abcdxyz59t9.execute-api.ap-south-1.amazonaws.com/dev/post'
const user = {
name: name.value,
value: value.value
};
// request options
const options = {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}
// send POST request
fetch(post_url, options)
.then(res => res.json())
.then(res => console.log(res));
})
I do get a Set-Cookie header like below
But the cookie doesn't get saved in the browser.
That's not the case when I directly try to hit a get request with that URL without the cors in the browser. Can anyone please tell me what to do?
I'm very new to Lambda.
My goal is to have an API endpoint where I can include an URL as argument (and probably a password), and have Lambda retrieve the file on that url, and save it into an S3 bucket.
I have the bucket ready, and have been reading all tutorials and examples I could find regarding lambda, but so far I can't figure out the pieces needed to get this to work.
Any guidance would be appreciated.
EDIT 1:
I got this far. This receives the url parameters from the API and checks the password, but it doesn't execute the get:
exports.handler = async (event) => {
let url = '';
let key = '';
let out = 'empty';
url = event["queryStringParameters"]['url'];
key = event["queryStringParameters"]['key'];
if (key != 'secret')
{
const response = {
statusCode: 200,
body: JSON.stringify('Unauthorized')
};
return response;
}
console.log("START");
var https = require('https');
https.get(url, function(res) {
console.log("Got response: " + res.statusCode);
const response = {
statusCode: 200,
body: JSON.stringify("success")
};
return response;
}).on('error', function(e) {
console.log("Got error: " + e.message);
const response = {
statusCode: 200,
body: JSON.stringify("fail")
};
return response;
}).end();
const response = {
statusCode: 200,
body: JSON.stringify(out)
};
return response;
};
In a Lambda, I would like to sign my AppSync endpoint with aws-signature-v4 in order to use it for a mutation.
The URL generated seems to be ok but it gives me the following error when I try it:
{
"errors" : [ {
"errorType" : "InvalidSignatureException",
"message" : "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. etc...
} ]
}
Here is my lambda function
import { Context, Callback } from 'aws-lambda';
import { GraphQLClient } from 'graphql-request';
const v4 = require('aws-signature-v4');
export async function handle(event: any, context: Context, callback: Callback) {
context.callbackWaitsForEmptyEventLoop = false;
const url = v4.createPresignedURL(
'POST',
'xxxxxxxxxxxxxxxxx.appsync-api.eu-west-1.amazonaws.com',
'/graphql',
'appsync',
'UNSIGNED-PAYLOAD',
{
key: 'yyyyyyyyyyyyyyyyyyyy',
secret: 'zzzzzzzzzzzzzzzzzzzzz',
region: 'eu-west-1'
}
);
const mutation = `{
FAKEviewProduct(title: "Inception") {
productId
}
}`;
const client = new GraphQLClient(url, {
headers: {
'Content-Type': 'application/graphql',
action: 'GetDataSource',
version: '2017-07-25'
}
});
try {
await client.request(mutation, { productId: 'jfsjfksldjfsdkjfsl' });
} catch (err) {
console.log(err);
callback(Error());
}
callback(null, {});
}
I got my key and secret by creating a new user and Allowing him appsync:GraphQL action.
What am I doing wrong?
This is how I trigger an AppSync mutation using by making a simple HTTP-request, using axios.
const AWS = require('aws-sdk');
const axios = require('axios');
exports.handler = async (event) => {
let result.data = await updateDb(event);
return result.data;
};
function updateDb({ owner, thingName, key }){
let req = new AWS.HttpRequest('https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql', 'eu-central-1');
req.method = 'POST';
req.headers.host = 'xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com';
req.headers['Content-Type'] = 'multipart/form-data';
req.body = JSON.stringify({
"query":"mutation ($input: UpdateUsersCamsInput!) { updateUsersCams(input: $input){ latestImage uid name } }",
"variables": {
"input": {
"uid": owner,
"name": thingName,
"latestImage": key
}
}
});
let signer = new AWS.Signers.V4(req, 'appsync', true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
return axios({
method: 'post',
url: 'https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql',
data: req.body,
headers: req.headers
});
}
Make sure to give the IAM-role your Lambda function is running as, permissions for appsync:GraphQL.
Adding an answer here because I had difficulty getting the accepted answer to work and I found an issue on the AWS SDK GitHub issues that said it's not recommended to use the AWS.Signers.V4 object in production. This is how I got it to work using the popular aws4 npm module that is recommended later on in the issue linked above.
const axios = require('axios');
const aws4 = require('aws4');
const query = `
query Query {
todos {
id,
title
}
}`
const sigOptions = {
method: 'POST',
host: 'xxxxxxxxxx.appsync-api.eu-west.amazonaws.com',
region: 'eu-west-1',
path: 'graphql',
body: JSON.stringify({
query
}),
service: 'appsync'
};
const creds = {
// AWS access tokens
}
axios({
url: 'https://xxxxxxxxxx.appsync-api.eu-west/graphql',
method: 'post',
headers: aws4.sign(sigOptions, creds).headers,
data: {
query
}
}).then(res => res.data))
You don't need to construct a pre-signed URL to call an AWS AppSync endpoint. Set the authentication mode on the AppSync endpoint to AWS_IAM, grant permissions to your Lambda execution role, and then follow the steps in the "Building a JavaScript Client" tutorial to invoke a mutation or query.