Strip specific query parameters in express - node.js

I am having some difficulty in stripping specific query parameters from a request in express. The issue is that in the normal flow, the user would fetch a Bearer Token from a third party which would then be sent to my REST API with each request. The token is decoded and the id of the user is added to the request.
I would like to be able to attach the ID of the user as a query parameter while in development mode, so that I can test the API using something like cURL or Postman, without having to write some sort of script to fetch a token every time.
To that end, I created a separate middleware to be used in development that would take the ID from the querystring, strip it out, and redirect to the new URL. I modified this answer to do so.
this.app.use((req, res, next) => {
const id = req.query.id;
req.user = { id };
let basePath = url.parse(req.url).pathname;
const queryKeys = Object.keys(req.query);
if(queryKeys.length === 0) return res.status(400).send('no id attached');
if(queryKeys.length === 1 && queryKeys[0] === 'id') return next();
basePath += '?';
queryKeys.forEach(queryKey => {
if(basePath[basePath.length -1] !== '?') basePath += '&';
if(queryKey !== 'id') basePath += `${queryKey}=${req.query[queryKey]}`
});
return res.redirect(basePath);
})
This function works fine if I test it using just the ID parameter, (say: http://localhost:5000/api/?id=someid) but if I add a second parameter(say: http://localhost:5000/api/?id=someid&skip=1), I get a 404 with the message / is not a recognized path.. From console.log() statements I can see that in the second example, res.redirect() is being called with /?skip=1, as expected.
What am I doing wrong?

I'm not sure I understand what you're trying to do as it seems that you're redirecting to the same URL without the id query parameter, which would return a 400 error.
In any case, please take a look at this code snippet that correctly removes the id query parameter and does the redirection.
const url = require('url');
app.use((req, res, next) => {
// return early if the id query parameter is not present
if (!req.query.id) {
return res.status(400).send('No id attached.');
}
// rebuild URL by removing the id query parameter
const baseUrl = url.format({
protocol: req.protocol,
host: req.get('host'),
});
const myUrl = new url.URL(req.originalUrl, baseUrl);
myUrl.searchParams.delete('id');
const cleanUrl = `${myUrl.pathname}${myUrl.search}`;
// redirecting to the URL without the id parameter will return a 400
return res.redirect(cleanUrl);
});
You can easily adapt the code by using the cleanUrl variable so that, instead of redirecting to it, you could take some different action.
Let me know how it goes.

Related

How to use the full request URL in AWS Lambda to execute logic only on certain pages

I have a website running on www.mywebsite.com. The files are hosted in an S3 bucket in combination with cloudFront. Recently, I have added a new part to the site, which is supposed to be only for private access, so I wanted to put some form of protection on there. The rest of the site, however, should remain public. My goal is for the site to be accessible for everyone, but as soon as someone gets to the new part, they should not see any source files, and be prompted for a username/password combination.
The URL of the new part would be for example www.mywebsite.com/private/index.html ,...
I found that an AWS Lambda function (with node.js) is good for this, and it kind of works. I have managed to authenticate everything in the entire website, but I can't figure out how to get it to work on only the pages that contain for example '/private/*' in the full URL name. The lambda function I wrote looks like this:
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
// Configure authentication
const authUser = 'USER';
const authPass = 'PASS';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
The part that doesn't work is the following part:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
// Continue request processing if authentication passed
callback(null, request);
return;
}
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
My guess is that the request.uri does not contain what I expected it to contain, but I can't seem to figure out what does contain what I need.
If you're using a Lambda#Edge function (appears you are). Then you can view the Request Event structure here: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html#lambda-event-structure-request
You can see the actual value of the request URI field by using console.log and checking the respective logs in Cloudwatch.
The problem might be this line:
if (!request.uri.toLowerCase().indexOf("/private/") > -1) {
If you're strictly looking to check if a JavaScript string contains another string in it, you probably want to do this instead:
if (!request.uri.toLowerCase().indexOf("/private/") !== -1) {
Or better yet, using more modern JS:
if (!request.uri.toLowerCase().includes("/private/")) {

Server returns 404 in POST request

I have a project I build with React and Strapi headless CMS (using nodejs). The backend, which is handled using Strapi, resides in port 443. When I send a GET request to any url in backend using http://site:443 I get a valid response, but a POST request always returns an error 404.
I've used an external tool https://reqbin.com/ to test it and got the same results.
The interesting thing is that even though I get 404 response, the route is being handled and executed, and yet gives a 404 response back.
Why could this be happening?
Thanks in advance
Here is the code upon reaching this route:
/**
* Create a/an orders record.
*
* #return {Object}
*/
create: async (ctx) => {
let params = ctx.request.body;
if (params.Sum) { // only if we got a good POST response
// check secret code
let a = params.UniqueID;
var res = a.substr(a.length - 12, a.length);
if (res !== 'secret') {
ctx.throw(500, 'SECURITY BLOCK', { expose: true });
return null;
}
var username = a.substr(0, a.length - 12);
// create validity
var date = new Date();
if (params.Sum === '250') { // one month membership
date.setTime( date.getTime() + 31 * 86400000 );
} else if (params.Sum === '390') { // one month membership
date.setTime( date.getTime() + 180 * 86400000 );
}
// create order in database
let today = new Date();
// extract username
var n = params.CustomerName.indexOf("_");
var name = params.CustomerName.substr(0, n);
var instId = params.CustomerName.substr(n+1, params.CustomerName.length);
const order = await strapi.services.orders.add({
userId: username,
username: name,
institutionId: instId,
sum: params.Sum,
transactionDate: today,
validity: date,
cardNum: params.CardNum,
cardName:params.CardName,
});
let axiosArr = {
institutions: [{_id: instId}],
validity: date,
secret: 'secret'
}
axios.put(apiUrl+'/users/'+username, axiosArr).then(() => {return order})
// return order
Most of the code is irrelevant to the question, I was thinking maybe the reason that the response is 404 is because I'm not returning the order object right away, but only after the axios.put?
Unfortunately I cannot test it right now
If the route is well executed but it returns a 404, it's because the response body is empty.
In your code your commented the return order.
Add in your code something like ctx.send({order}) or un comment your return
You will no longer have a 404.
I'm not familiar with strapi but it doesn't sound like an auth issue to me. Are you sure these routes are set up to accept POST requests and not just GET requests? It's common to return 404s if a user is missing a permission needed to access a resource, to not let the person know that resource exists but without any sample code I can't really say for sure what's happening in your case. Could you link more code?

Pass URL as query parameter in node js

I am trying to hit the URL http://localhost:3000/analyze/imageurl=https://www.google.com/ from my browser.
However, due to the presence of //, it does not correctly hit the URL, and gives me an error message, Cannot GET /analyze/imageurl=https://www.google.com/
If I get rid of the backquotes as follows, http://localhost:3000/analyze/imageurl=httpswww.google.com/, it does work correctly.
My backend API looks like this
app.get('/analyze/:imageurl', function (req, res) {
console.log('printing image url:' + req.params.imageurl);
}
Is there a way I can pass in the imageurl with backquotes as a query parameter?
You need to encode your URL before pass it on query string, using encodeURIComponent. For example:
var urlParam = encodeURIComponent('https://www.google.com/');
console.log(urlParam); // https%3A%2F%2Fwww.google.com%2F
var url = 'http://localhost:3000/analyze/' + urlParam;
console.log(url); // http://localhost:3000/analyze/https%3A%2F%2Fwww.google.com%2F
// Decode it in API handler
console.log(decodeURIComponent(urlParam)); // https://www.google.com/
encodeURIComponent
One approach could be to use Express' req.query in your route. It would look something like this:
// Client makes a request to server
fetch('http://localhost:3000/analyze?imageurl=https://google.com')
// You are able to receive the value of specified parameter
// from req.query.<your_parameter>
app.get('/analyze', (req, res, next) => {
res.json(req.query.imageurl)
})

Cannot read property 'getPayload' of undefined

I am implementing OAuth Google Sign in using backend (written in node.js, express framework at Heroku). Front end is Android and it sends the token id to the server just fine. And server receives the token id correctly.
Here is the code (which is ripped off straight from Google Documents)
var auth = new GoogleAuth;
var client = new auth.OAuth2(CLIENT_ID, '', '');
client.verifyIdToken(
token,
CLIENT_ID,
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3],
function(e, login) {
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
But at times login in undefined. Its so strange that this problem occurs 1/10 rather than for every try so that I am not able to track the source of issue. For every other 9/10 it works just fine.
Any suggestions on how to solve this?
The problem in your code is that you are not checking if your callback get's any error.
The standard way in node.js to use a callback function is using two parameters - error is the first, the actual (success) returned data is the second, and the convention is that if an error exists - you should address it, and you're not gauranteed to get the data, and if everything went well- error will be null and you'll get your data.
So in your code, you are not checking that there's an error (and like you say, not always there's one).
Should be something like:
function(e, login) {
if (e) {
// handle error here
return; // don't continue, you don't have login
}
// if we got here, login is defined
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});
The first parameter to the callback function is an error that needs to handled.
function(error, login) {
if (error) return console.error(error)
var payload = login.getPayload();
var userid = payload['sub'];
// If request specified a G Suite domain:
//var domain = payload['hd'];
});

check on server side if youtube video exist

How to check if youtube video exists on node.js app server side:
var youtubeId = "adase268_";
// pseudo code
youtubeVideoExist = function (youtubeId){
return true; // if youtube video exists
}
You don't need to use the youtube API per-se, you can look for the thumbnail image:
Valid video = 200 - OK:
http://img.youtube.com/vi/gC4j-V585Ug/0.jpg
Invalid video = 404 - Not found:
http://img.youtube.com/vi/gC4j-V58xxx/0.jpg
I thought I could make this work from the browser since you can load images from a third-party site without security problems. But testing it, it's failing to report the 404 as an error, probably because the content body is still a valid image. Since you're using node, you should be able to look at the HTTP response code directly.
I can't think of an approach that doesn't involve making a separate HTTP request to the video link to see if it exists or not unless you know beforehand of a set of video IDs that are inactive,dead, or wrong.
Here's an example of something that might work for you. I can't readily tell if you're using this as a standalone script or as part of a web server. The example below assumes the latter, assuming you call a web server on /video?123videoId and have it respond or do something depending on whether or not the video with that ID exists. It uses Node's request library, which you can install with npm install request:
var request = require('request');
// Your route here. Example on what route may look like if called on /video?id=123videoId
app.get('/video', function(req, response, callback){
var videoId = 'adase268_'; // Could change to something like request.params['id']
request.get('https://www.youtube.com/watch?v='+videoId, function(error, response, body){
if(response.statusCode === 404){
// Video doesn't exist. Do what you need to do here.
}
else{
// Video exists.
// Can handle other HTTP response codes here if you like.
}
});
});
// You could refactor the above to take out the 'request.get()', wrap it in a function
// that takes a callback and re-use in multiple routes, depending on your problem.
#rodrigomartell is on the right track, in that your check function will need to make an HTTP call; however, just checking the youtube.com URL won't work in most cases. You'll get back a 404 if the videoID is a malformed ID (i.e. less than 11 characters or using characters not valid in their scheme), but if it's a properly formed videoID that just happens to not correspond to a video, you'll still get back a 200. It would be better to use an API request, like this (note that it might be easier to use the request-json library instead of just the request library):
request = require('request-json');
var client = request.newClient('https://www.googleapis.com/youtube/v3/');
youtubeVideoExist = function (youtubeId){
var apikey ='YOUR_API_KEY'; // register for a javascript API key at the Google Developer's Console ... https://console.developers.google.com/
client.get('videos/?part=id&id='+youtubeId+'&key='+apikey, function(err, res, body) {
if (body.items.length) {
return true; // if youtube video exists
}
else {
return false;
}
});
};
Using youtube-feeds module. Works fast (~200ms) and no need API_KEY
youtube = require("youtube-feeds");
existsFunc = function(youtubeId, callback) {
youtube.video(youtubeId, function(err, result) {
var exists;
exists = result.id === youtubeId;
console.log("youtubeId");
console.log(youtubeId);
console.log("exists");
console.log(exists);
callback (exists);
});
};
var notExistentYoutubeId = "y0srjasdkfjcKC4eY"
existsFunc (notExistentYoutubeId, console.log)
var existentYoutubeId = "y0srjcKC4eY"
existsFunc (existentYoutubeId, console.log)
output:
❯ node /pathToFileWithCodeAbove/FileWithCodeAbove.js
youtubeId
y0srjcKC4eY
exists
true
true
youtubeId
y0srjasdkfjcKC4eY
exists
false
false
All you need is to look for the thumbnail image. In NodeJS it would be something like
var http = require('http');
function isValidYoutubeID(youtubeID) {
var options = {
method: 'HEAD',
host: 'img.youtube.com',
path: '/vi/' + youtubeID + '/0.jpg'
};
var req = http.request(options, function(res) {
if (res.statusCode == 200){
console.log("Valid Youtube ID");
} else {
console.log("Invalid Youtube ID");
}
});
req.end();
}
API_KEY is not needed. It is quite fast because there is only header check for statusCode 200/404 and image is not loaded.

Resources