With the following controller, how can I call one method from another in the same controller?
Specifically, calling login() within a successful signup(), while retaining the same functionality for login() when it is used by a form?
The line this.login(newUser) does not work, nor does plain old login(newUser)
In both scenarios, I get the error:
TypeError: Cannot call method 'login' of undefined
var mongoskin = require('mongoskin');
module.exports = {
login: (function (req, res) {
req.db.collection('auth').findOne({_id: mongoskin.helper.toObjectID(req.body.id)},
function (err, results) {
// log person in and send results to client
}
)
}),
signup: (function (req, res) {
var user = req.body;
req.db.collection('auth').insert(user, function (err, newUser) {
// after adding user, automatically log them in
// does not work:
//login(newUser, function (err) {
// do something
//})
// does not work:
this.login(newUser, function (err) {
// do something
})
}
)
})
}
Controllers should be doing as little as possible, and should orchestrate the work required by executing functions stored elsewhere.
View this gist - click here
What I have done is created "services" that are not tied to the client request, therefore re-usable everywhere.
Hope this helps.
Thanks to Dave Newton
var mongoskin = require('mongoskin');
var myCollection = 'auth';
Solution
function localLogin(db, myCollection, user, res){
db.collection(myCollection).findOne({_id: mongoskin.helper.toObjectID(user._id)},
function(err, user){
res.send({ token: createToken(user) });
});
module.exports = {
login: (function (req, res) {
var user = req.body;
localLogin(req.db, myCollection, user, res)
},
signup: (function (req, res) {
var user = req.body;
req.db.collection(myCollection).insert(user, function (err, newUser) {
// after adding user, automatically log them in
localLogin(req.db, myCollection, newUser, res)
})
}
) }) }
Related
I have the following web service that we have created.
router.post('/register', (req, res) => {
const user = req.body;
// registerSchema.validate(user, registerSchema, (err, result) => {
// if (err)
// res.status(500).end(err.message);
// });
findUserByEmail(user.email, (err, userFeedback) => {
console.log('Fired.');
if (userFeedback)
res.status(500).end(JSON.stringify("User already exists"));
});
const passwordPromise = util.promisify(bcrypt.hashSync);
const pass = bcrypt.hashSync(user.password);
createUser(user.name, user.email, pass, [], (err) => {
if (err)
res.status(418).end(JSON.stringify("Failed to create user."));
});
res.status(200).end(JSON.stringify("Signup successful."));
});
We are using this to register a user. Here are the methods we are calling in this part of the web service.
function createUser (userName, userEmail, userPass, dev, cb) {
var mg = require('mongodb').MongoClient;
mg.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, function(err, db){
var dbo = db.db(myDB);
var user = { name: userName,
email: userEmail,
password: userPass,
devices: dev };
var insert = util.promisify(dbo.collection("Users").insertOne);
dbo.collection("Users").insertOne(user, function(err, res) {
if (err) throw err;
console.log(`${user.name} has been added.`);
db.close();
sendEmail(userEmail,
'The CRUST Company welcomes you!',
'Thank you for signing up for our services!' );
});
});
}
//See if a user exists
function findUserByEmail (userEmail) {
var mg = require('mongodb').MongoClient;
mg.connect(url, { useNewUrlParser: true, useUnifiedTopology: true },
function(err, db){
var dbo = db.db(myDB);
var query = { email : userEmail };
var find = util.promisify(dbo.collection("Users").find);
return dbo.collection("Users").find(query).toArray(function(err, result) {
if (err) throw err;
db.close();
});
});
}
The createUser seems to be working fine but the FindUserByEmail is not firing at all. We even tried console.log within the method and got no response. Any ideas on why this method may not be firing? Thx
Your calling code passes findUserByEmail() a callback function:
findUserByEmail(user.email, (err, userFeedback) => {
console.log('Fired.');
if (userFeedback)
res.status(500).end(JSON.stringify("User already exists"));
});
But, your actual implementation of that function doesn't accept a callback and thus never calls it so of course the callback you pass never gets called.
Also, there are multiple asynchronous design problems. You are treating asynchronous operations like they are blocking. They are not. You must continue the flow of your code INSIDE the callback function, not after the function call which you do correctly inside of findUserByEmail() but do not do correctly in your request handler where you call it.
Also, createUser() declares a callback as an argument, but never calls it so the callback you pass to createUser() will also never get called.
This is the first time I create an API. I've tried to delete the user items once, the user is removed. I was able to delete the user but i didn't succeed to delete the items.
User.js
express = require('express');
User = require('./user');
Item = require('../item');
router = express.Router();
User.findByIdAndRemove(req.params.id, function(err, user) {
if (err) {
return res.status(500).send('User not found by id.');
}
Item.deleteMany(user._id, function(err, item) {
if (err) {
return res.status(500).send('Item is not found');
}
return res.status(200).send(user, item);
});
});
Is there a way to achieve this? because I have a feeling that I'm doing it the wrong way.
Thanks!
It looks like you are not defining the actual routes -- you need
router.route('/').post(function(req, res){ ... });
You should also include the body parser to get the parameters out of the request
var bodyParser = require('body-parser');
app.use(bodyParser.json());
var parseUrlencoded = bodyParser.urlencoded({extended: false});
The code you have for User methods will look more like the below block. You can change '/' to the URL path you would rather have the api respond to and can change the code from being in .post to .delete depending on what method you want to respond to
route.route('/')
.post(parseUrlencoded, function(req, res) {
// code to respond to 'post' methods
if (!req.params.id) {
return res.send('id not sent')
}
User.findByIdAndRemove(req.params.id, function(err, user) {
if (err) {
return res.status(500).send('User not found by id.');
}
Item.deleteMany(user._id, function(err, item) {
if (err) {
return res.status(500).send('Item is not found');
}
return res.status(200).send(user, item);
});
});
})
.delete(parseUrlencoded, function(req, res) {
// code to respond to 'delete' method
})
I'm new to Node, and Javascript in general. I'm working with the request module to get a JSON response from a web service. I'm not sure if it'd be best to turn my request into a function, or embed the request inside of another function. I haven't had any luck either way.
// Module imports
var express = require('express');
var router = express.Router();
var request = require('request');
var options = {
url: 'https:www.example.com/wow.json',
auth: {
user: 'user',
password: 'pass',
json: true
}
}
request(options, function (err, res, body) {
if (err) {
console.log(err);
return;
}
requestResult = JSON.parse(body); // All data
wowUsage = requestResult.publishers[0].used;
});
// Sending data to the template view
router.get('/', function(req, res, next) {
res.render('template', {tempVar: wowUsage});
});
module.exports = router;
Whenever I start up my web server, this code executes once, and then it's done. Refreshing the page won't load any new information. Should I embed the request in a function, and then call that function in my router.get statement? I tried to nest the request in a function, but I couldn't make that work at all.
If you put the request in a separate function, make sure to add a callback parameter to the new function that gets called with the appropriate data. For example:
function getJSON(callback) {
request(options, function(err, res, body) {
if (err)
return callback(err);
try {
callback(null, JSON.parse(body).publishers[0].used);
} catch (ex) {
callback(ex);
}
});
}
router.get('/', function(req, res, next) {
getJSON(function(err, wowUsage) {
if (err) {
console.log(err.stack);
return res.status(500);
}
res.render('template', {tempVar: wowUsage});
});
});
I'm trying to unit test a simple piece of Express middleware, a cascading athenticator that checks first for a JWT token using a passport-jwt-strategy, and then if that fails, using a passport-openid-strategy. Each of the strategies is already well tested so what I am trying to test is their integration.
The module I am testing looks like this:
"use strict";
let passport = require('passport');
let Strategies = require('./strategies');
let setupDone = false;
// set up passport
let setup = function (app) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (obj, done) {
done(null, obj);
});
passport.use('jwt', Strategies.jwt);
passport.use('openid', Strategies.openId);
app.use(passport.initialize());
app.use(passport.session());
setupDone = true;
};
let authenticate = function (req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
console.log(' cascadingAuthentication');
// first try the token option
passport.authenticate('jwt', function (jwterr, user, info) {
console.log(' jwt auth', jwterr, user, info);
if (jwterr || !user) {
passport.authenticate('openid', function (oautherr, user, info) {
if (oautherr || !user) {
return next(oautherr);
} else {
next();
}
});
} else {
req.user = user;
next();
}
});
};
module.exports = {
setup: setup,
authenticate: authenticate
}
My Jasmine test looks like this
"use strict";
let CascadingAuthentication = require('../../lib/middleware/cascadingAuthentication');
let TokenUtils = require('../support/tokenUtils');
let email = 'testing#test.tes';
describe('cascadingAuthentication', function () {
describe('when there is a token in the header', function () {
let req;
let res = {};
let app = {
use: function (used) { console.log('app.use called with', typeof used); }
};
beforeEach(function (done) {
let token = TokenUtils.makeJWT(email);
req = {
app: app,
header: {
Authorization: `Bearer ${token}`
}
}
CascadingAuthentication.setup(app);
CascadingAuthentication.authenticate(req, res, function () {
done();
});
});
it('populates req.user', function () {
expect(req.user).toEqual(jasmine.any(Object));
});
});
});
The issue I have is that, when I run the test, I see the first console.log(' cascadingAuthentication') but I never see the second console.log('jwt auth', err, user, info). The code just dies inside passport.authenticate without ever calling the callback, without raising an error, or without providing any kind of feedback at all.
I'm running my tests via gulp using Jasmine.
My questions are: in order,
Can you see anything obvious that I have done that I might have just missed?
Is there anything else I ought to mock out in my req, res, or app that might make this test work?
Is there any way to debug this interactively; stepping through the code under test as it runs, rather than just adding console.log statements (which seems a little 1980s to me).
Digging through passport's source I have worked out there were two problems with my code.
The first is that passport.authenticate returns a middleware function, it doesn't actually execute that function. So the solution was simply to call the returned function.
So my authenticate method now looks like:
let authenticate = function(req, res, next) {
if (!setupDone) throw new Error('You must have run setup(app) before you can use the middleware');
// first try the token option
passport.authenticate('jwt', function(jwterr, user, info) {
if (jwterr || !user) {
passport.authenticate('openid', function(autherr, user, info) {
if (autherr || !user) {
return next(autherr);
} else {
next();
}
})(req, res, next);
} else {
req.user = user;
next();
}
})(req, res, next);
};
(The above example is trimmed for use in the question)
The other issue was in my test I used header instead of headers in my mock req object, and also authorization ought to have had a lower case a.
With those two fixes the test now passes.
I fiddled with this for quite some time and eventually landed on the following setup (to test passport.authenticate('local', () => {})).
auth-router.js
const express = require('express');
const passport = require('passport');
const login = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) {
next(err);
return;
}
if (!user) {
const error = new Error(info.message);
error.status = 404;
next(error);
return;
}
// Add the found user record to the request to
// allow other middlewares to access it.
req.user = user;
next();
})(req, res, next);
};
const router = express.Router();
router.post('/auth/login', login);
module.exports = {
login,
router
};
auth-router.spec.js
const passport = require('passport');
describe('login', () => {
it('should login and add the user to the request object', (done) => {
spyOn(passport, 'authenticate').and.callFake((strategy, callback) => {
const err = null;
const user = {};
const info = {};
callback(err, user, info);
return (req, res, next) => {};
});
const auth = require('./auth'); // my middleware function
const req = { body: {} };
const res = {};
const next = () => {
expect(req.user).toBeDefined();
done();
};
auth.login(req, res, next);
});
});
Building an API with node and express. In my "home" route i set a session with a users id.
When i want to add and update information on a user i want to access the session to know which user to update. In my get routes i can access the session, but in my route with put method its always undefined. Why is this?
app.get('/users/:id/spots', spot.findSpotsByUserId); //I set the session in this method
app.get('/spots/:id', spot.findById);
app.put('/userspot/spot/:spotId/add'', spot.addUserSpot);
exports.findSpotsByUserId = function(req, res) {
var id = req.params.id; //Should ofc be done with login function later
db.collection('users', function(err, collection) {
collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, user) {
if (err) {
res.send({'error':'Couldnt find user'});
} else {
req.session.userId = id;//<----- sets session
console.log("SESSION",req.session.userId);
}
......}
exports.findById = function(req, res) {
var id = req.params.id;
console.log('Get spot: ' + id);
console.log("SESSION!",req.session.userId);// <----prints the id!
db.collection('spots', function(err, collection) {
collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) {
res.send(item);
});
});
};
exports.addUserSpot = function(req, res) {
var user = req.session.userId;
var spot = req.params.spotId;
console.log("SESSION!",req.session.userId);// always UNDEFINED!
//........}
You are looking for req.params.userId, not req.session.
The session is persisted between multiple calls, and it has no connection to the params object. You can set req.session.userId in a previous call and access it here, but I don't think this is what you want.
Try this:
exports.findById = function(req, res) {
req.session.test = "from findById";
...
};
exports.addUserSpot = function(req, res) {
console.log(req.session.test, req.params.userId);
...
};