Migrating Node JS code to Apollo server - node.js

I am setting up Apollo Server on my Node app and wondered about moving the functionality over to Apollo.
I have business logic like this:
router.post(
'/login',
(req, res, next) => {
if (!req.body.email || !req.body.password) {
return 'You must send the username and the password.';
}
Users.findOne({ email: req.body.email })
.then(user => {
bcrypt.compare(req.body.password, user.password, (err, success) => {
req.user = user;
next();
});
})
},
auth.createToken,
auth.createRefreshToken,
auth.logUserActivity,
(req, res) => {
res.status(201).send({
success: true,
authToken: req.authToken,
refreshToken: req.refreshToken
});
}
);
It follows Node router architecture where I add the found user object to req object, which passes the user to the next functions - createToken etc.. using the next() function. This was ok for my server before trying to introduce GraphQL/Apollo, but now I want all this logic to be easily accessible to the Apollo resolvers.
I often hear that people are having an easy time turning their server from REST/non-GraphQL into a GraphQL server, but at the moment it's looking like it's going to be a bit of a job to go through all the logic and separate everything in to their own functions which take parameters directly rather than using the req object.
Is this a correct assumption? Or am I missing something?
Thanks!

Migrating the code you have shown above would be a very easy task. Once you build your graphql server and create your schema, etc. Then all you need to do is create login mutation. Then your resolver would handle the logic you have shown above. Then, instead of pulling the values from from req.body they would be function parameters.
A good pattern I am currently following is creating a login method on the model itself. Then the resolver calls the method on the schema (Here is an example of a project I'm doing it on now: Login method. Then here is an example of what the resolver looks like: Resolver
Hopefully that helped!

Related

Nock isn't matching HTTPS call inside middleware?

I have a piece of middleware that performs authentication with a third-party service via request. I do this request with superagent. Obviously I want to mock this in my tests since it slows them down quite a lot and also is dependant on the third-parties server.
When using nock, it doesn't seem to find the request at all. I even tried using the recorder and it only picks up the actual requests of my local endpoints. (Although it uses an unfamiliar IP and port?).
The request inside my middleware;
export default async (req, res, next) => {
const user = await superagent
.get(`https://example.com/session/`)
.query({ session })
.set('Api-Key', '1234');
}
My Nock Instance;
nock('https://example.com/session/')
.persist()
.get('/session/')
.reply(200, {
success: true,
username: 'testuser',
})
.log(console.log);
You have 2 issues here.
First off, you define twice /session/:
nock('https://example.com/session/')
.get('/session/')
Choose:
nock('https://example.com').get('/session/')
nock('https://example.com/session/').get('/')
Second issue, you're adding a query string to your call (.query({ session })), but you don't tell that to Nock using .query(true).
In the end, you should have something like:
nock('https://example.com/session/')
.persist()
.get('/') // rewrote here
.query(true) // added here
.reply(200, {
success: true,
username: 'testuser',
})
.log(console.log);

Simple Express JS API token

I'm wonder how I can implement a simple API auth token without need for users? I just want one token that I can authenticate by adding it as a parameter when I call my API.
All the current examples on Google seem to be over engineered for my needs. I have some data stored in MongoDB and I simply serve this data like so:
app.get("/", (req, res) => {
Car.find((err, cars) => {
if(err){
throw err;
}
res.json({"cars": cars});
});
});
Is it possible to add some simple middleware that checks my environment file for an element with the name of api_token. Then check that the api_token in my env file matches the parameter that has been passed as a URL query.
Is there a way to do this? I'm aware that you can't use URL queries on a GET route so I am unsure how this would work.
Sure, use middleware: https://expressjs.com/en/guide/using-middleware.html
For your case, it can be as simple as the following:
// checkForApiToken.js
module.exports = (req, res, next) => {
const apiToken = req.header("api-token");
if (process.env.API_TOKEN !== apiToken) {
next(new Error("Unauthorized."));
return;
}
next();
}
The logic is simple:
Retrieve API-TOKEN value from the header.
Check it matches what I've defined in my env.
Does not match, throw an error by passing an error object into the next function.
Matches so I call next() with no error to proceed to the next request handler.
You would then use it like so:
app.get("/", checkForApiToken, async (req, res) => {
const cars = await Car.find().exec();
res.json({ cars });
});
Remember, Tokens are responsible for at least 2 API security mandatory things, authenticate and authorize. You don't need to authenticate users, but you need to be sure that the token you received is a Token and not a "HEADER".
If you use a static token,or anything else, first time i get your token your security is down. You need to specify AT LEAST when this token will die, and if it is a valid one based on some random assignment. You can't check for it's presence, you need to check if it is valid.
Javascript has an amazing convention background, whenever you have the opportunity, follow the convention. It is easier as it seems to implement a JWT based.
Just follow this : https://github.com/auth0/node-jsonwebtoken
and implement it in your middleware as you wishh.
Easily as this /
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
You can always redo the concepts by using a "Web HEADER" and calling it a "TOKEN". But as i said, it is a "Web Header" not an "WEB TOKEN".

I am wondering how to communicate between controllers

I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in the e-mail to implement the membership method using e-mail authentication.
//emailcontroller.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
request('http://localhost:8080/api/user', function(data) {
});
}
});
//usercontroller.js
router.post('/api/user', function(req, res) {
var user = new User();
user.userId = req.body.userId;
user.userPw = req.body.userPw;
user.save();
});
I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in email in order to implement membership method using email authentication.
As mentioned above, the email controller and the user controller are divided and each is routed. I want to modularize the code so that I want to call the existing user creation API to use it for general purpose rather than creating and exports common functions for a specific controller.
/*I do not want to implement it this way.*/
//emailController.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
userContoller.createUserFromEmail(userId, userPw);
}
});
//userController.js
exports.createUserFromEmail = function(userId, userPw) {
var user = new User();
user.userId = userId;
user.userPw = userPw;
user.save();
}
However, I have never seen communication between controllers in many examples. So I do not know if the way I thought was right. Rather, I think the cost of calling api internally on the server might be higher.
I want to know the correct pattern for communication between controllers. Please bear in mind that there is only a stack overflow when raising a question.
You got the right idea about exposing your API functionality as stand-alone functions (or classes). To avoid duplication, just call your internal methods from within your route handlers. So in your example:
router.post('/api/user', function(req, res) {
createUserFromEmail(req.body.userId, req.body.userPw);
});
In my own projects, I use classes to create my API. First I define a class with just the functionality and then I expose the methods in the route handlers:
export default class User {
read() {
}
create() {
}
update() {
}
delete() {
}
}
const user = new User();
router.get('/user/:id', (req, res) => user.read(req.params.id));
router.post('/user', (req, res) => user.create(req.body.data));
router.put('/user/:id', (req, res) => user.update(req.params.id, req.body.data));
router.delete('/user/:id', (req, res) => user.delete(req.params.id));
This should give you an idea of what you can do. You can write custom middleware and class decorators to reduce the boilerplate.
From your question what I understood:
You want to validate internally the token passed in query parameter, before doing anything else in the user controller.
I believe you are using express, and with express comes middlewares.
From docs:
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
What I usually do and a generally good practice is, pass the token in create user api and attach to email body.
for example:
api/user?token=somerandomstringloremispum
Route file:
router.post('/user', validateEmail, userController.create);
here validateEmail is a middleware function and will be invoked before create method of userController.
Now in your validateToken method, you can simply validate your token like:
function validateEmail (req, res, next) {
if(!valid(req.query.token)) {
//return with appropriate invalid token msg using res.json() or however you like
}
// if validated call `next middleware` like so:
next();
// this will allow `create` method of userController be invoked
}

How to handle authorization in a layered nodejs with passport app?

So I'm trying to build an app with nodejs, using express and passport, but as I try to do some kind of TDD, I want to decouple bussiness logic from controllers.
So I have a common scenario like this:
An authenticated user wants to delete an item, he sends a request to the api:
DELETE /api/item/1
The request is handled by the controller method, which passes the user that makes the request to the next layer (which doesn't seem like a good approach):
exports.delete = function (req, res, next) {
var itemId = req.params.id;
var userId = req.user._id;
itemService.delete(itemId, userId, function (err, item) {
if (err) next(err);
return res.json(item);
});
};
The service layer (or whatever you want to call it, the layer that has all the bussiness logic) then checks if the item is owned by that user, and then deletes it or returns an error otherwise.
So I was wondering if there is any way to get the current user from any layer without passing it from the controller.
You should ensure the user owns the item before even passing it to the controller, in the routes configuration:
app.del('/api/item/1', ensureUserOwnsItem, itemController.delete);
This will cause the function ensureUserOwnsItem to be called before calling the controller.
It should looks like this:
function ensureUserOwnsItem(req, res, next) {
if (/* user owns item */) {
next();
} else {
res.send(401, 'You can\'t delete an item you don\'t own');
}
}
You would be able to reuse it on the POST route:
app.post('/api/item/1', ensureUserOwnsItem, itemController.post);
I recommend you put this function inside an AuthController or something like that.

Node - Passport Auth - Authed Post Route hangs on form submission

This is a weird one. Im Passport's 'Local Strategy' for my express app and i'm running into an odd issue.
Essentially, I have three routes. Each have an auth check in place.
app.get('/admin', authenticatedOrNot, adminRoute.index);
app.get('/admin/new', authenticatedOrNot, adminRoute.newpost);
app.post('/admin/new', authenticatedOrNot, adminRoute.create);
the authenticatedOrNot method is simply :
var authenticatedOrNot = function(req, res, next){
if(req.isAuthenticated()){
next();
}else{
res.redirect("/login");
}
}
Works perfect for logging in to the admin area, and checking if a user is logged in, BUT when I submit a form to the '/admin/new' Post route, the browser hangs. Nothing happens in the console, even with console.log in place :
exports.create = function(req, res){
console.log(req);
// Database logic here
res.redirect('/admin');
}
I cant seem to get it to work. It just hangs, and eventually fails. The browser console just says 'pending' in the network request.
Ive tried removing the 'authenticatedOrNot' method from the post route and same issue, but if I remove all three it works fine.
Im stumped.
Any help guys? Anyone else run into this?
I had a problem very similar to this, so I'm posting this in case it helps out.
The issue seemed to be that i had another function definition inside the passport function, and this was preventing the done handler from being called. I think that was the issue because when I changed the function argument names things started working.
In hindsight I think the error is obvious, but since I'm new to node I'm still a bit uncertain with functions, callbacks, closures, etc, etc. I also have the impression that the node convention is always to use these argument names (err, done, next) and that there is some magic associated with them. I guess not though. Feel free to educate me on this point.
Anyway, I was using a passport local strategy that I copied from a tutorial (at http://scotch.io/tutorials/javascript/easy-node-authentication-setup-and-local).
The tutorial used mongo, but I decided to switch to postgresql. So I used the pg.js module from https://github.com/brianc/node-postgres-pure, and used the sample code provided.
Here's the relevant portion of the code, after I initially copied and pasted the pg.js sample code into the passport tutorial:
//Bad Code
passport.use('local', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) {
pg.connect(configDB.connectionString, function(err, client, done) {
if (err) {
return console.error('could not connect to postgres', err);
}
client.query('select email, password_hash from admin_user where email = $1', [email], function(err, result) {
// check password against db, and then try to call passports done callback
return done(null, userModel); // this actually invokes the pg.connect done callback
});
});
}));
So when this ran, on the post back to /login, the call to done would invoke pg.connect done, not passport done.
// Good? working code
function(req, email, password, done) {
pg.connect(configDB.connectionString, function(err, client, connect_done) {
if (err) {
return console.error('could not connect to postgres', err);
}
client.query('select email, password_hash from admin_user where email = $1', [email], function(err, result) {
connect_done() // free up postgres connection, which I should have been doing before
// check password against db, and then
return done(null, userModel); // invoke passport's done callback
});
});
}));
This code is now working for me (unless I mis-copied something).
Diagnostic of such a trouble become much more easy when you split more and more and more... Best approach is to use some sniffer (built in Chrome, Firefox, Opera or standalone) and get exactly the headers you sent on to your server. This is very useful since you can localize trouble to frontend app (<form acton="/admin/new" – mistype for example) or backend.
Lets apologize your headers are okay and you send exactly POST at /admin/new route. Since your console.log( req ); does not take effect obviously application does not come to this point. This could be because authenticatedOrNot hangs or because adminRoute.create is not instantiated correctly.
authenticatedOrNot could hang on /login redirection as I see, since you did not provide the way how you handle this route.
adminRoute.create could cause some troubles depending on the way you attached it into your app.
So in resume I need to see more of your code to establish the trouble.

Resources