Making a POST request using Superagent, AWS Lambda, API Gateway - aws-lambda

I am using AWS Lambda and API Gateway to create a custom endpoint for load tests. I have uploaded my handler function which is in a file, along with the node modules needed for the function in a zip, and set up the API Gateway API correctly according the instructions (in line with the way that I had made it work before), but I keep getting the error: {"error": "Missing Authentication Token"}. Everything I have seen online thus far points to the idea that the url that I am passing in with the POST request is invalid, but I have made a similar endpoint work with a GET request. As far as I know I have set up the POST request (using Superagent) correctly, and am passing in a valid access-token, as well as hardcoded params as part of the URL (valid params).
// Dependencies
var request = require('superagent');
var sync = require('synchronize');
exports.handler = function(event, context) {
sync.fiber(function() {
// Grabs params passed into the URL as a JSON object
var querystring = (event.querystring);
// Replaces params with an updated version which includes a single quotation
var queryStringUpdate = querystring.replace(/=/g, ":").replace(/}/g, "'}").replace(/:/g, ":'").replace(/,/g, "',");
// Updates the param information and sets it as a new string
eval('var queryString2 =' + queryStringUpdate);
// Define specific query params to be used in the REST calls
var userId = (queryString2.userId === undefined ? '229969' : queryString2.userId);
var roomdId = (queryString2.roomId === undefined ? '4' : queryString2.roomId);
var inviterId = (queryString2.inviterId === undefined ? '212733' : queryString2.inviterId);
var createInvitePost = function() {
request
.post('https://some_url/v2/invites/212733/create')
.set({'access-token': 'some_access_token'})
.set('Content-Type', 'application/json')
.query({user_id: "229969"})
.query({room_jid: "4"})
.end(function(err, res){
if (err) {
context.fail("Uh oh, something went wrong");
} else {
context.done(null, "Hurray, it worked!!");
}
});
};
try {
createInvitePost();
} catch(errOne) {
alert("No bueno!!");
}
});
};
Any thoughts on this?? Thanks

I usually get this error when I've missed some part of the URL needed for my API. In the past it's either been the name of the stage, misspelled resource name, or a missing Path parameter.

I'm from the Api Gateway team.
As others have said, the most common cause of the 403 response you're getting is an incorrect path/method. I'm not familiar with Superagent, but if you've run the same request in Postman and cURL then I would be surprised if you had the wrong path/method.
Maybe also check on a wire log if possible, to make sure that your querystring logic isn't appending a forward slash prior to the '?'.
Some things to check:
Have you deployed any recent changes to your API?
Is the stage 'v2' (I'm assuming that's the stage) pointing at a deployed version of the API that has the POST to invites/212733/create?
The 'access-token' should have no effect on the Api Gateway layer. If you're trying to use a native Api Gateway Api Key, the header is 'x-api-key'.
Jack

Related

GET request with query parameters returns 403 error (signature does not match) - AWS Amplify

Problem
I was trying to use 'aws-amplify' GET API request with query parameters on the client side, but it turned out to be Request failed with status code 403, and the response showed:
"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.
Note: React.js as front-end, Javascript as back-end.
My code
Front-end
function getData() {
const apiName = 'MyApiName';
const path = '/path';
const content = {
body:{
data:'myData',
},
};
return API.get(apiName, path, content);
}
Back-end
try {
const result = await dynamoDbLib.call("query", params);
} catch (e) {
return failure({ status: false });
}
What I did to debug
The GET lambda function works fine in Amazon Console (Tested)
If I change the backend lambda function so that the frontend request can be made without parameters, i.e. return API.get(apiName, path), then no error shows up.
My question
How can I make this GET request with query parameters works?
I changed GET to POST (return API.post()), everything works fine now.
If anyone can provide a more detailed explanation, it would be very helpful.

Nuxt Apollo with dynamic headers for a session based authentication

Apollo is not storing the header from the query dynamically.
pages/index.vue
methods: {
fetchCars() {
const token = Cookies.get('XSRF-TOKEN')
console.log(token) // 🟢 Token is shown in console
this.$apollo.query({
query: gql`
query {
cars {
uuid
name
}
}
`,
headers: {
'X-XSRF-TOKEN': token, // â­• Fetch without header
},
})
},
},
Is there a way to set the header value new for every Apollo request?
I have a separate Frontend and Backend. For the Frontend I am using Nuxt.js with Apollo. I want to have a session based communication with my server. For this reason I need to send the CSRF-Token with every Request.
Now the problem: On the first load of the page there is no Cookie set on the browser. I do a GET-Request on every initialization of my Nuxt application.
plugins/csrf.js
fetch('http://127.0.0.1:8000/api/csrf-cookie', {
credentials: 'include',
})
Now I have a valid Cookie set on my side and want to communicate with the GraphQL Server but my header is not set dynamically in the query. Does anyone know how I can solve this?
My Laravel Backend is throwing now a 419 Token Mismatch Exception because I did not send a CSRF-Token with my request.
Link to the repository: https://github.com/SuddenlyRust/session-based-auth
[SOLVED] Working solution: https://github.com/SuddenlyRust/session-based-auth/commit/de8fb9c18b00e58655f154f8d0c95a677d9b685b Thanks to the help of kofh in the Nuxt Apollo discord channel 🎉
In order to accomplish this, we need to access the code that gets run every time a fetch happens. This code lives inside your Apollo client's HttpLink. While the #nuxtjs/apollo module gives us many options, we can't quite configure this at such a high level.
Step 1: Creating a client plugin
As noted in the setup section of the Apollo module's docs, we can supply a path to a plugin that will define a clientConfig:
// nuxt.config.js
{
apollo: {
clientConfigs: {
default: '~/plugins/apollo-client.js'
}
}
}
This plugin should export a function which receives the nuxt context. It should return the configuration to be passed to the vue-cli-plugin-apollo's createApolloClient utility. You don't need to worry about that file, but it is how #nuxtjs/apollo creates the client internally.
Step 2: Creating the custom httpLink
In createApolloClient's options, we see we can disable defaultHttpLink and instead supply our own link. link needs to be the output of Apollo's official createHttpLink utility, docs for which can be found here. The option we're most interested in is the fetch option which as the docs state, is
a fetch compatible API for making a request
This boils down to meaning a function that takes uri and options parameters and returns a Promise that represents the network interaction.
Step 3: Creating the custom fetch method
As stated above, we need a function that takes uri and options and returns a promise. This function will be a simple passthrough to the standard fetch method (you may need to add isomorphic-fetch to your dependencies and import it here depending on your setup).
We'll extract your cookie the same as you did in your question, and then set it as a header. The fetch function should look like this:
(uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
Putting it all together
Ultimately, your ~/plugins/apollo-client.js file should look something like this:
import { createHttpLink } from 'apollo-link-http'
import fetch from 'isomorphic-fetch'
export default function(context) {
return {
defaultHttpLink: false,
link: createHttpLink({
uri: '/graphql',
credentials: 'include',
fetch: (uri, options) => {
const token = Cookies.get('XSRF-TOKEN')
options.headers['X-XSRF-TOKEN'] = token
return fetch(uri, options)
}
})
}
}

ApplePay completeMerchantValidation fails

We have a site example.com behind ssl that runs a page with ApplePay.
We've got a server side that returns a Merchant Session that looks like the following:
{"epochTimestamp":1581975586106,"expiresAt":1581979186106,"merchantSessionIdentifier":"SSH8E666B0...","nonce":"1239e567","merchantIdentifier":"...8557220BAF491419A...","domainName":"example.com","displayName":"ApplePay","signature":"...20101310f300d06096086480165030402010500308..."}
We receive this response in session.onvalidatemerchant as a string and convert it to a Json Object and pass to session.completeMerchantValidation.
As a result we get the following error:
Code: "InvalidAccessError"
Message: "The object does not support the operation or argument"
We run the following code on our page:
.....
session.onvalidatemerchant = (event) => {
const validationURL = event.validationURL;
getApplePaySession(validationURL).then(function (response) {
try {
let resp = JSON.parse(response);
session.completeMerchantValidation(resp);
} catch (e) {
console.error(JSON.stringify(e));
}
});
};
....
Additional questions:
Is the object described above a "correct" Merchant Session opaque that needs to be passed to completeMerchantValidation or it's missing some fields?
Is this object needs to be passed as is or it needs to be base64 encoded?
Does it need to be wrapped into another object?
Any help or lead is greatly appreciated.

Google Cloud Platform: Unable to upload a new file version in Storage via API

I wrote a script that uploads a file to a bucket in Google Cloud Storage:
Ref: https://cloud.google.com/storage/docs/json_api/v1/objects/insert
function submitForm(bucket, accessToken) {
console.log("Fetching the file...");
var input = document.getElementsByTagName('input')[0];
var name = input.files[0].name;
var uploadUrl = 'https://www.googleapis.com/upload/storage/v1/b/'+
bucket + '/o?uploadType=media&access_token=' + accessToken + '&name=' + name;
event.preventDefault();
fetch(uploadUrl, {
method: 'POST',
body: input.files[0]
}).then(function(res) {
console.log(res);
location.reload();
})
.catch(function(err) {
console.error('Got error:', err);
});
}
It works perfectly fine when uploading a new file.
However, I get a 403 status code in the API response body while trying to replace an existing file with a new version.
Please note that:
The OAuth 2.0 scope for Google Cloud Storage is: https://www.googleapis.com/auth/devstorage.read_write
I did enable the versioning for the destination bucket
Could someone help me in pointing out what I did wrong?
Update I:
As suggested, I am trying to invoke the rewrite function as follows:
const input = document.getElementsByName('uploadFile')[0];
const name = input.files[0].name;
const overwriteObjectUrl = 'https://www.googleapis.com/storage/v1/' +
'b/' + bucket +
'/o/' + name +
'/rewriteTo/b/' + bucket +
'/o/' + name;
fetch(overwriteObjectUrl, {
method: 'POST',
body: input.files[0]
})
However, I am getting a 400 (bad request error).
{"error":{"errors":[{"domain":"global","reason":"parseError","message":"Parse Error"}],"code":400,"message":"Parse Error"}}
Could you explain me what I am doing wrong?
Update II:
By changing body: input.files[0] with body: input.files[0].data I made it working... Theoretically!
I get a positive response body:
{
"kind":"storage#rewriteResponse",
"totalBytesRewritten":"43",
"objectSize":"43",
"done":true,
"resource":{
"kind":"storage#object",
"id":"mybuck/README.txt/1520085847067373",
"selfLink":"https://www.googleapis.com/storage/v1/b/mybuck/o/README.txt",
"name":"README.txt",
"bucket":"mybuck",
"generation":"1520085847067373",
"metageneration":"1",
"contentType":"text/plain",
"timeCreated":"2018-03-03T14:04:07.066Z",
"updated":"2018-03-03T14:04:07.066Z",
"storageClass":"MULTI_REGIONAL",
"timeStorageClassUpdated":"2018-03-03T14:04:07.066Z",
"size":"43",
"md5Hash":"UCQnjcpiPBEzdl/iWO2e1w==",
"mediaLink":"https://www.googleapis.com/download/storage/v1/b/mybuck/o/README.txt?generation=1520085847067373&alt=media",
"crc32c":"y4PZOw==",
"etag":"CO2VxYep0NkCEAE="
}
}
Whit as well a new generation number (versioning enabled).
However, the file content has been not updated: I did append new strings but they did not show off within the file. Do you have any idea?
Thanks a lot in advance.
Based on the information available it's difficult to diagnose this issue with certainty- however I would check the roles assigned to the user or service account you are using for this operation.
As you have been able to upload a file, but not overwrite a file, this sounds like you may have assigned the user or service account that is attempting to perform this task the 'Storage Object Creator' role.
Users/service accounts with the Storage Object Creator role can create new objects in buckets but not overwrite existing ones (you can see this mentioned here).
If this is the case, you could try assigning the user/service account the role of 'Storage Object Admin' which allows users full control over bucket objects.
"insert" is only to be used to create new objects per the Methods section of the API's documentation, so you'll need to use "rewrite" to rewrite an existing object.

Is cloud code sessionToken change in parse server 2.4.x?

I just updated parse-server from 2.2.x to 2.4.x and my cloud code using sessionToken did not work. Below is simple cloud code function:
Parse.Cloud.define('find_device', function(request, response) {
var user = request.user;
if(user){
var token = user.getSessionToken();
console.log("User token " + token);
var query = new Parse.Query('devices');
query.equalTo('deviceId', "389125651274465");
query.find({ sessionToken: token })//<- sessionToken does not work
.then(function(messages) {
response.success(messages);
},function(error){
console.log(error);
response.error("error");
});
}else{
response.error("error");
}
});
It uses {sessionToken: token} to query. This code worked before, but now it does not work in parse-server 2.4.x. I received error
ParseError { code: undefined, message: 'unauthorized' }
I don't know if anything change in parse-server version 2.4.x. If i change to {useMasterKey:true} it works ok, but in this case i want to use user's token to query. Thank for your help.
They havent really changes the the ... query.find({sessionToken : token}) ... part, but maybe they have changed how User.getSessionToken() works.
The documentations says :
String getSessionToken( )
Returns the session token for this user, if
the user has been logged in, or if it is the result of a query with
the master key. Otherwise, returns undefined.
Returns: the session token, or undefined
Since in case of cloud-code, neither the user is logged in, not its the result of a query using masterKey, getSessionToken() should actually behave that way only.
To correct this, what I would suggest is, rather than making the query on-behalf of the user in the cloud-code(and thus on the server), just let the user make it from the client.

Resources