How can I hide certain attribute from response with Bookshelf? - javascript

I am building a simple REST API. When registering a new user, my API returns:
{
"status": "success",
"data": {
"email": "test13#test.com",
"password": "$2b$10$DcFdth1FKskyy6A7uwCHDOE15oy4pgZBj.TwEBcQnSVrUK4mntZdy"
"first_name": "Tester",
"last_name": "Test",
"id": 13
}
}
I want to hide the password field in my response like so:
{
"status": "success",
"data": {
"email": "test13#test.com",
"first_name": "Tester",
"last_name": "Test",
"id": 13
}
}
I've added delete user.attributes.password to my code, but is this correct?
My code:
/**
* Auth Controller
*/
const bcrypt = require('bcrypt');
const debug = require('debug')('books:auth_controller');
const { matchedData, validationResult } = require('express-validator');
const models = require('../models');
/**
* Register a new user
*
* POST /register
*/
const register = async (req, res) => {
// check for any validation errors
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).send({ status: 'fail', data: errors.array() });
}
// get only the validated data from the request
const validData = matchedData(req);
console.log("The validated data:", validData);
// generate a hash of `validData.password`
// and overwrite `validData.password` with the generated hash
try {
validData.password = await bcrypt.hash(validData.password, models.User.hashSaltRounds);
} catch (error) {
res.status(500).send({
status: 'error',
message: 'Exception thrown when hashing the password.',
});
throw error;
}
try {
const user = await new models.User(validData).save();
debug("Created new user successfully: %O", user);
delete user.attributes.password;
res.send({
status: 'success',
data: user,
});
} catch (error) {
res.status(500).send({
status: 'error',
message: 'Exception thrown in database when creating a new user.',
});
throw error;
}
}
module.exports = {
register,
}
This is my user model. I am using Bookshelf.js.
/**
* User model
*/
module.exports = (bookshelf) => {
return bookshelf.model('User', {
tableName: 'users',
albums() {
return this.hasMany('Album');
},
photos() {
return this.hasMany('Photo');
}
}, {
hashSaltRounds: 10,
async fetchById(id, fetchOptions = {}) {
return await new this({ id }).fetch(fetchOptions);
},
async login(email, password) {
// find user based on the email (bail if no such user exists)
const user = await new this({ email }).fetch({ require: false });
if (!user) {
return false;
}
const hash = user.get('password');
// hash the incoming cleartext password using the salt from the db
// and compare if the generated hash matches the db-hash
const result = await bcrypt.compare(password, hash);
if (!result) {
return false;
}
// all is well, return user
return user;
}
});
};

you can add a list of model attributes to exclude from the output when serializing it. check it out here
return bookshelf.model('User', {
tableName: 'users',
hidden: ['password']
})

Related

How to Update User With JWT Token When he Tries To Login in Nodejs and Reactjs with MongoDB

I am trying to create a login functionality for my Reactjs Webiste using Nodejs express backend.
I want to set a JWT token when the user tries to log in and update that token in my mongoDB database and then verify the token on the frontend and save it to localStorage.
However, when the user tries to log in after registration, it returns back the result without the token, and thus not allowing the user to log in, unless he clicks the login button again, then my code would generate and update the user with the JWT token.
Why is this behavior happening? Why is the first response only returning the found user from the findOne() operation when i am resolving the result from the findOneAndUpdate operation?
Here is my code:
Auth Controller:
login(params) {
params.email = params.email.toLowerCase();
return new Promise((resolve, reject) => {
db.collection("Users").findOne({ email: params.email }).then((response) => {
console.log(response)
if(response) {
bcrypt.compare(params.password, response.password, (err, success) => {
if(success) {
let token = jwt.sign({
name: response.name,
id: response._id
}, proccess.env.JWT_SECRET);
db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, function (e, s) {
if(e) {
console.log(e)
reject(e)
} else {
console.log("updated")
resolve(s)
}
})
} else {
reject({msg: 'Incorrect email or password.'})
}
})
} else {
reject({msg: 'cannot log in user'});
}
})
})
}
Auth Router:
router.post('/login', (req, res) => {
let User = new models.User()
let processes = [];
processes.push(function (callback) {
User.login(req.body).then(function (response) {
callback(null, response);
}, function (error) {
console.log(error)
callback(error);
});
});
async.waterfall(processes, function (error, data) {
if (!error) {
return res.json({
statusCode: 200,
msg: 'User logged in successfully.',
result: data
});
} else {
return res.json({
statusCode: 401,
msg: 'Cannot login user.',
error: error
});
}
});
})
React Login.js:
const login = () => {
axios.post('/login', data).then(async (response) => {
console.log(response)
if(response && response.data.result.value.token ) {
localStorage.setItem("authUser", JSON.stringify(response.data.result.value.token))
history.push("/")
console.log(response.data.result)
} else {
console.log("ERROR")
}
})
}
MongoDBs method findOneAndUpdate does return the old document by default.
In order to return the updated document pass returnNewDocument: true as option:
https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/
In your case:
db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, {
returnNewDocument: true
}, function (e, s) {
if(e) {
console.log(e)
reject(e)
} else {
console.log("updated")
resolve(s)
}
})
PS: You might should use async functions with await. This could make your code way more readable (at least within the User Model) :)
This can help you.
In your model
async login(params) {
params.email = params.email.toLowerCase();
try {
const user = await db.collection("Users").findOne({ email: params.email });
if(!user) {
throw {message: "Incorrect email"}
}
const vaild = await bcrypt.compare(params.password, user.password);
if(!valid) {
throw {msg: 'Incorrect email or password.'}
}
let token = jwt.sign({
name: user.name,
id: user._id
}, proccess.env.JWT_SECRET);
return db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, {new: true}); //FOR THE RETRIEVE NEW UPDATEs FROM MONGODB
} catch(e) {
throw e
}
}

Getting erros using passport-google-oauth20 InternalOAuthError: Failed to fetch user profile and Cannot set headers after they are sent to the client

I'm using passport strategies for different socialMedia logins and getting the following two errors
InternalOAuthError: Failed to fetch user profile
Cannot set headers after they are sent to the client
I have doubt there somewhere I have returned a callback or response so getting 2nd error but for 1st don't know reasons scope seems to be correct!
strategy code
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_SECRET_KEY,
callbackURL: GOOGLE_CALLBACK_URL
}, async (acessToken, refreshToken, profile, done) => {
await User.findOne({ email: profile._json.email }, async (err, user) => {
if (err) {
console.log("passport.config --> err", err);
done(err, null);
} else if (user) {
if (user.socialType !== "GOOGLE" || user.socialType === null)
done(`LOGIN_CREDENTIALS_WITH_${(user.socialType || "PASSWORD").toUpperCase()}`, false);
else {
done(null, user);
}
} else {
// console.log(profile);
const user = {
email: profile._json.email,
socialId: profile.id,
socialType: "GOOGLE",
firstName: profile.name.givenName,
lastName: profile.name.familyName,
isActive: profile._json.email_verified,
isVerified: profile._json.email_verified,
socialImageUrl: profile._json.picture,
userType: "CUSTOMER"
};
const newUser = new User({ ...user });
const newUserData = await newUser.save();
done(null, newUserData);
}
});
}));
route code:
router.get('/auth/:socialType', customerCtrl.socialTypeLogin);
router.get('/auth/:socialType/callback', customerCtrl.socialTypeLoginCallback);
controller code:
const socialTypeLogin = async (req, res) => {
await customerService.socialTypeLogin(req, res);
};
const socialTypeLoginCallback = async (req,res) => {
await customerService.socialTypeLoginCallback(req,res);
};
service code:
const socialTypeLogin = async (req, res) => {
try {
const socialType = (req.params.socialType || '').toLowerCase();
const GOOGLE_SCOPE = ['email', 'profile'];
const FACEBOOK_SCOPE = ['email'];
let scope = [];
if (socialType === 'google') {
scope = GOOGLE_SCOPE;
} else if (socialType === 'facebook') {
scope = FACEBOOK_SCOPE;
}
let oauthOptions = { scope: scope};
const { returnUrl } = req.query;
if(returnUrl && returnUrl.trim().length !== 0) {
oauthOptions['state'] =JSON.stringify({ returnUrl: returnUrl });
}
passport.authenticate(socialType, oauthOptions)(req, res);
}
catch (error) {
}
}
/**
* #param {string} socialType
*/
const socialTypeLoginCallback = async (req, res) => {
const socialType = (req.params.socialType || '').toLowerCase();
// return new Promise((resolve, reject) => {
try {
passport.authenticate(socialType, async (err, user) => {
let webappRedirectURL = WEBAPP_LOGIN_URL;
try {
const state = req.query.state;
if(state) {
const stateObj = JSON.parse(state);
webappRedirectURL = stateObj.returnUrl;
}
} catch (err1) {
console.log("customer.service --> parsing error",err1);
}
if (err || !user) {
console.log("customer.service --> !user",err);
res.render('oauth-redirect', {
webappRedirectURL: webappRedirectURL,
success: false,
error: err,
timerCounter: 5,
accessToken: undefined
});
}
else {
console.log("customer.service --> Generating Token",user.generateJWT());
res.render('oauth-redirect', {
webappRedirectURL: webappRedirectURL,
success: true,
timerCounter: 5,
accessToken: user.generateJWT(),
error: undefined
});
}
})(req, res);
}
catch (error) {
console.log("customerService.js ==> socialTypeLoginCallback -->",error);
}
};
Thanks for help in advance!
I have doubt there somewhere I have returned a callback or response so getting 2nd error but for 1st don't know reasons scope seems to be correct!
In socialTypeLogin
add line
oauthOptions['session'] = false;

nodemon app crashed - waiting for file changes before starting error after geting response from server

I'm developing a server in Node JS where there are two routes - Login and Signup.
Whenever I do signup, I am getting response as success and the data is being stored in MongoDB database successfully and then I'm getting [nodemon] app crashed - waiting for file changes before starting... in my console.
Note:- "The problem is in signup only not in login".
postSignup() will be called when a user requests for signup which is validated according to schema and inserted in database.
I'm providing the code related to signup.
signup.js
const { User } = require("../../models");
const createError = require("http-errors");
const postSignup = (req, res, next) => {
//validation
const validation = User.validate(req.body);
if (validation.error) {
const error = new Error(validation.error.message);
error.statusCode = 400;
return next(error);
}
//check Existence
const user = new User(req.body);
user
.checkExistence()
.then((result) => {
if (result.check) {
const error = new Error(result.message);
error.statusCode = 409;
return next(error);
}
user.save((err) => {
if (err) {
console.log(err);
return next(createError(500));
}
res.status(201).json({
message: "User has been Successfully Created",
});
});
})
.catch((err) => {
next(createError(500));
});
};
module.exports = {
postSignup,
};
User.js
const { dbCon } = require("../configuration");
const { userValidator, logSchema } = require("../validator");
const { hashSync, compareSync } = require("bcryptjs");
class User {
constructor(userData) {
this.userData = { ...userData };
}
save(cb) {
dbCon("users", (db) => {
try {
const hashPass = hashSync(this.userData["password"], 12);
this.userData["password"] = hashPass;
db.insertOne(this.userData);
cb();
} catch (err) {
cb(err);
}
});
}
checkExistence() {
return new Promise((resolve, reject) => {
dbCon("users", async (db) => {
try {
const user = await db.findOne({
$or: [
{ username: this.userData["username"] },
{ email: this.userData["email"] },
],
});
if (!user) {
resolve({
check: false,
});
} else if (this.userData["username"] === user.username) {
resolve({
check: true,
message: "username already exists",
});
} else if (this.userData["email"] === user.email) {
resolve({
check: true,
message: "email already exists",
});
}
} catch (err) {
reject(err);
}
});
});
}
static validate(userData) {
//console.log(userData);
return userValidator.validate(userData);
}
module.exports = User;
userValidator.js
const Joi = require("#hapi/joi");
const schema = Joi.object({
username: Joi.string().alphanum().required().min(3).max(15),
email: Joi.string().email().required(),
password: Joi.string()
.pattern(
new RegExp(
"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!#$%^&*-]).{8,}$"
)
)
.message(
"Password must contain at least eight characters, at least one number and both lower and uppercase letters and special characters"
)
.required(),
first_name: Joi.string().required(),
last_name: Joi.string().required(),
});
module.exports = {
schema
};
I faced the same issue. I don't know what was the issue but I tried to change node version in mongo db connect and then used the new connect URL.
If it still doesn't work, then try to create new cluster and connect it again with new cluster.

update user info in array

i am having an issue, i want to update hobbies as a array (like- {"hobbies":["running","dancing"]} )
User is a model.
router.patch('/userInfo', async (req, res) => {
const updates = Object.keys(req.body);
const allowedUpdates = ['userId', 'userImages','intrestedIn', 'hobbies']`
const isValidOperation = updates.every((update) => {
return allowedUpdates.includes(update)
});
if (!isValidOperation) {
return res.send({
error: "Validation fail"
})
}
try {
const user = await User.findOne({ _id: req.body.userId }, req.body)
console.log(user)
if (!user) {
return res.send({
error: 'Invalid user Id'
})
}
updates.forEach((update) => {
user[update] = req.body[update]
})
await user.save()
return res.send({ user })
} catch (e) {
res.send(e)
}
})
and output is that, but i want array(like intrestedIn output)
{
"user": {
"intrestedIn": [
"Female"
],
"hobbies": [
"{\"hobbies\":[\"dancing\",\"running\"]} "
],
"_id": "5ec71c43026b2f1d640b657f"
}
}

I am trying to create a doc to model with mongoose but model.create() does not return any promise

it seems that the create method does not return any promise that then can handle
I tried different things but nothing worked
this is my routes file
const express = require("express")
const router = express.Router();
const controller = require("./controller")
router.post("/signup", controller.create);
module.exports = router;
and this is my model file
const mongoose = require('mongoose');
const User = new mongoose.Schema(
{
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
picture: {
type: String
},
password: {
type: String,
select: false
},
email: {
required: true,
type: String,
unique: true
}
},
{
timestamps: true
}
);
User.index({
firstName: 'text',
lastName: 'text',
});
module.exports = mongoose.model('User', User);
and this is the controller file
const User = require('./model');
const { hash, compareHash } = require('../lib/util');
const { createToken, findUserByToken } = require('../lib/auth');
const cookieIsSecure = process.env.ENVIRONMENT === 'production';
exports.create = async (req, res) => {
const password = await hash(req.body.password);
const rawUser = {
...req.body,
password,
};
User.create(rawUser)
.then(async user => {
return user.save();
})
.then(async user => {
const newUser = user.toObject();
res.send(newUser);
})
.catch(err => {
if (err.code === 11000) {
res.status(400).send({ message: 'A user with this email address has already registered.' });
return;
}
res.status(500).send({ message: 'An unexpected error occurred' });
});
};
it always return the 500 error "an unexpected error occurred"
which is not really specific. and i do not know what is the problem exactly. but I am sure it has something to do with the model.create() it does not return any promise.
Here you are mixing methods. create doesn't want save in it as it's implicit:
https://mongoosejs.com/docs/api.html#model_Model.create
Please try this, I've refactored your code a bit and added much easier to read and use try/catch:
const rawUser = new User({ ...req.body, password});
try {
await rawUser.save();
res.status(201).send(newUser);
} catch(err) {
if (err.code === 11000) return res.status(400).send({ message: 'A user with this email address has already registered.' });
res.status(500).send({ message: 'An unexpected error occurred' });
}
You need to use async/await like this:
exports.create = async (req, res) => {
try {
const password = await hash(req.body.password);
const rawUser = {
...req.body,
password
};
const user = await User.create(rawUser);
const newUser = user.toObject();
res.send(newUser);
} catch (err) {
console.log("ERROR: ", err);
if (err.code === 11000) {
return res.status(400).send({
message: "A user with this email address has already registered."
});
}
res.status(500).send({ message: "An unexpected error occurred" });
}
};

Categories