I am just a beginner at Javascript & MERN. I am trying to create a small social media app, and in my sign up api, I gave a response of the user's info. I couldn't segregate and hide the password.
here is the code
userRouter.post("/signUp", async (req, res) => {
const {name, userName, email, password} = req.body
const existingUser = await userSchema.findOne({email: email})
const SameUserName = await userSchema.findOne({userName: userName})
if (existingUser) {
return res.status(406).send({
message: `sorry, an account with email: ${email} has already been created.`
})
} else if (SameUserName) {
return res.status(406).send({
message: `sorry, user name taken. Try another one...`
})
}
const newUser = new userSchema({
name,
userName,
email,
password
})
console.log(newUser)
try {
await newUser.save()
res.status(201).send({
message: `Account successfully created!`,
user: newUser
})
} catch (err) {
res.send({
message:`Something went wrong`,
})
}
})
So, how can I send the user info without the password?
Following up on the comment I left below, here is what you can do.
Refactoring of your code is must thou.
try {
const userSaved = await newUser.save();
delete userSaved.password // assuming this is the property name
return res.status(201).send({ message: 'Account created successfully', user: userSaved })
}
you could also just:
try {
const userSaved = await newUser.save();
delete userSaved.password // assuming this is the property name
return userSaved;
}
In this case you handle the message and everything on the front-end.
You'll want to implement the toJSON and transform methods on your schema. This will allow you to 'intercept' schema objects as they are created, and as they are serialized and sent to the client.
Here's an example:
Schema:
import { Schema, model } from 'mongoose';
const schema = new Schema(
{
name: {
required: true,
type: String
},
userName: {
required: true,
type: String
},
email: {
required: true,
type: String
},
password: {
required: true,
type: String
}
},
{
// here, we implement the `toJSON` method to serialize the user object sans password, __v;
// we'll also convert the mongo-specific `_id` property to a db-agnostic format
toJSON: {
transform(_, ret) {
ret.id = ret._id;
delete ret.password;
delete ret._id;
delete ret.__v;
}
}
}
);
// this is our user schema, used to initialize new user objects before we persist them in the db
const User = model('User', schema);
userRouter.post('/signUp', async (req, res) => {
// grab the inputs - we do *not* at this time know whether any of these are valid - they must be validated
const { name, userName, email, password } = req.body;
// validate the email format, performing checks for any requirements you wish to enforce
if (!email) {
// error response
}
// now, we check if the email is already in-use
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).send({
message: `sorry, an account with email: ${email} has already been created.`
});
}
// validate userName format here
if (!userName) {
// error response
}
// notice we don't bother making this query until `existingUser` check has passed
// this way we don't incur needless computation
const sameUserName = await User.findOne({ userName });
if (sameUserName) {
return res.status(400).send({
message: `sorry, user name taken. Try another one...`
});
}
// validate name and password and handle accordingly here
if (!name || ...) {
// error response
}
// assuming all is well, we create a new user with the schema
// think of the schema as a template
const newUser = new User({ name, userName, email, password });
// save the new user
await newUser.save().catch((ex) => {
// error response
});
res.status(201).send({
message: `Account successfully created!`,
user: newUser
});
});
You might also look into express-validator, a middleware that handles much of the request body validation for you.
Related
I'm learning node.js and it's amazing, especially with mongo, but sometimes I struggle to solve a simple problem, like patching only 1 attribute in my user database.
It's easier to patch something that cannot be unique, but I want to patch an username attribute and I defined it as "unique" in my schema. I don't know why, but MongoDB doesn't care other db entry has the same user, it let me save.
My schema:
/** #format */
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true },
userNumber: { type: Number, required: true },
description: { type: String },
verified: { type: Boolean, default: false },
isAdmin: { type: Boolean, default: false },
isSubscriber: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
acceptedTerms: { type: Number, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
On my user controllers in node, I want to updateOne({ _id: userId}, { username: myNewUsername} but it always happens, it doesn't take into consideration another db entry can have the username, so I tried a different strategy but it doesn't work:
exports.changeUsername = (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
User.findOne({ username: req.body.username })
.then(result => {
console.log(result);
if (result.username) {
const error = new Error('Could not find this sport');
error.code = 'DUPLICATED';
throw error;
}
return;
})
.catch(err => next(err));
// if no username was in use then updateOne
User.updateOne({ _id: userId }, { username: newUsername })
.then(result => {
res.status(200).json({
message: 'username has been updated',
username: result.username,
});
})
.catch(err => next(err));
};
I don't know if I can updateOne at the same time add some find validation. What I am doing wrong? Users cannot have the same username.
On the console, it seems it works, but it throws an extra error I don't understand:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/Users/username/Sites/pipi-api/node_modules/express/lib/response.js:776:10)
I tried this other approach and it works, but doesn't trigger an error if the record is not unique as I stated in the schema.
// GET ONLY ONE SPORT BY ID
exports.changeUsername = async (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
try {
const oldUsername = await User.findOne({ username: newUsername });
if (oldUsername.username) {
throw new Error('Error: its duplicated');
}
const user = await User.findOneAndUpdate(
{ _id: userId },
{ username: newUsername },
{ new: true }
);
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (err) {
console.log('ERROR: ', err);
return res.status(400).json({ success: false });
}
};
If I uncomment the code above, it triggers an error if I find a record on the database that matches but it doesn't allow me to continue to my next line of codes I the username is not found on the db.
I get a new error:
userId: 6231bdef334afbde85ed9f43
newUsername: tetete
ERROR: TypeError: Cannot read properties of null (reading 'username')
at exports.changeUsername (/Users/user/Sites/pipi-api/v1/controllers/users/index.js:43:21)
That error is not related to Mongo. It means that you are trying to send a response and the response is already sent.
The issue is because you called both User.findOne and User.updateOne and both of them has .then handler. So the first one of these that finishes will send the actual response. In the moment the second one finished, the response is already send and the error is thrown because you are trying to send response again.
Mongo will throw the error if you try to change username property that some other user already have. You should check if the req.params.userId and req.body.username sent correctly to the backend. Try to console.log() them and check if they are maybe null.
Consider refactoring your handler to use async/await instead of then/catch. You can do it like this:
exports.changeUsername = async (req, res, next) => {
try {
const userId = req.params.userId;
const newUsername = req.body.username;
const user = await User.findOneAndUpdate({ _id: userId }, { username: newUsername }, { new: true });
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (error) {
console.log('ERROR: ', error);
return res.status(400).json({ success: false });
}
}
I have a code where I am updating my schema object with request body. I have applied validation rules on the schema. The problem is, I want the schema to throw an error when there's a non existing field in the request body. Non existing key doesn't save to the database as I want but I want to throw some error instead of saving the object. Schema:
const peopleSchema = new mongoose.Schema(
{
fullname: {
type: String,
required: [true, "fullname is required"],
validate: [(value) => isAlpha(value, "en-US", {ignore: " "}), "name should be alphabetic only"],
},
phone: {
type: String,
validate: [isPhone, "please enter a valid phone number"],
},
address: String,
},
{ timestamps: true }
);
Code to update person:
router.put("/:id", checkUser, async (req, res, next) => {
try {
const { id } = req.params;
const user = req.currentUser;
const person = user.people.id(id);
person.set(req.body);
const response = await user.save();
res.json({ response });
} catch (err) {
next(new BadRequestError(err));
}
});
for validation there are two way based on callback and async approache ,
because your code is based on async/await you must to use validateSync() like the following code:
let errors = user.validateSync()//check validation
if(errors){
console.log(errors)
throw errors;//handle your error
}
const response = await user.save()
in callback method :
user.save(function(err,response){
if (err){
console.log(err);
//handle error
}
else{
console.log(response)
res.json({ response });
}
})
Note: this is my first time posting, if you have feedback please let me know
Goal: I am building some endpoints that let a user reset their password if they forgot it. Flow would look like this:
User doesn't know password so they click on forgot password.
User types in email and clicks send
User receives email with link to reset password. Clicks on link and is redirected to type in their new password.
They click 'save' and they are redirected to login to sign in with their new password
I am using Insomnia to hit the endpoints for testing.
Things that are working:
When providing an email to reset password, Nodemailer does send out an email.
When updating the password it does show 'password updated' and gives a 200 status.
Bugs:
After trying to log in with that new password, it is not saving to the database. Only the old password will allow you to log back in.
Things I have tried:
I tried changing my user.model to use my findByEmail function and ran into some weird bugs, which then led me down a rabbit hold of issues.
I tried console logging quite a few things to see if I could trace the path.
I tried changing the user.update function but was not able to get it to work.
Here is my code:
Any guidance would be appreciated. If you need to look at any other files please let me know.
Forgot.password.js
const router = require('express').Router();
const crypto = require('crypto')
const User = require('../models/users.model')
const nodemailer = require('nodemailer')
router.post('/forgotpassword', (req, res) => {
let {
email
} = req.body
console.log(req.body)
// if (req.body.email === '') {
// res.status(400).json({ message: 'Email is required'})
// } console.error(req.body.email)
User.findBy({
email
})
.first()
.then(user => {
if (user === null) {
res.status(403).json({
message: 'Email not in db'
})
} else {
const token = crypto.randomBytes(20).toString('hex')
User.update({
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: `${process.env.EMAIL_USER}`,
pass: `${process.env.EMAIL_PASS}`
}
})
const mailOptions = {
from: `${process.env.EMAIL_USER}`,
to: `${user.email}`,
subject: '[Promoquo] Reset Password Link',
text: 'You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\n' +
'Please click on the following link, or paste this into your browser to complete the process within one hour of receiving it:\n\n' +
`http://localhost:5000/reset/${token}\n\n` +
'If you did not request this, please ignore this email and your password will remain unchanged.\n',
}
transporter.sendMail(mailOptions, (err, res) => {
if (err) {
console.log('ERROR coming from forgot.password js and it sucks', err)
} else {
console.log('here is the res', res)
res.status(200).json({
message: 'recovery email sent hell yes'
})
}
})
}
res.status(200).json({
message: 'Reset password email has been sent WOOHOO 🎉'
})
})
.catch(error => {
res.status(500).json({
message: 'ERROR on last catch forgotpassword.js, likely no user exists',
error
})
console.log(error)
})
})
module.exports = router
Update.password.js
const router = require('express').Router();
const passport = require('passport')
const bcrypt = require('bcrypt')
const User = require('../models/users.model')
const BCRYPT_SALT_ROUNDS = 12
router.put('/updatePasswordViaEmail', (req, res) => {
User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
.then(user => {
if (user == null) {
console.error('password reset link has expired')
res.status(403).json({ message: 'Password reset link is invalid or has expired' })
} else if (user != null) {
console.log('user exists in db')
bcrypt.hash(req.body.password, BCRYPT_SALT_ROUNDS)
.then(hashedPassword => {
User.update({
password: hashedPassword,
resetPasswordToken: null,
resetPasswordExpires: null,
})
})
.then(() => {
console.log('log for THEN updating password')
res.status(200).json({ message: 'password updated' })
})
} else {
console.error('no user exists in db to update')
res.status(401).json({ message: 'no user exists in db to update'})
}
})
})
module.exports = router
Users.model.js
const db = require('../dbConfig')
module.exports = {
add,
find,
findBy,
findById,
findByEmail,
findByType,
update
};
function find() {
return db('users').select('id', 'username', 'email', 'password');
}
function findBy(filter) {
return db('users').where(filter);
}
async function add(user) {
const [id] = await db('users').insert(user);
return findById(id);
}
function findById(id) {
return db('users').where({ id }).first();
}
function findByEmail(email) {
return db('users').where({ email }).first();
}
function findByType(type) {
return db('users').where({ type }).first();
}
function update(changes, id) {
return db('users').where({ id }).update(changes)
}
20200913211559_users.js (this is the table)
exports.up = function(knex) {
return knex.schema.createTable('users', tbl => {
tbl.increments();
tbl.string('firstname', 30).notNullable();
tbl.string('lastname', 30).notNullable();
tbl.string('username', 30).notNullable()
tbl.string('email', 50).notNullable()
tbl.string('password', 128).notNullable();
tbl.string('type').notNullable();
tbl.boolean('confirmed').defaultTo('false');
tbl.string('resetPasswordToken');
tbl.date('resetPasswordExpires');
})
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('users')
};
Your User.update() lines aren't running (you either need to return their promises into the chains of promises, or hook into their callbacks). async/await is your friend here to avoid "callback hell."
const user = await User.find({
where: {
username: req.body.username,
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: Date.now() + 3600000,
}
})
if (!user) { /* ... */ }
const token = crypto.randomBytes(20).toString('hex')
await User.update({ // await here!
resetPasswordToken: token,
resetPasswordExpires: Date.now() + 3600000,
})
my idea is very simple, it is get the update value from req.body, but it doenst work properly, the data never change in the mongodb.
already tried the {$set: email, password}
const id = { _id: req.params.id };
const { email, password } = req.body;
let user = await User.findByIdAndUpdate(id, { email, password });
if (!user) {
return res.json({ message: "error"});
}
return res.json({
user: user,
updated: true
})
you passing a object into to id field when all it needs is a string/ObjectId
const { email, password } = req.body;
let user = await User.findByIdAndUpdate(req.params.id, { email, password });
if (!user) {
return res.json({ message: "error"});
}
return res.json({
user: user,
updated: true
})
The Expression :
const id = { _id: req.params.id };
creates an Object named id with a field _id set to the actual id value from the params.
So eventually what you end up doing is passing an entire object instead of an id in the first argument to findByIdAndUpdate which only accepts an id as the first parameter.
If you still want to use the object just replace id with id._id in the method findByIdAndUpdate. Which you would then write as :
let user = await User.findByIdAndUpdate(id._id, { email, password });
I'm having a little trouble when querying inside a mutation, I believe maybe I'm not calling correctly the query, because it executes but is not waiting for the response so I get an undefined value, please correct me.
Please note that I'm using prisma-binding
This is my mutation resolvers:
const Mutation = {
async signUp(parent, args, ctx, info) {
const password = await bcrypt.hash(args.password, 10)
const user = await ctx.db.mutation.createUser({ data: {...args, password} })
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},
async login(parent, args, ctx, info) {
const user = await ctx.db.query.users( {where:{email: args.email}}, info)
if (!user) {
throw new Error('No such user found')
}
const valid = bcrypt.compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},
};
module.exports = Mutation;
In the login function when querying the user or users I try both queries even knowing I have email as unique field it always print No such user found due I receive undefined, IDK if it's because prisma-binding or not doing correctly the function call to prisma.
Here is my schema.graphql
type Query {
// some queries
}
type Mutation {
signUp(
name: String!
email: String!
password: String!
): AuthPayLoad
login(
email: String!
password: String!
): AuthPayLoad
}
type AuthPayLoad {
token: String
user: User
}
So for prisma-binding I don't have to define the query users(), the binding will handle that right?? Even if was that, I would have another error which isn't the case.
Maybe I'm missing some little detail, will be grateful if someone point me to the right direction.
Thanks in advance...
You are passing the wrong selection set to the query. You are returning AuthPayload in login mutation but you are passing it to the user query which is surely incompatible.
try this
async login(parent, args, ctx, info) {
const user = await ctx.db.query.user( {where:{email: args.email}},`{ id name email }`) // pass in the other fields you want in this selection set
if (!user) {
throw new Error('No such user found')
}
const valid = bcrypt.compare(args.password, user.password)
if (!valid) {
throw new Error('Invalid password')
}
const token = jwt.sign({ userId: user.id }, process.env.PRISMA_SECRET)
return {
token,
user,
}
},