Express lambda return JSON from api call - amazon-web-services

I have a serverless express app. In the app I have a app.get called '/', which should call an api, retrieve the data from the api and send it back to the user.
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev
I can see data as json on the page being returned.
This is my index.js of the lambda function:
const serverless = require('serverless-http');
const express = require('express');
const request = require('request');
const app = express()
app.get('/', function (req, res) {
var options = { method: 'POST',
url: 'https://some.api.domain/getTopNstc',
headers:
{ 'Content-Type': 'application/json' },
body: {},
json: true
};
request(options, function (error, response, body) {
console.log('request call')
if (error) throw new Error(error);
// res.status(200).send(response);
res.json(response);
});
});
module.exports.handler = serverless(app);
However I would to be able to call the lambda '/' via axios (or other promise-request library)
I've tried to use the following code to make a call to my lambda:
axios.get('https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev', {
headers: {
'Content-Type': 'application/json',
},
body:{}
}).then((res) => {
console.log(res);
});
Failed to load
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'myDomain' is therefore not
allowed access. bundle.js:31 Cross-Origin Read Blocking (CORB) blocked
cross-origin response
https://y31q4zn654.execute-api.eu-west-1.amazonaws.com/dev with MIME
type application/json. See
https://www.chromestatus.com/feature/5629709824032768 for more
details.
Api gateway config:

I concur with #KMo. Pretty sure this is a CORS issue. There is a module in npm exactly for this purpose, read up about it here.
To install it, run npm install -s cors
Then in your express app, add the following:
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors());

Related

How to block external requests to a NextJS Api route

I am using NextJS API routes to basically just proxy a custom API built with Python and Django that has not yet been made completely public, I used the tutorial on Vercel to add cors as a middleware to the route however it hasn't provided the exact functionality I wanted.
I do not want to allow any person to make a request to the route, this sort of defeats the purpose for but it still at least hides my API key.
Question
Is there a better way of properly stopping requests made to the route from external sources?
Any answer is appreciated!
// Api Route
import axios from "axios";
import Cors from 'cors'
// Initializing the cors middleware
const cors = Cors({
methods: ['GET', 'HEAD'],
allowedHeaders: ['Content-Type', 'Authorization','Origin'],
origin: ["https://squadkitresearch.net", 'http://localhost:3000'],
optionsSuccessStatus: 200,
})
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (res) => {
if (res instanceof Error) {
return reject(res)
}
return resolve(res)
})
})
}
async function getApi(req, res) {
try {
await runMiddleware(req, res, cors)
const {
query: { url },
} = req;
const URL = `https://xxx/api/${url}`;
const response = await axios.get(URL, {
headers: {
Authorization: `Api-Key xxxx`,
Accept: "application/json",
}
});
if (response.status === 200) {
res.status(200).send(response.data)
}
console.log('Server Side response.data -->', response.data)
} catch (error) {
console.log('Error -->', error)
res.status(500).send({ error: 'Server Error' });
}
}
export default getApi
Sorry for this late answer,
I just think that this is the default behaviour of NextJS. You are already set, don't worry. There is in contrast, a little bit customization to make if you want to allow external sources fetching your Next API

Why is AWS Lambda, API Gateway returning a CORS error

I know this issue has been covered in many posts all over the web and I think I've tried them all, but I'm still getting a 403 CORS error to my local react app.
Here are in part, the Headers from Dev Tools:
#GENERAL:
Request URL: https://<myGatewayApiUrl>.amazonaws.com/dev/api/byid/1/129
Request Method: OPTIONS
Status Code: 403
#RESPONSE HEADERS
access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
access-control-allow-methods: GET,OPTIONS
access-control-allow-origin: *
content-length: 42
content-type: application/json
I've been working in the API Gateway setting the Enable CORS, but I get an error for one get methods Add Access-Control-Allow-Origin Integration Response Header Mapping to GET method -> invalid response status code specified - But the OPTIONS headers get set and the GET header Access-Control-Allow-Origin is set.
I am using express and cors packages, here's a snippet from my API index.js file:
const app = express();
app.use(cors());
app.options('*', cors());
Here is the request code from React app:
export const getRecordById = async (userId, id, token) => {
try {
const response = await axios.get(
process.env.REACT_APP_API_URL + `/byid/${userId}/${id}`,
{
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
return response.data;
} catch (error) {
console.log('ERROR', error);
return error;
}
};
Here is my response code from the Lambda API:
getById: asyncHandler(async (req, res, next) => {
const { user, id } = req.params;
const result = await recordsService.getRecordById(user, id);
res.set({
'content-type': 'application/json',
'Access-Control-Allow-Origin': '*',
});
if (!result) {
res.status(400).json({
error: true,
message: 'get record by ID action failed',
data: {},
});
}
res.status(200).json({
error: false,
message: 'successful record retrieval',
data: {
record: result,
},
});
}),
Also, I have my serverless.yml file http events set as such: (from what I understand cors: true should handle the preflight requests)
- http:
path: /api/records/byid/{user}/{id}
method: GET
cors: true
I've spent way too much time trying to figure this out. It must be something simple and dumb, am I using res.set() properly? Everything looks correct, I know I'm missing something. Thanks
API Gateway will reject the call with a CORS error when a URL is not found by default.
It looks like Axios is missing the /records bit from the request URL.

Unable to set authorization header using Apollo's setContext()

So I'm trying to pass an authorization header to Apollo according to the docs on their site.
https://www.apollographql.com/docs/react/networking/authentication/?fbclid=IwAR17YAJ0VA5G8InmAJ_PxcukXKNQxFLFI8aeT4oB7wYE3DjWNaB_F67__zs
However, when I try to log request.headers on my server I don't see the authorization property on there. Next I checked whether or not the function setContext() was being run at all by putting a console.log() within it and found that it didn't run.
const httpLink = new HttpLink({ uri: 'http://localhost:4000' });
const authLink = setContext((request, { headers }) => {
const token = localStorage.getItem('library-user-token')
console.log(token) //doesn't log at all
return ({
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
}
})
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
I have almost identical code in my project and it works. Are you passing the client to your ApolloProvider?

Apify: Preserve headers in RequestQueue

I'm trying to crawl our local Confluence installation with the PuppeteerCrawler. My strategy is to login first, then extracting the session cookies and using them in the header of the start url. The code is as follows:
First, I login 'by foot' to extract the relevant credentials:
const Apify = require("apify");
const browser = await Apify.launchPuppeteer({sloMo: 500});
const page = await browser.newPage();
await page.goto('https://mycompany/confluence/login.action');
await page.focus('input#os_username');
await page.keyboard.type('myusername');
await page.focus('input#os_password');
await page.keyboard.type('mypasswd');
await page.keyboard.press('Enter');
await page.waitForNavigation();
// Get cookies and close the login session
const cookies = await page.cookies();
browser.close();
const cookie_jsession = cookies.filter( cookie => {
return cookie.name === "JSESSIONID"
})[0];
const cookie_crowdtoken = cookies.filter( cookie => {
return cookie.name === "crowd.token_key"
})[0];
Then I'm building up the crawler structure with the prepared request header:
const startURL = {
url: 'https://mycompany/confluence/index.action',
method: 'GET',
headers:
{
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
Cookie: `${cookie_jsession.name}=${cookie_jsession.value}; ${cookie_crowdtoken.name}=${cookie_crowdtoken.value}`,
}
}
const requestQueue = await Apify.openRequestQueue();
await requestQueue.addRequest(new Apify.Request(startURL));
const pseudoUrls = [ new Apify.PseudoUrl('https://mycompany/confluence/[.*]')];
const crawler = new Apify.PuppeteerCrawler({
launchPuppeteerOptions: {headless: false, sloMo: 500 },
requestQueue,
handlePageFunction: async ({ request, page }) => {
const title = await page.title();
console.log(`Title of ${request.url}: ${title}`);
console.log(page.content());
await Apify.utils.enqueueLinks({
page,
selector: 'a:not(.like-button)',
pseudoUrls,
requestQueue
});
},
maxRequestsPerCrawl: 3,
maxConcurrency: 10,
});
await crawler.run();
The by-foot-login and cookie extraction seems to be ok (the "curlified" request works perfectly), but Confluence doesn't accept the login via puppeteer / headless chromium. It seems like the headers are getting lost somehow..
What am I doing wrong?
Without first going into the details of why the headers don't work, I would suggest defining a custom gotoFunction in the PuppeteerCrawler options, such as:
{
// ...
gotoFunction: async ({ request, page }) => {
await page.setCookie(...cookies); // From page.cookies() earlier.
return page.goto(request.url, { timeout: 60000 })
}
}
This way, you don't need to do the parsing and the cookies will automatically be injected into the browser before each page load.
As a note, modifying default request headers when using a headless browser is not a good practice, because it may lead to blocking on some sites that match received headers against a list of known browser fingerprints.
Update:
The below section is no longer relevant, because you can now use the Request class to override headers as expected.
The headers problem is a complex one involving request interception in Puppeteer. Here's the related GitHub issue in Apify SDK. Unfortunately, the method of overriding headers via a Request object currently does not work in PuppeteerCrawler, so that's why you were unsuccessful.

Google Cloud Functions enable CORS?

I just finished the Hello World Google Cloud Functions tutorial and received the following response headers:
Connection → keep-alive
Content-Length → 14
Content-Type → text/plain; charset=utf-8
Date → Mon, 29 Feb 2016 07:02:37 GMT
Execution-Id → XbT-WC9lXKL-0
Server → nginx
How can I add the CORS headers to be able to call my function from my website?
here we go:
exports.helloWorld = function helloWorld(req, res) {
res.set('Access-Control-Allow-Origin', "*")
res.set('Access-Control-Allow-Methods', 'GET, POST');
if (req.method === "OPTIONS") {
// stop preflight requests here
res.status(204).send('');
return;
}
// handle full requests
res.status(200).send('weeee!);
};
then you can jquery/whatever it as usual:
$.get(myUrl, (r) => console.log(r))
I'm the product manager for Google Cloud Functions. Thanks for your question, this has been a popular request.
We don't have anything to announce just yet, but we're aware of several enhancements that need to be made to the HTTP invocation capabilities of Cloud Functions and we'll be rolling out improvements to this and many other areas in future iterations.
UPDATE:
We've improved the way you deal with HTTP in Cloud Functions. You now have full access to the HTTP Request/Response objects so you can set the appropriate CORS headers and respond to pre-flight OPTIONS requests (https://cloud.google.com/functions/docs/writing/http)
UPDATE (2022):
Just noticed there was a question about docs, and our docs have moved. Updated docs for CORS are here:
https://cloud.google.com/functions/docs/samples/functions-http-cors
You can use the CORS express middleware.
package.json
npm install express --save
npm install cors --save
index.js
'use strict';
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors')({origin: true});
const app = express();
app.use(cors);
app.get('*', (req, res) => {
res.send(`Hello, world`);
});
exports.hello = functions.https.onRequest(app);
I've just created webfunc. It's a lightweight HTTP server that supports CORS as well as routing for Google Cloud Functions. Example:
const { serveHttp, app } = require('webfunc')
exports.yourapp = serveHttp([
app.get('/', (req, res) => res.status(200).send('Hello World')),
app.get('/users/{userId}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}`)),
app.get('/users/{userId}/document/{docName}', (req, res, params) => res.status(200).send(`Hello user ${params.userId}. I like your document ${params.docName}`)),
])
In your project's root, simply add a appconfig.json that looks like this:
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
Hope this helps.
In the python environment, you can use the flask request object to manage CORS requests.
def cors_enabled_function(request):
if request.method == 'OPTIONS':
# Allows GET requests from any origin with the Content-Type
# header and caches preflight response for an 3600s
headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '3600'
}
return ('', 204, headers)
# Set CORS headers for the main request
headers = {
'Access-Control-Allow-Origin': '*'
}
return ('Hello World!', 200, headers)
See the gcloud docs for more.
You need to send an 'OPTIONS' response by setting its header as follows:
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', '*');
res.set('Access-Control-Allow-Headers', '*');
res.status(204).send('');
}
Runtime: NodeJS 10
If you tried the accepted answer but encountered a preflight error, the docs offer examples of handling it in multiple languages, with the caveat that it only works on public functions, i.e. deployed with --allow-unauthenticated:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
/* handle preflight OPTIONS request */
res.set("Access-Control-Allow-Methods", "GET, POST");
res.set("Access-Control-Allow-Headers", "Content-Type");
// cache preflight response for 3600 sec
res.set("Access-Control-Max-Age", "3600");
return res.sendStatus(204);
}
// handle the main request
res.send("main response");
};
Another option is to use Express as shown in this post, complete with cross-origin enabled.
You must enable CORS within all your functions, for example hello function:
index.js
const cors = require('cors')();
// My Hello Function
function hello(req, res) {
res.status(200)
.send('Hello, Functions');
};
// CORS and Cloud Functions export
exports.hello = (req, res) => {
cors(req, res, () => {
hello(req, res);
});
}
Don't forget about package.json
package.json
{
"name": "function-hello",
"version": "0.1.0",
"private": true,
"dependencies": {
"cors": "^2.8.5"
}
}
After applying your favourite answer from here, if you're still getting this error, check for uncaught errors in your cloud function. This can result in the browser receiving a CORS error, even when your error has nothing to do with CORS
After CORS enabled if you send POST request to your function also check for your request Content-Type header, mine was set it to "text/plain" and my browser was constantly triggering CORS errors, after setting the header to "application/json" everything worked properly.