I am trying to use cors package in node and express environment to verify if requesting domain can access my resources. That part was not a problem following official documentation. The problem however is with response. If domain is not allowed to access - cors is sending error with stack trace (paths to files). Can this be avoided and just respond with status 401, something like res.status(401).end() ? I tried that but it gives me error because headers were already sent.
const cors = require("cors");
const corsOptions = async (req, callback) => {
let domain = getDomain(req);
if (domain === false) {
return callback(new Error("Not allowed by CORS"));
}
const isWhitelisted = await client.get(domain).catch(err => { console.log(err); });
if (isWhitelisted !== undefined) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
};
app.use(cors(corsOptions));
So i was hoping to replace callback(new Error("Not allowed by CORS")); this part with just sending status 401 and ending stream so no errors with stack are printed in the client.
It can't be done, according to the source code what you are sending in callback as err is passed in the next function like
if(err) next(err);
But there is one easy workaround to what you want.
The cors() is returning a middleware(a function with three params req, res and next). So you can wrap the cors in your middleware and return whatever you want. If all the things are perfect you can use the cors.
const app = require('express')();
const cors = require("cors");
const corsMid = (req, res, next)=>{
const trueVal = true;
if(trueVal){
res.status(400).send("Error")
}else{
return cors()(req,res,next);
}
}
app.use(corsMid);
app.get('/', (req, res)=>{
res.json('Hello World');
})
app.listen(8080, ()=>{
console.log('started')
})
I found a workaround is you can construct the Error on a variable first, then remove the stack property from the error variable:
let err = new Error('Not allowed by CORS')
err.stack = ""
callback(err)
Hope this help!
Related
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
res.json(authData)
}catch(err){
res.status(401);
}
next()
};
exports.verifyAccessToken = async (accessToken) => {
return new Promise((resolve, reject) => {
jwt.verify(accessToken, keys.accessTokenSecret, (err, authData) => {
if(err){
console.log(err)
return reject(createError.Unauthorized());
}
console.log(authData, "accesstoken")
return resolve(authData);
});
})
};
exports.verifyRefreshToken = async (refreshToken, res) => {
return new Promise((resolve, reject) =>{
//const refreshToken = req.body.refreshToken;
jwt.verify(refreshToken, keys.refreshTokenSecret, (err, authData) =>{
if (err) { reject(createError.Unauthorized()); return }
const userId = authData.userId
return resolve(userId)
})
})
};
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh")
res.clearCookie("access")
res.sendStatus(204);
res.redirect("/");
return res.send()
}catch(err){
console.log(err)
}
};
**The routes file has code something like this **
app.get('/api/logout', authService.checkTokenMW,authService.logOut)
I have been trying to tackle this error from a while not exactly sure what header is setting itself multiple times
**Here is the error **
**
> Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
> at ServerResponse.setHeader (_http_outgoing.js:561:11)
> at ServerResponse.header (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:771:10)
> at ServerResponse.append (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:732:15)
> at ServerResponse.res.cookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:857:8)
> at ServerResponse.clearCookie (E:\COURSE-WEBSITE\main-backend\wiz_backend\node_modules\express\lib\response.js:804:15)
> at exports.logOut (E:\COURSE-WEBSITE\main-backend\wiz_backend\controllers\auth-controller.js:131:13)
> at processTicksAndRejections (internal/process/task_queues.js:95:5) {
> code: 'ERR_HTTP_HEADERS_SENT'
> }
**
The problem is inside the middleware function. Once you check for authData you don't need to send it back to response using res.json(authData). Because after that response will be sent and your next() function will be triggered anyways. Since next() will be called your route handler will try to also send another response which is the conflict you face. At the same time, in catch block, you need to have a return statement, so the function execution will be stopped, and it will not reach till next()
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
// res.json(authData) // <- remove this line
}catch(err){
return res.status(401); // <- here
}
next()
};
The specific error you report is caused when your code tries to send more than one response to a given incoming request. You get to send one and only one response per request. So, all code paths, including error code paths have to be very careful to make sure that you are sending exactly one response.
Plus, if you have already sent a response, do not call next() because that will continue routing to other requests and eventually attempt to send some response (a 404 if no other handlers match).
With these in mind, you have several places your code needs fixing.
In your checkTokenWM:
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw")
res.json(authData)
}catch(err){
res.status(401);
}
next()
};
You are calling res.json(authData) and then also calling next(). But, if the purpose of this is to be middleware that continues routing, then you should not be sending any response here if the token passes.
Then, in the catch block, you're setting a status, but not sending a response - that's not technically wrong, but probably not what you want. I'd suggest fixing both of those like this:
exports.checkTokenMW = async (req, res, next) => {
try{
const token = req.cookies["access"]
const authData = await this.verifyAccessToken(token);
console.log(authData, "checkingTokenmw");
// the access token verified so continue routing
next();
}catch(err){
// token did not verify, send error response
res.sendStatus(401);
}
};
In your logout:
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh")
res.clearCookie("access")
res.sendStatus(204);
res.redirect("/");
return res.send()
}catch(err){
console.log(err)
}
};
You are attempting to send three responses upon successful logout and no responses upon error.
First off, res.sendStatus(204) sets the status AND sends an empty response. res.status(204) would just set the status for a future call that actually sends the response if that's what you meant to do. But, with that status, you can't then do res.redirect().
It's not entirely clear what you're trying to do here. If you want to redirect, then that needs to be a 3xx status so you can't use 204. I'm going to assume you want to do a redirect.
I'd suggest fixing by changing to this:
exports.logOut = async (req, res) => {
try{
console.log("----------- logout")
const refreshToken = req.cookies["refresh"];
if(!refreshToken) {throw createError.BadRequest();}
const user = await this.verifyRefreshToken(refreshToken);
res.clearCookie("refresh");
res.clearCookie("access");
res.redirect("/");
}catch(err){
// can't call logout, if you weren't logged in
res.sendStatus(401);
}
};
FYI, most apps won't make it an error to call logout if you weren't logged in. They would just clear the cookies and redirect to home either way as it's really no issue whether they were previously logged in or not. The problem with doing it your way is if the cookies somehow get corrupted, then your code doesn't let the user attempt to clear things up by logging out and logging back in.
So, I'd probably just skip the token check entirely:
exports.logOut = async (req, res) => {
res.clearCookie("refresh");
res.clearCookie("access");
res.redirect("/");
};
And, also I'd change this:
app.get('/api/logout', authService.checkTokenMW,authService.logOut)
to this:
app.get('/api/logout', authService.logOut);
I'm writing a script in Node.js that integrates Todoist, Notion and Discord. I'm using Heroku for hosting because I want to use webhooks (from Todoist). The idea is that every time I add or complete a task, a notification is fired by Todoist to my app and after verifying that the request is legitimate, I update my Notion database and send messages through a discord bot.
The problem I encounter is when I have to check the HMAC SHA256. I'm still quite new to JavaScript and even newer to hashing and secret keys, so please bear with me.
The Todoist documentation says:
To verify each webhook request was indeed sent by Todoist, an X-Todoist-Hmac-SHA256 header is included; it is a SHA256 Hmac generated using your client_secret as the encryption key and the whole request payload as the message to be encrypted. The resulting Hmac would be encoded in a base64 string.
After a lot of research, I tried to use a function I found as the verified answer in another question, but this happens: throw new Error('Malformed UTF-8 data');
This is what I have so far:
const express = require('express');
const Discord = require('discord.js');
const client = new Discord.Client();
var CryptoJS = require('crypto-js');
require('dotenv').config();
const PORT = process.env.PORT || 3000;
app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
process.on('unhandledRejection', error => {
// Will print "unhandledRejection err is not defined"
console.log('unhandledRejection', error.message);
});
function sign_string(message, key){
var secret_key = CryptoJS.enc.Base64.parse(key).toString(CryptoJS.enc.Utf8);
var hash = CryptoJS.HmacSHA256(message, secret_key);
return CryptoJS.enc.Base64.stringify(hash);
}
client.on('ready', () => {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('Hey, The bot is up!'));
});
app.get('', (req, res) => {
res.send('Hello World');
});
app.post('', (req, res) => {
if(req.get('User-Agent') === 'Todoist-Webhooks') {
var delivered_hmac = req.get('X-Todoist-Hmac-SHA256');
var computed_hmac = sign_string(JSON.stringify(req.body), process.env.TODOIST_CLIENT_SECRET);
if(delivered_hmac === computed_hmac) {
if(req.body.event_name === 'item:added' && req.body.event_data.description === '') {
// add task to notion
// idea: ask user for data
} else {
if(req.body.event_name === 'item:completed' && req.body.event_data.description !== '') {
// complete task on notion
}
}
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('You can update your tasklist if you want'));
res.status(200).send('Event handled');
} else {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('A 403 (Unauthorized) status code has been sent to a request'));
res.status(403).send('Unauthorized');
console.log(`delivered_hmac: ${delivered_hmac}\ncomputed_hmac: ${computed_hmac}\n`)
console.log(req.body);
}
} else {
client.users.fetch(process.env.MY_USER_ID).then(user => user.send('A 400 (Bad request) status code has been sent to a request'));
res.status(400).send('Bad request');
}
//handle notion
})
client.login(process.env.BOT_TOKEN);
app.listen(PORT, () => {
console.log(`App up at port ${PORT}`);
});
I am having a bit of an issue returning a JSON response from my Express handling middleware. Currently, I am getting an HTML error page in Postman. On my actual client, I only return a 500 error from the fetch request in the console. The JSON data that should be the error message does not come through as anticipated.
Here is my error handling function. It simply passes the error as a JSON response back to the client. Anytime next(some_error) is called in my controller routes, Express pipes them through this error handling function:
const express = require('express');
const router = express.Router();
exports.errorHandler = (err, req, res, next) => {
res.setHeader('Content-type', 'application/json');
res.status(500).json({ err });
};
module.exports = router;
Here is a portion of the controller route that I am throwing an intentional error in to test the error handling middleware:
if (isMatch) {
const payload = { id: user._id };
jwt.sign(
payload,
JWT_SECRET_KEY,
{ expiresIn: 900000 },
(err, token) => {
if (err) {
const error = new Error(JWT_FAILED);
error.httpStatusCode = 400;
return next(error);
}
payload.token = `Bearer ${token}`;
return res.status(200).json({
success: true,
accountant: payload
});
}
);
} else {
const error = new Error(PASSWORD_INCORRECT);
error.genericError =
'The provided password did not match the database.';
error.httpStatusCode = 400;
return next(error);
}
This is the page I am getting in response for reference:
I am not sure what I am doing wrong, I usually don't have an issue sending a JSON response back from Express. I have a hunch the errors handled by Express require an extra step somewhere to not default to returning as HTML and not JSON.
This fixed my issue. I removed router and added module.exports = errorHandler and this resolved the issue. Express was not calling my errorHandler middleware function. It was just seeing a next(some_error) in my controller routes and then returning the error it's default way. I assumed my errorHandler function was returning this when in fact, my function was never even called.
This is the updated error handling middleware:
const errorHandler = (err, req, res, next) => {
res.json({ err: 'and error' });
};
module.exports = errorHandler;
This now sends back JSON. Phewwww.
Using express framework I did a middleware that handles a URI with a date as parameter.
app.get("/:date",function(req,res){
var result;
var myDate=req.params.date
if(moment(myDate).isValid()){
//some code here
}
else {
//some code here
}
}
and the http is for example:
https://theweb.com/December%2015,%2020
The middleware works well. But if the http finish in % as in
https://theweb.com/December%2015,%
it gives a error:
URIError: Failed to decode param '/December%2015,%'
and what I would like is to convert that error in the display of a message as Bad Request.
You can register an error handler like this:
app.use(function(err, req, res, next) {
console.log('hit error handler');
res.sendStatus(err.status || 500);
});
Note that the function must have 4 arguments, even if you don't use all of them. This function can then respond in whatever way you think is appropriate, in my example I've just sent back the status code. Express sets err.status to 400 for a URIError.
See https://expressjs.com/en/guide/error-handling.html
I had already use this middleware, but no result. It still gives same error when the URI is: https://myweb.com/December%2015,%
URIError: Failed to decode param '/December%2015,%'
Might be is the position of the code
var express = require('express');
var moment=require("moment")
var app = express();
app.use(express.static('public'));
app.use(function(err, req, res, next) {
console.log('hit error handler');
res.sendStatus(err.status || 500);
});
app.get("/", function (request, response,next) {
response.sendFile(__dirname + '/views/index.html');
});
app.get("/:date",function(req,res){
var result;
var myDate=req.params.date //From here comes the error
if(moment.unix(myDate).isValid()){
// some code
result={/*some code*/}
} else if(moment(myDate).isValid()){
//some code
result={/*some code*/}
else {
result={/*some code*/}
}
res.json(result)
})
I'm building a React application and I'm trying to make a call to https://itunes.apple.com/search?term=jack+johnson
I have a helper called requestHelper.js which looks like :
import 'whatwg-fetch';
function parseJSON(response) {
return response.json();
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
export default function request(url, options) {
return fetch(url, options)
.then(checkStatus)
.then(parseJSON);
}
So I get:
XMLHttpRequest cannot load
https://itunes.apple.com/search?term=jack%20johnson. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3000' is therefore not allowed
access.
My express server looks like this:
const ip = require('ip');
const cors = require('cors');
const path = require('path');
const express = require('express');
const port = process.env.PORT || 3000;
const resolve = require('path').resolve;
const app = express();
app.use(cors());
app.use('/', express.static(resolve(process.cwd(), 'dist')));
app.get('*', function(req, res) {
res.sendFile(path.resolve(resolve(process.cwd(), 'dist'), 'index.html'))
});
// Start app
app.listen(port, (err) => {
if (err) {
console.error(err.message);
return false;
}
const divider = '\n-----------------------------------';
console.log('Server started ✓');
console.log(`Access URLs:${divider}\n
Localhost: http://localhost:${port}
LAN: http://${ip.address()}:${port}
${divider}
`);
});
I have tried using mode: 'no-cors' but is not actually what I need since the response is empty.
Am I doing something wrong with this configuration?
The same origin policy kicks in when code hosted on A makes a request to B.
In this case A is your Express app and B is iTunes.
CORS is used to allow B to grant permission to the code on A to read the response.
You are setting up CORS on A. This does nothing useful since your site cannot grant your client side code permission to read data from a different site.
You need to set it up on B. Since you (presumably) do not work for Apple, you can't do this. Only Apple can grant your client side code permission to read data from its servers.
Read the data with server side code instead.