sails.js - unique model attribute check - node.js

Project:
API over websockets (socket.io)
My goal:
check if a username is unique before storing it
My solution:
Overwrite default createUser function
My problem:
calling User:create doesn't do the validation
code in the user controller:
create: function(req, res, next){
User.findByUsername(req.param('username')).then(function(usr){
if(usr !='')
{
// username already taken
return res.json({'type':'validationError','errorMessage':'Username already taken','usr':usr});
}
else{
// username is unique
User.create( req.params.all(),function userCreated(err,user){
// try to create user
if(err){
console.log(err);
return res.json({
'type':'validationError',
'errorMessage':err}
);
}
res.json(user);
});
}
},
function(err){
// error finding user by username
}
);

I believe approaching the problem in a slightly different way could potentially make this easier for you. You can handle the validation in the model:
attributes: {
username: {
type: 'string',
unique: true
}
}
Then, without overriding anything in the controller you should be able to attempt to create a user via websocket, and handle the error that is returned if you attempt to use a non-unique username.

Related

Firebase Admin SDK - Create user with custom fields

I've been following the Firebase SDK Admin - Manager User docs and everything went find for me with it's system. Either way, i want to extend my users information with fields such as first name, last name, nacionality, etc etc.
Right now i'm using Vue.JS (fronted), Axios (Http client) and Express.js (server) to manage my project and this is the way i create my users:
Vue.JS createUser method:
createUser() {
axios.post('http://localhost:3000/users',
{
username: this.username,
email: this.email,
firstName: this.firstName,
lastName: this.lastName,
password: this.password,
}).then(r => {
console.log(r)
}).catch(e => {
console.log(e)
})
// createUser
}
Each field: username, email, firstName, etc... got its value form a common HTML Form and they work just fine.
This is my "user create" route on the server side:
router.post('/', function (req, res, next) {
firebase.auth().createUser({
email: req.body.email,
username: req.body.username,
firstName: req.body.firstName,
lastName: req.body.lastName,
emailVerified: false,
password: req.body.password,
disabled: false
}).then(function(userRecord) {
console.log("Successfully created new user:", userRecord.uid);
}).catch(function(error) {
console.log("Error creating new user:", error);
});
});
And this is how i retrieve the users information:
router.get('/', function(req, res, next) {
firebase.auth().listUsers()
.then(function(listUsersResult) {
res.send(listUsersResult.users);
}).catch(function(error) {
console.log("Error listing users:", error);
})
});
The problem is that i can't get the custom fields i added (or they simply does not exists). Here's is a pictures of the data once i get it with the next method:
getItems() {
axios.get('http://localhost:3000/users').then(r => {
this.users = r.data
}).catch(e => {
console.log(e)
})
},
Is there a way to set custom fields on my user creation or (if my process is right) a way to retrieve them?
Firebase users are not customizable, unfortunately you can not add custom fields to the user... check out these docs for the properties of this object. Also check out this question & answer where Frank explains that you need to store any extra info separately... this is typically done in a table (named something like /userDetails) in your database.
FEBRUARY 2022 EDIT:
Since writing this answer, custom user claims are now a thing. It's meant for authentication / rules purposes and not data-storage, so most things should still be kept in the database as originally stated. Note that you CAN access the custom fields client-side.
Keep in mind, this is adding additional data to the ID token that gets sent with EVERY server request, so you should try to limit usage of this to role/permission based things and not, say, image data for a 2nd profile avatar. Read the best practices for custom claims on the official docs.
You can set a user's profile, immediately after creating.
firebase.auth().createUserWithEmailAndPassword(
email,
password,
).then(credential => {
if (credential) {
credential.user.updateProfile({
displayName: username
})
}
}

Why is passport.authenticate needed in a registration?

I'm learning Passport, using local strategy and Mongoose, through passport-local-mongoose plugin.
It makes sense to me in a /login for example, but I don't understand why passport.authenticate is needed in a registration, right after it having already taken place...
Can anyone explain me, please? Also, what if it fails?
The following code was taken from a tutorial, but I have found similar constructions all over.
router.post('/register', function(req, res) {
User.register(new User({ username : req.body.username }),
req.body.password, function(err, user) {
if (err) {
return res.status(500).json({err: err});
}
if (req.body.firstname) {
user.firstname = req.body.firstname;
}
if (req.body.lastname) {
user.lastname = req.body.lastname;
}
user.save(function(err,user) {
passport.authenticate('local')(req, res, function () {
return res.status(200).json({status: 'Registration Successful!'});
});
});
});
});
The password isn't used to authenticated the user inside this snipped.
Call User.register
Creates a new User Object with the username, and needs the password to do
some magic with it. Probably build a hash which gets added to your 'user' object.
Call user.save
Saves this 'user' object inside your Storage, if this is successful the user gets authenticated, and returns HTTP 200 which ends the registration process.
Error Handling isn't implemented in this save method, for that you will have an exception on error.
Hope this helps to understand, your snippet.
To use your /login method your new user can use his username and password which he gave to you during the registration process.

Check for existing user using Mongoose

I'm trying to write a middleware function that (when a POST request is made with a username/password) checks to see if the user being created already exists in the database. I don't know if I'm doing this properly though.
User.find({ username: req.body.username }) returns an object which contains (or does not contain) the user if it exists...but how to properly return to exit if a user under the same username is found? Whenever I test this with Mocha, res.body.msg comes up as undefined.
Code:
module.exports = exports = function(req, res, next) {
User.find({ username: req.body.username }, (err, user) => {
if (err) return handleDBError(err, res);
if (user) return res.status(200).json({ msg: 'an account with this username already exists' });
});
next();
};
User Schema:
var userSchema = new mongoose.Schema({
username: String,
authentication: {
email: String,
password: String
}
});
give it a try very initial create a function to get the user response
function findUser(arg, callback) {
/* write your query here */
return callback(pass response here)
}
And then use it where you want
findUser(arg,function(callbackResponse) { /*do something*/ })
Since nodejs is asynchronous, chances are that the response is being sent after you are trying to read it. Make sure to keep the reading process waiting untill response is sent. I personaly use passport for handling that.

Return value from one function to another with Node.JS

I am working on a login interface using the MEAN stack. I have managed to get it to work using PassportJS. My problem now is I need a way to let my client-side know whether the person logging in is an admin or user(user role). These info are available from my MongoDB.
The flow of my API call is as follow :
app.post('/login', passport.authenticate('local'), authRoutes.loginCheck);
First, it runs the passport.authenticate where it calls the function below
function verifyCredentials(username, password, done) // username & password from what user provide when logging in
{
console.log('VC');
User.findOne({username: username}, function(err, user) //query Mongo
{
console.log(user); // User role is available here, in JSON format
if(user === null) // if no username in database, do this
{
console.log('Username does not exist in database');
}
else
{
user.comparePassword(password, function(err, match) // function written to compare hashed password in Mongo & password provided by user
{
if(match)
{
done(null, {id: username, name: username});
return user; // this is not the correct syntax, but the idea is, I want to send over the user details here, so I can access the role later
}
else
{
done(null, null);
}
});
}
});
}
The verifyFunction is called with this syntax.
passport.use(new LocalStrategy(verifyCredentials));
Once that function is successfully called, the server executes the 2nd part of it which is the loginCheck.
module.exports.loginCheck = function(req, res)
{
console.log('Calling loginCheck route');
// I generate some sort of jwt token here
// payload, body, blah blah blah ...
console.log(req.body);
res.json({
authenticated: req.isAuthenticated(), //built-in authentication function, returns true or false
token: token // sends over token
role: user.role // want to send over something like this
}); // sends all these to client side as JSON
}
Since both functions are in different files, I am unclear if I have to require something or simply just pass an extra parameter to the loginCheck function. I have tried the latter though and it did not work.
One way that I could think of is do another Mongo query in the loginCheck function, but that would be kinda redundant.
Even a specific keyword for me to google up would definitely be of big help as don't I know what I should be looking for. The reason is because I am new to NodeJS, thus I am not familiarize with most of the terms yet.
I think these codes should suffice but if I am needed to provide more, let me know and I will do so. Thanks in advance !!
To pass control to next matching route you need to use next that passes as third argument in the routes:
function verifyCredentials(req, res, next) {
User.findOne({username: req.body.username}, function(err, user) //query Mongo
{
if(user === null) {
return next(new Error('Username does not exist in database'));
} else {
user.comparePassword(req.body.password, function(err, match) {
if(match) {
next(null, {id: username, name: username});
} else {
next(new Error('not match'));
}
});
}
});
}
app.post('/login', verifyCredentials, authRoutes.loginCheck);

Input validation, sanitization and service layer

I'm trying to keep my controller actions as lightweight as possible so i am implementing service layer. Now i've stucked with validation and sanitization. I know that validation should be done in service layer but what about sanitization? I would like to re-render the with the input data when there are validation errors.
//userService.js function
function register(data, callback) {
if (!data) {
return callback(new Error('Here some error...'));
}
/* Sanitize and validate the data */
/* Method #1 */
//If not valid then call back with validationErrors
if (notValid) {
return callback({
validationErrors: {
'username': 'Username is already in use.',
'email': 'Invalid characters.',
}
});
}
/* Method #2 */
if (notValid) {
return callback({
fields: {
//We put here a sanitized fields
},
validationErrors: {
'username': 'Username is already in use.',
'email': 'Invalid characters.',
}
});
}
};
//userController.js function
// GET/POST: /register
function registerAction(request, response, next) {
if (request.method === 'POST') {
var registerData = {
username: request.body['username'],
password: request.body['password'],
email: request.body['email'],
firstName: request.body['firstName'],
lastName: request.body['lastName'],
};
register(registerData, function(error, someDataIfSucceed) {
if (error) {
//Re-post the data so the user wont have to fill the form again
//Sanitize registerData variable here.
return response.render('register', {
error: error,
validationErrors: error.validationErrors
});
};
//User registered succesfully.
return response.render('registerSuccess');
});
return;
}
return response.render('register');
}
I see there 2 options.
Call service function 'register' with raw POST data, sanitize and validate it then push back only validation errors. If there are validation errors then sanitize them in controller before rendering the view.
Same as first one but we push back validation errors and sanitized fields.
If you're using Express, an interesting option is:
Create a middleware and use it as a validation layer, using express-validator, which is based on node-validator.
For example (see node-validator documentation for all validation/sanitization options):
exports.validate = function(req, res, next) {
req.assert('username').notEmpty();
req.assert('password').notEmpty();
req.assert('email').isEmail();
req.assert('firstName').len(2, 20).xss();
req.assert('lastName').len(2, 20).xss();
var errors = req.validationErrors(true);
if (errors){
res.status(400).json({ status: 'ko', errors: errors });
}
else {
next();
}
}
Then, in your controller, simply obtain the validated request params and run the sign up logic (your register function call and response rendering),
IMHO, this way you can keep your code more clean and decoupled.
If it's a small project don't bother, just do what works.
If, however, it is a large (read: long-lived) project, then:
If by "sanitization" you mean HTTP/HTML sanitization (or inputs, or of display messages), then this belongs in the controller. Think about it this way: the controller may not be the only place where you pass input to your service layer from. In the future you might have API access to the service. Or a test driver may invoke it directly, without going thru HTTP. So HTTP/HTML is just the transport, and as such logic specific to it should be outside of the service.
If, however, by "sanitization" you mean business-logic sanitization (e.g.: you don't allow non-existing country codes), then by all means this should be in the service, for exactly the same reasons.

Resources