I need to add a node.js express middleware for Emails duplicates controls, and a short and easy looking code.
This is my middleware.js file :
// ----------------------------------- MIDDLEWARE FUNCTIONS -------------------------------------------
module.exports = {
/*
* CHECKING FOR DUPLICATE EMAIL
* #params
* #return NEXT()
* #error Status 403s
*/
async duplicate_email(db, req, res, next) {
let availableEmail = await db.collection("users").findOne({ 'email': req.body.email })
if (!availableEmail) {
console.log(" FORBIDDEN ")
res.status(403).send({ errorCode: "403" })
return
} else {
next() // continue the process
}
}
}
And this is my Register web service, in another file called usersCrud.js , I'm struggling since 3 days to make it work but no way :
/*
* Register anonymous user
* #params JSON OBJECT : {}
* #return 200
* #error 400
*/
app.post("/registerUser", middleware.duplicate_email(), function(req, res) {
try {
var user = new User({
_id: null,
prenom: req.body.prenom,
nom: req.body.nom,
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 10),
role: "user",
permissions: middleware.create_permissions("user"),
filenames: [],
groups: [],
last_update: new Date(),
img: "",
birthday: "",
age: "",
job: "",
mentra: "",
});
db.collection("users").insertOne(user);
console.log("Added one user");
res.sendStatus(200);
} catch (e) {
console.log(e);
res.sendStatus(400);
}
});
This is the error :
Error: Route.post() requires a callback function but got a [object Promise]
I have tried tons of things, like adding the db parameter, use a return function, removing all parameters, but I can' t make it work. The code would be really simplier if that thing could work like this, but I don't know if it is possible.
Maybe it is easy to solve for you .
The error message tells you what's wrong, since you invoke middleware.duplicate_email() in your app.post and thus returns a promise. You need to pass the function without invoking it or define it as a factory function that returns the middleware and takes the db object as a parameter. I'd go with the second approach, which would look something like:
module.exports = {
duplicate_email(db) {
return async (req, res, next) => {
let availableEmail = await db.collection("users").findOne({'email': req.body.email})
if (!availableEmail) {
console.log(" FORBIDDEN ")
res.status(403).send({errorCode: "403"})
return
} else {
next() // continue the process
}
}
}
}
You can then use it like this:
app.post("/registerUser", middleware.duplicate_email(db), function(req, res) { ... }
Have you tried to do something like this?
app.post("/registerUser", await middleware.duplicate_email(), function(req, res)
It has to be included in the async function of course to be able to use the await keyword. As the error says, the callback is expected, but you provided a not resolved Promise.
Another thing that may work is:
app.post("/registerUser", async () => {
const result = await middleware.duplicate_email();
return result;
}, function(req, res) {
// stuff
}
Related
I am using TSED - TypeScript Express Decorators (https://tsed.io) and it replaces express code like:
server.get('/api/tasks', passport.authenticate('oauth-bearer', { session: false }), listTasks);
With an annotated middleware class - https://tsed.io/docs/middlewares.html
So now the call to passport.authenticate() is in the use() method like:
#OverrideMiddleware(AuthenticatedMiddleware)
export class UserAuthMiddleware implements IMiddleware {
constructor(#Inject() private authService: AuthService) {
}
public use(
#EndpointInfo() endpoint: EndpointMetadata,
#Request() request: express.Request,
#Response() response: express.Response,
#Next() next: express.NextFunction
) {
const options = endpoint.get(AuthenticatedMiddleware) || {};
this.authService.authenticate(request, response, next); // <-- HERE
if (!request.isAuthenticated()) {
throw new Forbidden('Forbidden');
}
next();
}
}
And then my AuthService.authenticate() is
authenticate(request: express.Request, response: express.Response, next: express.NextFunction) {
console.log(`before passport authenticate time: ${Date.now()}`);
Passport.authenticate('oauth-bearer', {session: false})(request, response, next);
console.log(`after passport authenticate time : ${Date.now()}`);
}
My passport configuration is performed in this same AuthService class:
#Service()
export class AuthService implements BeforeRoutesInit, AfterRoutesInit {
users = [];
owner = '';
constructor(private serverSettings: ServerSettingsService,
#Inject(ExpressApplication) private expressApplication: ExpressApplication) {
}
$beforeRoutesInit() {
this.expressApplication.use(Passport.initialize());
}
$afterRoutesInit() {
this.setup();
}
setup() {
Passport.use('oauth-bearer', new BearerStrategy(jwtOptions, (token: ITokenPayload, done: VerifyCallback) => {
// TODO - reconsider the use of an array for Users
const findById = (id, fn) => {
for (let i = 0, len = this.users.length; i < len; i++) {
const user = this.users[i];
if (user.oid === id) {
logger.info('Found user: ', user);
return fn(null, user);
}
}
return fn(null, null);
};
console.log(token, 'was the token retrieved');
findById(token.oid, (err, user) => {
if (err) {
return done(err);
}
if (!user) {
// 'Auto-registration'
logger.info('User was added automatically as they were new. Their oid is: ', token.oid);
this.users.push(token);
this.owner = token.oid;
const val = done(null, token);
console.log(`after strategy done authenticate time: ${Date.now()}`)
return val;
}
this.owner = token.oid;
const val = done(null, user, token);
console.log(`after strategy done authenticate time: ${Date.now()}`);
return val;
});
}));
}
This all works - My Azure configuration and setup for this logs in and retrieves an access_token for my API, and this token successfully authenticates and a user object is placed on the request.
HOWEVER Passport.authenticate() seems to be asynchronous and doesn't complete until after the test for request.isAuthenticated(). I have put in timing comments as can be seen. The after passport authenticate time: xxx happens 2 milliseconds after the before one.
And the after strategy done authenticate time: xxx one happens a second after the after passport authenticate time: xxx one.
So it looks like Async behaviour to me.
Looking in node_modules/passport/lib/middleware/authenticate.js (https://github.com/jaredhanson/passport/blob/master/lib/middleware/authenticate.js) there are no promises or async mentioned. However in node_modules/passport-azure-ad/lib/bearerstrategy.js (https://github.com/AzureAD/passport-azure-ad/blob/dev/lib/bearerstrategy.js) is an async.waterfall:
/*
* We let the metadata loading happen in `authenticate` function, and use waterfall
* to make sure the authentication code runs after the metadata loading is finished.
*/
Strategy.prototype.authenticate = function authenticateStrategy(req, options) {
const self = this;
var params = {};
var optionsToValidate = {};
var tenantIdOrName = options && options.tenantIdOrName;
/* Some introduction to async.waterfall (from the following link):
* http://stackoverflow.com/questions/28908180/what-is-a-simple-implementation-of-async-waterfall
*
* Runs the tasks array of functions in series, each passing their results
* to the next in the array. However, if any of the tasks pass an error to
* their own callback, the next function is not executed, and the main callback
* is immediately called with the error.
*
* Example:
*
* async.waterfall([
* function(callback) {
* callback(null, 'one', 'two');
* },
* function(arg1, arg2, callback) {
* // arg1 now equals 'one' and arg2 now equals 'two'
* callback(null, 'three');
* },
* function(arg1, callback) {
* // arg1 now equals 'three'
* callback(null, 'done');
* }
* ], function (err, result) {
* // result now equals 'done'
* });
*/
async.waterfall([
// compute metadataUrl
(next) => {
params.metadataURL = aadutils.concatUrl(self._options.identityMetadata,
[
`${aadutils.getLibraryProductParameterName()}=${aadutils.getLibraryProduct()}`,
`${aadutils.getLibraryVersionParameterName()}=${aadutils.getLibraryVersion()}`
]
);
// if we are not using the common endpoint, but we have tenantIdOrName, just ignore it
if (!self._options._isCommonEndpoint && tenantIdOrName) {
...
...
return self.jwtVerify(req, token, params.metadata, optionsToValidate, verified);
}],
(waterfallError) => { // This function gets called after the three tasks have called their 'task callbacks'
if (waterfallError) {
return self.failWithLog(waterfallError);
}
return true;
}
);
};
Could that cause async code? Would it be a problem if run in 'normal express Middleware'? Can someone confirm what I've said or to deny what I've said and to provide a solution that works.
For the record I started asking for help on this Passport-Azure-Ad problem at my SO question - Azure AD open BearerStrategy "TypeError: self.success is not a function". The problems there seem to have been solved.
Edit - the title originally included 'in TSED framework' but I believe this problem described exists solely within passport-azure-ad.
This is a solution to work around what I believe is a problem with passport-azure-ad being async but with no way to control this. It is not the answer I'd like - to confirm what I've said or to deny what I've said and to provide a solution that works.
The following is a solution for the https://tsed.io framework. In https://github.com/TypedProject/ts-express-decorators/issues/559 they suggest not using #OverrideMiddleware(AuthenticatedMiddleware) but to use a #UseAuth middleware. It works so for illustration purposes that is not important here (I will work through the feedback shortly).
#OverrideMiddleware(AuthenticatedMiddleware)
export class UserAuthMiddleware implements IMiddleware {
constructor(#Inject() private authService: AuthService) {
}
// NO THIS VERSION DOES NOT WORK. I even removed checkForAuthentication() and
// inlined the setInterval() but it made no difference
// Before the 200 is sent WITH content, a 204 NO CONTENT is
// HAD TO CHANGE to the setTimeout() version
// async checkForAuthentication(request: express.Request): Promise<void> {
// return new Promise<void>(resolve => {
// let iterations = 30;
// const id = setInterval(() => {
// if (request.isAuthenticated() || iterations-- <= 0) {
// clearInterval(id);
// resolve();
// }
// }, 50);
// });
// }
// #async
public use(
#EndpointInfo() endpoint: EndpointMetadata,
#Request() request: express.Request,
#Response() response: express.Response,
#Next() next: express.NextFunction
) {
const options = endpoint.get(AuthenticatedMiddleware) || {};
this.authService.authenticate(request, response, next);
// AS DISCUSSED above this doesn't work
// await this.checkForAuthentication(request);
// TODO - check roles in options against AD scopes
// if (!request.isAuthenticated()) {
// throw new Forbidden('Forbidden');
// }
// next();
// HAD TO USE setTimeout()
setTimeout(() => {
if (!request.isAuthenticated()) {
console.log(`throw forbidden`);
throw new Forbidden('Forbidden');
}
next();
}, 1500);
}
Edit - I had a version that used setInterval() but I found it didn't work. I even tried inlining the code in to the one method so I could remove the async. It seemed to cause the #Post the UserAuthMiddleware is attached to, to complete immediately and return a 204 "No Content". The sequence would complete after this and a 200 with the desired content would be returned but it was too late. I don't understand why.
I have a mongoose schema like the following:
const User: Schema = new Schema({
// some other fields
email: {type: String, unique: true, require: true, validate: [myValidator, 'invalid email provided'],
// some more fields
)};
My myValidator uses validatorJS isEmail function to check if a valid email address has been entered and looks like this:
function myValidator(email: String) {
return isEmail(email: String, {require_tld: true, domain_specific_validation: true});
So far all of this works as expected. But now I have the following problem, which might be linked to my limited understanding of Typescript/JaveScript.
I would like to extend myValidator to check the entered email address against an external API.
So far I've tried something like this:
function myValidator(email: String) {
let isValid = false;
request('https://disposable.debounce.io/?email=' + email, { json: true }, async (err, res, body) => {
if (err) { return console.log(err); }
isValid = body.disposable;
});
if (isValid && !isEmail(email, {require_tld: true, domain_specific_validation: true})) {
return false;
}
But this obviously fails as request is an async operation so isValid will always be false
So I 'attempted' to make this function an async/await function.
function myValidator(email: String) {
const remoteCheck = function() {
return new Promise(function() {
request('https://disposable.debounce.io/?email=' + email, { json: true }, async (err, res, body) => {
if (err) { return console.log(err); }
return body.disposable;
});
});
};
async function f() {
const isValid = await remoteCheck();
if (isValid === true) {
return false;
} else {
isEmail(email, {require_tld: true, domain_specific_validation: true});
}
}
if(f()) {
return true;
} else {
return false;
}
But this gives me an error, because this function is not assignable to validate.
Please note that this is my first attempt on async/await functions. Could somebody point out what I'm missing here?
UPDATE:
So after I've spend a whole day now to familiarize myself with the concepts of callbacks and promises I came up with a working solution. I'll provide it as an answer below
I checked SO and I couldn't find a solution to this. So if anybody comes across this issue again, I'll provide a working solution for this.
If you want to validate a field against an external data-source, try the following. This validator will check the email-address provided against an API (you can of course use any other API as well) that checks if the address is a DEA and also use ValidatorJS isEmail to check for syntax:
const YourSchema = new Schema({
// your fields
})
SchemaName.path('fieldNameToCheck').validate({
validator: function(value) {
return new Promise(function (resolve, reject) {
request.get('https://disposable.debounce.io/?email=' + value, function (error, response, data) {
if (error) reject(error);
const content = JSON.parse(data);
resolve((content.disposable === 'false' && isEmail(value)))
}
});
}
});
The mistake I made was that I tried to resolve my Promise outside of the callback_function of my request. But the data I want to use is obviously only available inside of it on time.
EDIT:
for the purpose of understanding the API call. The API returns a JSON in the form of {disposable: boolean} whether the entered email-address belongs to a DEA (disposable Email Address) service
What would be the optimal way for error handling?
I need custom json error messages. It's an API.
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.finally(next); //OR return next(error)? redundant?
};
I am no mongoose guy but I know one or two things about express and promise
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
res.status(201).json({message: 'Curso atualizado.', data: data});
}, function(error){
res.status(500).json({message: 'ERROR!'});
})
};
And this is basically all that you need. Based on the implementation, this is probably a normal route because it always returns something (res.json) to the client. Therefore, you don't have to call next because it is meant for middlewares to call
Also you don't have to return anything because when you call res.json, it basically says that this request ends here, nothing else.
Last but not least, by specification, promise then supports 2 functions, the first one is for handing successful case, the 2nd one is for exceptions. So, you don't have to call catch
Considering Curso a mongoose document
You can do it like this
req.curso.save(function(err,data){
if(err) res.status(500).json({message: 'ERROR!'});
else res.status(201).json({message: 'Curso atualizado.', data: data})
});
EDIT : if you have so many similar issues through out your little huge node application, its worth looking at rb, then you can do it like
var RB = require('rb');
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
// the below line could have been written in some middleware (eg middleware provided by express.io), so we do get clear code in controller part.
res.RB = RB.build(res, { // you may customize your builder yours way, after looking into `rb` docs
errorStatus : 500, successStatus : 201,
errorKey : false, successKey : 'data',
preProcessError : function(){ return { message : 'ERROR!' } },
addToSuccess : { message : 'Curso atualizado.' }
});
//Now only one line in controller
req.curso.save(res.RB.all);
};
Disclosure : i am author of rb.
asCallback takes a callback which it calls with the promise outcome mapped to the callback convention:
If the promise is rejected, it calls the callback with the error as first argument: cb(error)
If the promise is fulfilled, it calls the callback with the value as the second argument: cb(null, value).
exports.putCurso = function (req, res, next) {
util.updateDocument(req.curso, Curso, req.body);
req.curso.saveAsync()
.then(function (data) {
return res.status(201).json({message: 'Curso atualizado.', data: data});
})
.catch(function(error) {
return res.status(500).json({message: 'ERROR!'});
//OR return next(error); but I need custom json error messages so it doesn't make sense
})
.asCallback(next);
};
How can I unit test my validations that are done using express-validator?
I have tried creating a dummy request object, but I get the error: TypeError: Object #<Object> has no method 'checkBody'. I am able to manually test that the validation works in the application.
Here is what I have tried:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
My understanding is that the checkBody method is added to the request object when I have app.use(expressValidator()) in my express application, but as I am only testing that the validation is working in this unit test I do not have an instance of the express application available, and the validation method that I am testing is not called directly from it anyway as it is only called through a post route, which I do not want to call for a unit test as it involves a database operation.
Here's a solution for the new express-validator api (v4):
tl;dr: You can use this function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
It can be called like this:
const { validationResult } = require('express-validator/check');
await testExpressValidatorMiddleware(req, res, expressValidatorMiddlewareArray);
const result = validationResult(req);
expect(result....
These solutions assume you have the async/await syntax available. You can use the node-mocks-http library to create the req and res objects.
Explanation:
Each element in an express-validator array is applied to the route as middleware. Say this is your array:
[
check('addresses.*.street').exists(),
check('addresses.*.postalCode').isPostalCode(),
]
Each check will be loaded as middleware.
In order to test middleware, we need to implement a function which acts similarly to how express implements middleware.
Express middleware always accepts three params, the request and response objects, and the next function it should call (next by convention). Why do we need next? For scenarios where we want our middleware to do something before and after the proceeding function, e.g.
const loggerMiddleware = (req, res, next) => {
console.log('req body is ' + req.body);
next();
console.log('res status is ' + res.status);
};
But express-validator doesn't do this, it just calls next() once each of its validators is finished. For that reason, our implementation doesn't really need to bother with next().
Instead, we can just run each of our middlewares in turn and pass an empty function as next to avoid a TypeError:
middlewares.map((middleware) => {
middleware(req, res, () => undefined);
});
But this won't work, because express-validator middleware returns promises and we need to wait for them to resolve...
middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
});
And we don't want to move on until all promises in our iteration are resolved (Mozilla docs on Promise.all are here):
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
And we should extract this as a reusable function:
exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
await Promise.all(middlewares.map(async (middleware) => {
await middleware(req, res, () => undefined);
}));
};
And now we've arrived at my solution. If someone can improve on this implementation, I'm very happy to make edits.
I faced the same issue and I had to create the methods using this:
var validRequest = {
// Default validations used
checkBody: function () { return this; },
checkQuery: function () { return this; },
notEmpty: function () { return this; },
// Custom validations used
isArray: function () { return this; },
gte: function () { return this; },
// Validation errors
validationErrors: function () { return false; }
};
function getValidInputRequest(request) {
Object.assign(request, validRequest);
return request;
}
So, in your code you have to call the getValidInputRequest helper:
describe('couponModel', function () {
it('returns errors when necessary fields are empty', function(done){
var testBody = {
merchant : '',
startDate : '',
endDate : ''
};
var request = {
body : testBody
};
request = getValidInputRequest(request); // <-- Update the request
var errors = Model.validateCouponForm(request);
errors.should.not.be.empty;
done();
});
});
Now, the request object has the body property and all the methods needed by express-validator.
If you want to test the cases that the validator fails, you should use something like this:
function getInvalidInputRequest(request, errorParams) {
// Get de default valid request
Object.assign(request, validRequest);
// Override the validationErrors function with desired errors
request.validationErrors = function () {
var errors = [];
errorParams.forEach(function(error){
errors.push({msg: 'the parameter "'+ error +'" is mandatory'})
});
return errors;
};
return request;
}
And to update the request you should do:
request = getInvalidInputRequest(request, ['mandatory_param_1', 'mandatory_param_2']);
I'm stuck when it comes to returning response, from method that includes database call.
Here's sample of what I need...
service.js
module.exports = {
getUserStatus: function(userId){
return User
.find()
.where({userId: userId)
.exec(function(err, user){
return user.status;
}
}
}
In this service.js, I should fetch user's status and if I console.log(user.status) inside exec method, that is printed OK (I got status).
The problem is I need this result outside service.js:
controller.js
// this code is extracted from longer file, just for demo purpose.
// userService is required 'service.js'
index: function(req, res) {
var status = userService.getUserStatus(req.session.User.id);
console.log(status);
return res.view({userStatus: status});
}
If I console.log(status) here, it will be undefined.
I guess that it has something to do with promises and stuff (because of the async calls), but not sure what is the right way to do it.
getUserStatus contains asynchronous code, so it needs a callback:
module.exports = {
getUserStatus: function(userId, cb){
User.findOne().where({userId: userId}).exec(function(err, user){
if (err) return cb(err);
return cb(null, user.status);
});
}
}
then in the code that uses it:
index: function(req, res) {
userService.getUserStatus(req.session.User.id, function(err, status) {
// If an error was returned from the service, bail out
if (err) {return res.serverError(err);}
console.log(status);
return res.view({userStatus: status});
});
}
Note the use of findOne instead of find; find will return an array.
An alternative would be to return a promise from the service function, and chain your controller code with .then() and .fail():
module.exports = {
getUserStatus: function(userId, cb){
return User.findOne().where({userId: userId});
}
}
index: function(req, res) {
userService.getUserStatus(req.session.User.id)
.then(function(user) {
console.log(user.status);
return res.view({userStatus: user.status});
})
.fail(function(err) {
return res.serverError(err);
});
});
}
It's a matter of preference, but I think the first method is better especially in your case, since it allows the service call to deliver just the status, rather than the whole user object. In general, getting used to the standard (err, result) callback method in Node will serve you well.