Returning a 403 response from a node/express app. The browser network inspector works as expected, but axios "catch" does not execute.
Axios "then" is executing and with a undefined response.
If I return 200 instead of 403, the response object doesn't get undefined value.
Thoughts ?
Middleware:
function authorize(permissions) {
return async function (req, res, next) {
if (await req.user.can(permissions)){
return next();
}
return res.status(403).json({message: 'Not enough permissions', permissionsRequired: permissions});
};
}
Route:
router.get('/', authorize('read_user'), async (req, res) => {
return await simplePaginateModelJson(req, res);
});
Client side:
const getData = (searchFields = {}, page = 1) => {
simplePaginateList(props.modelName, searchFields,{page: page, limit: props.itemsCountPerPage}).then(r => {
console.log('res', r);
}).catch(err => {
console.log('err', err);
});
}
Network:
Console:
Related
What I'm trying to do is to test a REST API endpoint that validates requests with req.session (set by express-session).
Here are snippets of my code:
middleware.ts
export const validateSecurityCode = (req, res, next) => {
// How do I mock/spy on this "req.session.securityCode"?
if (req.session.securityCode !== req.body.securityCode) {
throw new BadRequestError('Wrong security code was provided');
}
return next();
};
api/item.ts
router.post('/item', [validateSecurityCode], async (req, res) => {
await createItem(req.body);
res.send({ message: 'DONE' });
});
As you can see, if req.session.securityCode !== req.body.securityCode, this endpoint has to throw an error.
Is it possible to mock/spy on req.session while testing the endpoint with jest and supurtest?
Below is what I'd like to achieve:
it('should pass security code check', async () => {
// " MOCK_REQ_SESSION('valid-code')" will ensure that "req.session.securityCode" will get 'valid-code'
validateSecurityCode = MOCK_REQ_SESSION('valid-code');
const response = await request(app).post('/api/item').send({
securityCode: 'valid-code',
// other request body values
});
expect(response.statusCode).toEqual(200);
});
As the title suggest, I get a weird error when responding with data from my server.
In homepage.js (which I want to load after loggin in) I have this request to the server to get the posts and then set the posts to the response.
useEffect(() => {
//userService.getDashboard() === Axios.get('http://localhost:3001/homepage')
userService.getDashboard().then((response) => {
setListOfPosts(response)
});
}, []);
This request first goes to the homepage.js, which further sends a request to getPosts, like so:
const headers = req.headers;
const getPosts = Axios.get('http://localhost:3001/getPosts', {headers: headers});
getPosts.catch((response) => {
//NEVER GET ANY RESPONSE???
console.log('Error in homepage.js')
//res.send(response);
});
getPosts.then((response) => {
//NEVER GET ANY RESPONSE???
res.send(response.data);
});
And lastly in the chain I have the getPosts router which does:
router.get('/', authenticateToken, async (req, res) => {
await db.query('SELECT * FROM posts',
(err, result) => {
if (err) {
console.log('HELLO FROM ERROR')
res.send({errorMessage: err});
} else {
console.log(result)
res.send(result);
}
});
});
So I can confirm that after every request to homepage I get all the way to getPosts() and the database query always works fine and goes into the result where "console.log(result)" lies and I can confirm that the result is indeed all the posts. The weird stuff happens when I'm sending back the data. So from getPosts() I'm obviously doing a res.send(result) which sends the data back to homepage.js. But this is when I get the error "UnhandledPromiseRejectionWarning: Error: Request failed with status code 304"
Any idea why?
you should not use res.send inside the .then of axios
this code works for me
useEffect(() => {
getPosts.then((response) => {
console.log("inside getPosts.then ");
console.log(response);
});
and this is my controller file to send request to backend:
const axios = require("axios");
export const getPosts = axios.get("http://localhost:5000/tasks/taskscheck");
getPosts.catch((response) => {
console.log("Error in homepage.js");
});
getPosts.then((response) => {
console.log("inside then get posts");
console.log(response);
});
I have tasks project and I can see in the response all my tasks.
I have an asynchronous function that is called when the api is connected to. this should return some json and then it will be displayed on the json response of the page. In the json response I get undefined.
This is the code i am using:
const express = require('express');
const router = express.Router();
const superagent = require('superagent');
function getCyrpto(){
var result;
superagent.get('https://min-api.cryptocompare.com/data/v2/pair/mapping/exchange?e=Kraken')
.query({ api_key: 'xxxxxxxx'})
.end((err, res) => {
if (err) { return console.log(err); }
result = res.body;
});
setTimeout(() => {
console.log(result);
return result;
}, 2000)
}
router.get('/', (req, res, next) => {
crypto=getCyrpto()
setTimeout(()=> {
res.status(200).json({
message: 'geting cyrpto',
apiResponse: crypto
});
}, 2500)
});
The reason it is happeing because your setTimeOut methods runs before your api call get the result and assign it to the result.
This is a common problem most of us face when we start to learn concurrency concept.
For example:
console.log("a");
setTimeOut(()=>console.log("b"),1000);
console.log("c");
Output of above function will
a
c
b
this is happening beacause setTimeout function is a promise which means your nodejs will not wait for it to finish before running the next line, it will just process the setTimeout function in background and when it will finish it will call its callback function given as first parameter in setTimeOut.
Your solution should be
function getCyrpto(){
return new Promise((resolve,reject)=>{
var result;
superagent.get('https://min-api.cryptocompare.com/data/v2/pair/mapping/exchange?e=Kraken')
.query({ api_key: 'xxxxxxxx'})
.end((err, res) => {
if (err) { console.log(err); reject(err); }
result = res.body;
setTimeout(() => {
console.log(result);
resolve(result);
}, 2000)
});
}
router.get('/', (req, res, next) => {
getCyrpto().then(crypto=>{
setTimeout(()=> {
res.status(200).json({
message: 'geting cyrpto',
apiResponse: crypto
},2500);
}).catch(err=>{res.status(400).json(err)})
}
Test
it('should fail trying to GET bookmarks with false user id',async () => {
try {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
} catch (e) {
console.log(e); //it doesn't reach this point
expect(e.httpStatus).to.equal(HttpStatus.UNAUTHORIZED);
}
});
The relevant part of the method under test:
/* GET bookmark of user */
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
userIdTokenValidator.validateUserIdInToken(request);
...
}));
where wrapAsync makes sure the error is passed to the custom error handler:
let wrapAsync = function (fn) {
return function(req, res, next) {
// Make sure to `.catch()` any errors and pass them along to the `next()`
// middleware in the chain, in this case the error handler.
fn(req, res, next).catch(next);
};
}
The validateUserIdInToken method which causes the method under test to throw an exception:
const AppError = require('../models/error');
const HttpStatus = require('http-status-codes');
let validateUserIdInToken = function (request) {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
throw new AppError(HttpStatus.UNAUTHORIZED, 'Unauthorized', ['the userId does not match the subject in the access token']);
}
}
module.exports.validateUserIdInToken = validateUserIdInToken;
and the custom error handler in the root middleware:
app.use(function(err, req, res, next) {
if (res.headersSent) {
return next(err)
}
if(err instanceof AppError) { //execution lands here as expected and the test stops...
res.status(err.httpStatus);
return res.send(err);
} else {
res.status(err.status || HttpStatus.INTERNAL_SERVER_ERROR);
res.send({
message: err.message,
error: {}
});
}
});
I think you may be approaching this incorrectly. Invalid auth should not raise errors in the app - it's not an error really, is a validation issue.
If the auth fails, simply send the relevant http error code - 401 back to the client.
res.send(HttpStatus.UNAUTHORIZED, 'a message if you want'); // 401
In your route handler:
personalBookmarksRouter.get('/', keycloak.protect(), wrapAsync(async (request, response) => {
const userId = request.kauth.grant.access_token.content.sub;
if ( userId !== request.params.userId ) {
return response.send(HttpStatus.UNAUTHORIZED);
}
...
}));
In your test, check the for status 401:
chai.request(server)
.get('/false_user_id/bookmarks')
.end((err, result) => {
if (err) {
return callback(err);
}
result.should.have.status(401);
});
Thanks to #laggingreflex's comment I missed debugging that the response actually returned with the expected status and error message
The adjusted test case now looks like this:
it('should fail trying to GET bookmarks with false user id',async () => {
const response = await request(app)
.get(baseApiUrlUnderTest + 'false_user_id/bookmarks')
.set('Authorization', bearerToken);
expect(response.status).to.equal(HttpStatus.UNAUTHORIZED);
});
This is my code:
const functions = require('firebase-functions');
const axios = require('axios');
const cors = require('cors')({ origin: true });
exports.apistatus = functions
.region('europe-west1')
.https.onRequest((req, res) => {
cors(req, res, () => {
if (req.method !== "GET") {
return res.status(401).json({
message: "Not allowed"
});
}
return axios.get('https://api.bsmsa.eu/ext/api/bsm/gbfs/v2/en/station_status')
.then(response => {
console.log(response.data);
return res.status(200).json(
response.data
)
})
.catch(err => {
return res.status(500).json({
error: err
})
})
})
// ! res.end();
});
I'm already using the Blaze Plan but I always get this when I invoke it >> Function execution took 60002 ms, finished with status: 'timeout'.
If I try to terminate the function like the documentation says with res.send() or res.end() I get this error: Error: Can't set headers after they are sent.
It works fine when I try to fetch other APIs.
You need a return keyword before the cors() function call.
exports.apistatus = functions
.region('europe-west1')
.https.onRequest((req, res) => {
return cors(req, res, () => { // <------ RETURN NEEDED HERE
if (req.method !== "GET") {
return res.status(401).json({
message: "Not allowed"
});
}
return axios.get('https://api.bsmsa.eu/ext/api/bsm/gbfs/v2/en/station_status')
.then(response => {
console.log(response.data);
return res.status(200).json(
response.data
)
})
.catch(err => {
return res.status(500).json({
error: err
})
})
})
});
Your axios code is being executed, and I suspect it's receiving a response, but because you aren't returning the result from CORS, the "parent" cloud function never knows the task has finished.
This example shows a sample from the official Firebase GitHub where they're using Cors.