How to attach user credentials to the request pipeline in expressjs? - javascript

I am trying to write a middleware that extracts the user model and attach it to the request pipeline.
I have already written a token extractor middleware and managed to attach the token to the request pipeline, but for some reason when I try to extract the user model, it works fine inside the middleware function yet inside my controller it returns as undefined.
Here's what I have tried:
utils/middleware.js
const tokenExtractor = async (request, response, next) => {
const authorization = await request.get('authorization');
if (authorization && authorization.toLowerCase().startsWith('bearer ')) {
request.token = authorization.substring(7);
} else{
request.token = null;
}
next();
};
const userExtractor = async (request, response, next) => {
tokenExtractor(request, response, next);
if(request.token){
const decodedToken = jwt.verify(request.token, process.env.SECRET);
request.user = await User.findById(decodedToken.id);
console.log(request.user); // Works
next();
} else{
response.status(403).json({ error: 'no token received' });
}
};
Inside my controllers it breaks down:
controllers/blogs.js
blogRouter.post("/", async (request, response, next) => {
if (request.body.title && request.body.url) {
const token = request.token;
if (!token) {
return response.status(401).json({ error: 'invalid token' });
}
console.log(request.user); // undefined !
if(!request.user){
return response.status(401).json({ error: 'invalid user' });
}
const user = request.user;
const blog = new Blog({
title: request.body.title,
author: request.body.author,
url: request.body.url,
likes: request.body.likes,
user: user._id,
});
await blog.save();
user.blogs = user.blogs.concat(blog._id);
await user.save();
response.status(201).json(blog);
}
response.status(400).end();
});
Both middleware are already attached to the express app.
EDIT:
I have fixed the issue by removing the call to tokenExtractor from userExtractor function, and then chaining the middleware to the router instead of calling them before everything.
I was using the tokenExtractor globaly, while the userExtractor locally to the blogsRouter. What was happening was that while the tokenExtractor was working fine, the blogRouters was being called before the userExtractor ever get called, hence why I was getting undefined.
app.js
// app.use(tokenExtractor);
app.use(requestLogger);
app.use(errorHandler);
// app.use(userExtractor);
app.use('/api/login', tokenExtractor, loginRouter);
app.use('/api/users', usersRouter);
app.use('/api/blogs', tokenExtractor, userExtractor, blogRouter); // chaining the extractors

It makes sense, let next() carry the (req, res, next) instances forward, as a pipe. No hacks are needed and you can stack as many middlewares as needed and even reuse values from one inside the other - if you can trust the order of the call stack.

You don't need to chain it. The callback argument for the next middleware function only needs to be specified as follows.
const tokenExtractor = (request, response, next) => {
const auth = request.get('authorization')
if (auth && auth.toLowerCase().startsWith('bearer ')) {
request.token = auth.substring(7)
next()
} else {
next()
}
}

Related

Why I am getting Query was already executed error?

I have the following Schema and Model:
const userSchema = new mongoose.Schema({
name: String,
})
const UserModel = mongoose.model('User', userSchema, 'users')
and I have written the following express middleware, which simply takes one argument, awaits that argument, set the returned value from that awaiting job to the req object on a property called gottenDocs, (i.e.: req.gottenDocs)
function getDocumentsMw(query) {
return async (req, res, next) => {
const dbRes = await query
req.gottenDocs = dbRes
next()
}
}
and I have the following route:
app.get(
'/users',
getDocumentsMw(UserModel.find({})),
(req, res, next) => {
const gottenDoc = req.gottenDocs
res.status(200).json({
status: 'success',
data: gottenDoc,
})
})
That's all I have, now, when I request [ GET " /users " ] I recieve the following response which is great, and nothing is wrong:
{
"status": "success",
"data": []
}
but, the weird thing is when I request this route again, it throws this error:
MongooseError: Query was already executed: User.find({})
What could be the problem? is it a bug in Nodejs? which could be hmmm, something like, that it is not removing the function call from the call stack after the response has been sent?
any help appreciated.
The problem is on this line
getDocumentsMw(UserModel.find({})),
Here you create a query once the application start, because of that the query is created once but executed multiple times.
You may need to refactor your code to something like that
getDocumentsMw(() => UserModel.find({})),
Now you are passing a function and not a query. The function creates a query, aka factory. Next step is to refactor getDocumetnsMw to call the function to create a query when it needs to do something with it.
function getDocumentsMw(queryFactory) {
return async (req, res, next) => {
const dbRes = await queryFactory()
req.gottenDocs = dbRes
next()
}
}

Axios post request failing with a 404

I'm using Axios to query an endpoint in my backend. When I try and do this, I get a 404 not found. If I copy/paste the uri it gives in the error from the console and try and access it directly in the browser it connects fine and does not give me an error (instead giving me an empty object which is expected).
Below is my Axios code
axios.post("/api/myEndpoint", { id: this.userID })
.then((response) => {
this.property = response.data.property;
})
.catch((errors) => {
console.log(errors);
router.push("/");
});
Below is the route definition in my backend
const myEndpointRoute = require('../api/myEndpoint.js')();
exprApp.use('/api/myEndpoint', myEndpointRoute);
For reference, the uri is 'http://localhost:3000/api/myEndpoint'. I can access this uri completely fine in the browser but Axios returns a 404 as described above. It is for this reason that I'm confident this is an issue in the frontend, however I have set up this Axios request in the same way as the many others I have and they all work fine.
Edit: here's the rest of the backend
myEndpoint.js
module.exports = function() {
const express = require('express'), router = express.Router();
const authMiddleware = require('../loaders/authMiddleware.js')();
router.get('/', authMiddleware, async function(req, res) {
const id = req.body.id;
const property = await require('../services/myEndpointService.js')
(id).catch((e) => { console.log(e) });
res.send({ property: property });
});
return router;
};
myEndpointService.js
module.exports = async function(id) {
const results = await require('../models/getMyEndpointProperty')(id);
return results;
};
getMyEndpointProperty
module.exports = async function(id) {
const pool = require('../loaders/pool.js')();
const res = await pool.query(`SELECT * FROM myTable WHERE id = ${id};`);
return res.rows;
};
myEndpoint.js defines only a GET method but your axios call sends a POST in the frontend. Try changing (or adding) the express route:
// notice the `.post`
router.post('/', authMiddleware, async function(req, res) {
...
})
It worked when you manually tested it in the browser for this reason as well, since the browser sent a GET request.

Unit Testing Controllers use Jest, NodeJS

I want to check a case that certain routes are calling the correct controller use Jest specific (mock or spy).
It is case specific for unit testing. Somebody can help me how to check it use jest. I don't need verify kind of
expect (status code or res object) i need to check if controller have been called.
Thanks!
For instance:
// todoController.js
function todoController (req, res) {
res.send('Hello i am todo controller')
}
// index.spec.js
const express = require('express');
const request = require('request-promise');
const todoController = require('./todoController');
jest.mock('./todoController');
const app = express();
app.get('/todo', todoController)
test('If certain routes are calling the correct controller , controller should to have been called times one.', async() => {
await request({url: 'http://127.0.0.1/todo'})
expect(todoController).toHaveBeenCalledTimes(1);
})
Actually if you search, there are many references out there.
In the following, I share a few ways that I know.
One of the big conceptual leaps to testing Express applications with mocked request/response is understanding how to mock a chained
API eg. res.status(200).json({ foo: 'bar' }).
First you can make some kind of interceptor, this is achieved by returning the res instance from each of its methods:
// util/interceptor.js
module.exports = {
mockRequest: () => {
const req = {}
req.body = jest.fn().mockReturnValue(req)
req.params = jest.fn().mockReturnValue(req)
return req
},
mockResponse: () => {
const res = {}
res.send = jest.fn().mockReturnValue(res)
res.status = jest.fn().mockReturnValue(res)
res.json = jest.fn().mockReturnValue(res)
return res
},
// mockNext: () => jest.fn()
}
The Express user-land API is based around middleware. AN middleware that takes a request (usually called req), a response (usually called res ) and a next (call next middleware) as parameters.
And then you have controller like this :
// todoController.js
function todoController (req, res) {
if (!req.params.id) {
return res.status(404).json({ message: 'Not Found' });
}
res.send('Hello i am todo controller')
}
They are consumed by being “mounted” on an Express application (app) instance (in app.js):
// app.js
const express = require('express');
const app = express();
const todoController = require('./todoController');
app.get('/todo', todoController);
Using the mockRequest and mockResponse we’ve defined before, then we’ll asume that res.send() is called with the right payload ({ data }).
So on your test file :
// todo.spec.js
const { mockRequest, mockResponse } = require('util/interceptor')
const controller = require('todoController.js')
describe("Check method \'todoController\' ", () => {
test('should 200 and return correct value', async () => {
let req = mockRequest();
req.params.id = 1;
const res = mockResponse();
await controller.todoController(req, res);
expect(res.send).toHaveBeenCalledTimes(1)
expect(res.send.mock.calls.length).toBe(1);
expect(res.send).toHaveBeenCalledWith('Hello i am todo controller');
});
test('should 404 and return correct value', async () => {
let req = mockRequest();
req.params.id = null;
const res = mockResponse();
await controller.todoController(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({ message: 'Not Found' });
});
});
This is only 1 approach to testing Express handlers and middleware. The alternative is to fire up the Express server.

Using express middle ware and passing res.local data to routes

I am trying to set up a securitycheck middleware that will run on the routes i add it to.
Middleware
function SecurityCheckHelper(req, res, next){
apiKey = req.query.apiKey;
security.securityCheck(apiKey).then(function(result) {
res.locals.Security = result.securitycheck;
console.log(res.locals.Security);
});
return next(); // go to routes
};
Route
app.get('/settings', SecurityCheckHelper,function(req, res, next) {
console.log(res.locals);
});
Its relatively straight forward I'm trying to pass result.securitycheck (which return true/false) into the /settings route. However res.locals is returning an empty object
Your calling next before you have run your securityCheck.
IOW: the next middleware is getting processed before you securityCheck has finished processing. securityCheck is async,.
Placing your next inside your then, will wait until the securityCheck has finished.
eg.
function SecurityCheckHelper(req, res, next){
apiKey = req.query.apiKey;
security.securityCheck(apiKey).then(function(result) {
res.locals.Security = result.securitycheck;
console.log(res.locals.Security);
next();
});
};
You need to interface security check promise with the callback.
function SecurityCheckHelper(req, res, next){
apiKey = req.query.apiKey;
security.securityCheck(apiKey)
.then(function(result) {
res.locals.Security = result.securitycheck;
console.log(res.locals.Security);
return next();
})
.catch(err => next(err));
};

Calling Express Route internally from inside NodeJS

I have an ExpressJS routing for my API and I want to call it from within NodeJS
var api = require('./routes/api')
app.use('/api', api);
and inside my ./routes/api.js file
var express = require('express');
var router = express.Router();
router.use('/update', require('./update'));
module.exports = router;
so if I want to call /api/update/something/:withParam from my front end its all find, but I need to call this from within another aspect of my NodeJS script without having to redefine the whole function again in 2nd location
I have tried using the HTTP module from inside but I just get a "ECONNREFUSED" error
http.get('/api/update/something/:withParam', function(res) {
console.log("Got response: " + res.statusCode);
res.resume();
}).on('error', function(e) {
console.log("Got error: " + e.message);
});
I understand the idea behind Express is to create routes, but how do I internally call them
The 'usual' or 'correct' way to handle this would be to have the function you want to call broken out by itself, detached from any route definitions. Perhaps in its own module, but not necessarily. Then just call it wherever you need it. Like so:
function updateSomething(thing) {
return myDb.save(thing);
}
// elsewhere:
router.put('/api/update/something/:withParam', function(req, res) {
updateSomething(req.params.withParam)
.then(function() { res.send(200, 'ok'); });
});
// another place:
function someOtherFunction() {
// other code...
updateSomething(...);
// ..
}
This is an easy way to do an internal redirect in Express 4:
The function that magic can do is: app._router.handle()
Testing: We make a request to home "/" and redirect it to otherPath "/other/path"
var app = express()
function otherPath(req, res, next) {
return res.send('ok')
}
function home(req, res, next) {
req.url = '/other/path'
/* Uncomment the next line if you want to change the method */
// req.method = 'POST'
return app._router.handle(req, res, next)
}
app.get('/other/path', otherPath)
app.get('/', home)
I've made a dedicated middleware for this : uest.
Available within req it allows you to req.uest another route (from a given route).
It forwards original cookies to subsequent requests, and keeps req.session in sync across requests, for ex:
app.post('/login', async (req, res, next) => {
const {username, password} = req.body
const {body: session} = await req.uest({
method: 'POST',
url: '/api/sessions',
body: {username, password}
}).catch(next)
console.log(`Welcome back ${session.user.firstname}!`
res.redirect('/profile')
})
It supports Promise, await and error-first callback.
See the README for more details
Separate your app and server files with the app being imported into the server file.
In the place you want to call your app internally, you can import you app as well as 'request' from 'supertest'. Then you can write
request(app).post('/someroute').send({
id: 'ecf8d501-5abe-46a9-984e-e081ac925def',
etc....
});`
This is another way.
const app = require('express')()
const axios = require('axios')
const log = console.log
const PORT = 3000
const URL = 'http://localhost:' + PORT
const apiPath = (path) => URL + path
app.get('/a', (req, res) => {
res.json('yoy')
})
app.get('/b', async (req, res) => {
let a = await axios.get(apiPath('/a'))
res.json(a.data)
})
app.listen(PORT)

Categories