It's a simple signup route to store credentials in a mongoDB database but I miss something because the 2 else if won't work properly. I suspect it is my find().
The first else if returns me in Postman "error": "E11000 duplicate key error collection: vinted.users index: email_1 dup key: { email: \"jean#dpont.com\" }" and the second give me "email already exists".
Thanks in advance for your help
const express = require("express");
const router = express.Router();
const SHA256 = require("crypto-js/sha256");
const encBase64 = require("crypto-js/enc-base64");
const uid2 = require("uid2");
const User = require("../models/User");
router.post("/user/signup", async (req, res) => {
try {
const email = req.fields.email;
const username = req.fields.username;
const phone = req.fields.phone;
const password = req.fields.password;
const token = uid2(64);
const salt = uid2(16);
const hash = SHA256(password + salt).toString(encBase64);
const emailSearch = await User.find({ email: email });
if (!emailSearch || username !== null) {
const newUser = new User({
email: email,
account: {
username: username,
phone: phone,
},
password: password,
token: token,
hash: hash,
salt: salt,
});
await newUser.save();
res.status(200).json({
_id: newUser._id,
token: newUser.token,
account: newUser.account,
});
}
//problem under
else if (emailSearch) {
res.status(404).json({ message: "email already exists" });
} else if (username === null) {
res.status(404).json({ message: "please type a username" });
}
} catch (error) {
res.status(404).json({
error: error.message,
});
}
});
It looks like the issue is that if the username in the request body is not null, it's going to attempt to create a new User with that username regardless of whether a User exists with the same email - if (!emailSearch || username !== null).
It's generally best-practice to do as much input validation as you can before you start looking for records or creating new ones, as you will be able to avoid more Mongo errors and database actions if you can stop invalid actions before they're attempted. So in this case, check that the username is valid before looking for existing Users.
To solve this problem, I would move that last else-if to before you check whether a User exists with the same email. That way, once you determine whether the username is valid, then the only thing that you need to consider is existing Users before creating a new one. Something like this:
if (username === null) {
res.status(400).send({ message: "Error: Please provide a 'username'." });
}
const existingUserWithEmail = await User.find({ email: email });
if (!existingUserWithEmail) {
// Create the new User
} else {
res.status(400).send({ message: "Error: An account already exists with this email." });
}
Related
I'm trying to perform user validation using if statements but on testing using post man, I keep on getting only the first return statement 'Email is required ' even after adding a valid email address and also an invalid email address. I have attached the user model, user controller logic and a picture of postman's response
user schema model
const mongoose = require('mongoose');
const { Schema } = mongoose
const userSchema = new Schema({
firstName: {
type: String,
required: true,
min: 3,
max: 20
},
lastName: {
type: String,
required: true,
min: 3,
max: 20
},
email: {
type: String,
required: true,
unique: true,
},
phoneNumber: {
type: String,
required: true,
},
password: {
type: String,
required: true,
min: 5
},
confirmPassword: {
type: String,
required: true,
min: 5
}
});
const User = mongoose.model('User', userSchema);
module.exports = User;
user.controller.js
module.exports.users = async(req, res) => {
try {
const email = await user.findOne({ email: req.body.email });
const firstName = await user.find({ firstName: req.body.firstName });
const lastName = await user.find({ lastName: req.body.lastName });
const password = await user.find({ password: req.body.password });
const confirmPassword = await user.find({ confirmPassword: req.body.confirmPassword });
const phoneNumber = await user.find({ phoneNumber: req.body.phoneNumber });
if (!email) return res.send('Email is required')
const filterEmail = /^([a-zA-Z0-9_\.\-])+\#(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (!filterEmail.test(email.value)) return
res.send('Please provide a valid email address');
if (email) return res.send('Email already exists');
if (!firstName) return res.send('First Name is required');
if (firstName.length < 3 || firstName.length > 20) return
res.send('First name must be at least 3 characters and less than 20 characters');;
if (!lastName) return res.send('Last Name is required');
if (lastName.length < 3 || lastName.length > 20) return
res.send('Last name must be at least 3 characters and less than 20 characters')
if (!password) return res.send('PassWord is required');
const filterPassword = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])(?!.*\s).{5,15}$/;
if (!filterPassword.test(password.value)) return
res.send('Password must include at least one lowercase letter, one uppercase letter, one digit, and one special character');
if (!confirmPassword) return res.send(' Please confirm password');
if (password.value !== confirmPassword.value) return res.send('Passwords do not match');
if (!phoneNumber) return res.send('Phone Number is required');
phone(phoneNumber.value);
let User = new user(_.pick(req.body, ['firstName', 'lastName', 'email', 'phoneNumber', 'password']));
bcrypt.genSalt(10, async(err, salt) => {
if (err) throw err;
return user.password = await bcrypt.hash(user.password, salt);
});
await User.save();
} catch (err) {
res.status(500).send('Something went wrong');
console.warn(err);
}
}
Your controller is not good. You need to get the email, lastName and firstName from req.body.
const {email, lastName} = req.body
And then do the validation, which by the way will already happen in mongoose.
const email = await user.findOne({ email: req.body.email });
In this line you are looking in your DB for a user with email = req.body.email. But since there is no such a user it return your if statement
if (!email) return res.send('Email is required')
to get your user you only need to compare with one value if you set it to be unique in your schema, for instead user email.
const user = await user.findOne({ email });
In this case email was already destructed and if there is no user, you can create one.
you can use await user.create() and pass your values and hash the password with pre("save") in your schema.
UserSchema.pre("save", async function () {
if (!this.isModified("password")) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
You can also validate user email in the schema
email: {
type: String,
required: [true, "Please provide email"],
match: [
/^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
"Please provide a valid email",
],
unique: true,
},
You can simply install the validator package npm i validator then call it in in the user model const { isEmail } = require('validator'); and finally type this in your schema instead of hardcoding the verification code in the function.
email: { type: String, required: true, validate: [isEmail], unique: true },
Instead of writing custom logic for validation Please add a validation layer after performing security checks
you can use Express validator, to perform all your validations in a smoother way, It will eliminate unnecessary logic and code lines and add structured code to validate all types inputs, nested objects, DB call for existing data handling. It will handle the response and response message on its own without hitting the controller
just hit
npm install --save express-validator
and you are ready to validate requests very easily and effectively
Beside other answers i thought one thing which is missing but yet it is very strong validation method in Node.JS is JOI package, you can define your validation schema and get rid of all other pain full if and else, example is following
const Joi = require('joi');
const schema = Joi.object().keys({
firstName: Joi.string().alphanum().min(3).max(20).required(),
lastName: Joi.string().alphanum().min(3).max(20).required(),
email: Joi.email().required(),
phoneNumber: Joi.string().length(10).pattern(/^[0-9]+$/).required(),
password: Joi.string().min(5).required(),
confirmPassword: Joi.any().valid(Joi.ref('password')).required()
});
const dataToValidate = {
firstName: 'chris',
lastName: 'John',
email: 'test#test.com'
}
const result = Joi.validate(dataToValidate, schema);
// result.error == null means valid
Without node script you can always test your JOI schema and your data object on this webpage
I want to update a customer's information that consists of first name, last name, email and password. I want to encrypt the password when its being updated. I wrote my route like this:
customerRouter.put(
'/:id', isAuth,
expressAsyncHandler( async (req, res) => {
const {id} = req.params;
const customer = await Customers.findOne({where: {id: id}});
const {firstname,lastname,password,email} = req.body;
if (!customer) {
res.status(401).send({
message: "Customer Not Found",
});
}else{
bcrypt.hash(password,10).then((hash)=>{
customer.firstName = firstname || customer.firstName
customer.lastName = lastname || customer.lastName
customer.email = email || customer.email
customer.password = hash || customer.password
})
const updatedCustomer = await customer.save();
res.send({
id: updatedCustomer.id,
firstname: updatedCustomer.firstName,
lastname: updatedCustomer.lastName,
email: updatedCustomer.email,
isAdmin: updatedCustomer.isAdmin,
token: generateToken(updatedCustomer)
});
};
})
);
The password wont change in the backend, I had a previous method that didn't encrypt and only changed the password, but this one doesn't encrypt the password or change it. Any help/guidance is much appreciated.
I read the documentation that describes how to solve the issue with extracting certain fields of an object found by id. I've tried to write a similar code, but the output assigned to the user variable includes all fields of a single element.
export const getUser = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const { id } = req.params;
const userFetchesOwnData = req.app.locals.userId === id;
let user: any;
if (req.app.locals.isAuth && userFetchesOwnData) {
user = await User.findById(
id,
"email username age sex location summary"
).exec();
} else {
user = await User.findById(
id,
" username age sex location summary"
).exec();
}
console.log(user);
res.status(200).json({
message: "User fetched",
user,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode = 500;
}
next(err);
}
};
You can specifically query fields in a Mongoose object by comma separated values.
User.findById(id).select("_id, email, username, age, sex, location, summary").then(...)
I am creating accounts for new users by this mutation createaccnt but i ended up getting the error above, have stared at it for days..could someone point where i have messed and correct.
here are the codes.
how i defined my schema
const userSchema = new Schema({
username:String,
email:String,
Password:String,
addedOn:String
});
type definitions
//user type definitio
type User{
id:ID!,
username:String!,
email:String!,
token:String!,
Password:String!,
addedOn:String!,
},
input UserInput{
email:String!,
username:String!,
Password:String!,
confirmPassword:String!
},
//mutation
type Mutation{
createAccnt ( userInput:UserInput!) : User!
}
this is how i implemented the mutatiion
//createAccnt
async createAccnt(parent,{userInput:{
email,
username,
Password,
confirmPassword
}},context,info){
try {
const {errors,valid}=validateUserInput(
email,
username,
Password,
confirmPassword
);
//allow for error detection
if(!valid){
throw new UserInputError('errors',{errors})
};
//check if user already exist
const user= await User.findOne({username});
if(user){
throw new UserInputError('username is taken',{
errors:{
username: 'username is taken'
}
});
};
//hash password
Password= await bcrypt.hash(Password,12);
//create a new user object
const newUsr= new User({
email,
username,
Password,
addedOn:new Date().toISOString()
});
//save to db
const res=await newUsr.save();
//web token
const token= jwt.sign({
id:res.id,
email:res.email,
usernaem:res.username
},SECRET_KEY,{expiresIn:'1h'});
return{
...res._doc,
id:res._id,
token
}
}
catch (error) {
throw new Error(error)
}
i have checked throughly through the code ,i can't identify the error.Help identify and correct where possible.
If you are unsure about the handling under the hood.
type User{
id:ID!,
username:String!,
email:String!,
token:String!,
Password:String!,
addedOn:String!,
}
"!" it's mean can not return "null" so make sure your code return a User correctly
Above it's mean you must have to return a User object like this.
return {
id: "some id here",
username: "some username here",
email: "some email here",
token:"token here",
Password:"pass here",
addedOn: "date here",
}
My use case is similar to Parse Suggested Migration Strategy
(but I'm not using Parse, just integrating 3rd party auth)
On Node.js / express, here is relevant code:
firebaseWebSDK.auth().signInWithCustomToken(customToken)
.then(user => {
let nextStep
let message
// check if returned user has "email" field set..
if (!user.email) {
message = 'Signed up user, saved: email, password, and display name.'
const usersRef = firebaseServerSDK.database().ref('users')
nextStep = Promise.all([
user.updateEmail(email),
user.updatePassword(password),
user.updateProfile({ displayName }),
usersRef.child(user.uid).set({ displayName, email }),
])
} else {
message = `User already exists for that email: ${email}`
nextStep = Promise.resolve(null)
}
nextStep.then(() => {
delete req.session.user
return res.json({ message })
})
.catch(updateError => res.json({ error: updateError.message }))
})
.catch(signInError => {
const errorCode = signInError.code
const errorMessage = signInError.message
return res.json({ type: 'signInError', errorCode, errorMessage })
})
Here's what happens:
Sometimes user email updates, sometimes it doesn't
Never does the password update
Pretty sure the Profile updates always
database "/users" saves correctly, no issues there.
Always tested with new user, so it's always entering the "update" branch of code...
Any ideas? Code follows structure of documentation linked above, so not sure what issue is.