Defining Role in backend MERN - reactjs

How to define role to Admin, Customer, Worker and Log in with different roles in frontend ? Im new in coding.
For authentication, I'm using JWT. Now I want to add roles and rules for admin, worker and customer.
1 -> Permissions can be assigned to a role ( Create, Update, delete etc...).
As a result, a user can have one or more roles. A user can use APIs for which he has rights, such as creating data, deleting data, updating data, and so on.
Here is the user schema:
UserSchema.model
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = mongoose.Schema(
{
role: {
type: String,
enum: ["admin", "customer", "worker"],
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
role: {
type: Boolean,
// required: true,
default: false,
enum: ["admin", "customer", "worker"],
},
pic: {
type: String,
// required: true,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
},
{
timestamps: true,
}
);
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// will encrypt password everytime its saved
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
module.exports = User;
UserController
const asyncHandler = require("express-async-handler");
const User = require("../models/user.model");
const generateToken = require("../utility/token");
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
isAdmin: user.isAdmin,
pic: user.pic,
token: generateToken(user._id),
});
} else {
res.status(401);
throw new Error("Invalid Email or Password");
}
});
const registerUser = asyncHandler(async (req, res) => {
const { firstname, lastname, email, password, pic } = req.body;
try {
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ error: "User already exist." });
}
const user = await User.create({
firstname,
lastname,
email,
password,
pic,
});
res.status(201).json({
_id: user._id,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
isAdmin: user.isAdmin,
pic: user.pic,
token: generateToken(user._id),
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const updateUserProfile = asyncHandler(async (req, res) => {
const user = await User.findById(req.user._id);
if (user) {
user.firstname = req.body.firstname || user.firstname;
user.lastname = req.body.lastname || user.lastname;
user.email = req.body.email || user.email;
user.pic = req.body.pic || user.pic;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
firstname: updatedUser.firstname,
lastname: updatedUser.lastname,
email: updatedUser.email,
pic: updatedUser.pic,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
} else {
res.status(404);
throw new Error("User Not Found");
}
});
module.exports = { authUser, updateUserProfile, registerUser };
UserSchema.model
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = mongoose.Schema(
{
role: {
type: String,
enum: ["admin", "customer", "worker"],
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
role: {
type: Boolean,
// required: true,
default: false,
enum: ["admin", "customer", "worker"],
},
pic: {
type: String,
// required: true,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
},
{
timestamps: true,
}
);
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// will encrypt password everytime its saved
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
});
const User = mongoose.model("User", userSchema);
module.exports = User;
UserController
const asyncHandler = require("express-async-handler");
const User = require("../models/user.model");
const generateToken = require("../utility/token");
const authUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (user && (await user.matchPassword(password))) {
res.json({
_id: user._id,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
isAdmin: user.isAdmin,
pic: user.pic,
token: generateToken(user._id),
});
} else {
res.status(401);
throw new Error("Invalid Email or Password");
}
});
const registerUser = asyncHandler(async (req, res) => {
const { firstname, lastname, email, password, pic } = req.body;
try {
const userExists = await User.findOne({ email });
if (userExists) {
return res.status(400).json({ error: "User already exist." });
}
const user = await User.create({
firstname,
lastname,
email,
password,
pic,
});
res.status(201).json({
_id: user._id,
firstname: user.firstname,
lastname: user.lastname,
email: user.email,
isAdmin: user.isAdmin,
pic: user.pic,
token: generateToken(user._id),
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
const updateUserProfile = asyncHandler(async (req, res) => {
const user = await User.findById(req.user._id);
if (user) {
user.firstname = req.body.firstname || user.firstname;
user.lastname = req.body.lastname || user.lastname;
user.email = req.body.email || user.email;
user.pic = req.body.pic || user.pic;
if (req.body.password) {
user.password = req.body.password;
}
const updatedUser = await user.save();
res.json({
_id: updatedUser._id,
firstname: updatedUser.firstname,
lastname: updatedUser.lastname,
email: updatedUser.email,
pic: updatedUser.pic,
isAdmin: updatedUser.isAdmin,
token: generateToken(updatedUser._id),
});
} else {
res.status(404);
throw new Error("User Not Found");
}
});
module.exports = { authUser, updateUserProfile, registerUser };

Related

State don't update after login by google in react native

i hope you are doing well.
I have a few problem when i try to log in with Google in react native.
I have a global state for user :
const initialState = {
userId: null,
token: null,
email: "",
firstName: "",
lastName: "",
nom: "",
dateOfBirth: null,
roles: [],
photoUrl: "",
phoneNumber: "",
favorites: [],
};
With Login, it work well and my global state is update. The logic :
export const login = (email, password) => {
return async (dispatch) => {
const response = await auth.signInWithEmailAndPassword(email, password).then().catch((error) => {
Alert.alert("L'email ou le mot de passe est invalide");
throw new Error()
} );
if (response && response.user.uid === null) {
throw new Error("Something went wrong!");
}
const user = await db.collection("Users").doc(response.user.uid).get();
const expirationTime = (await response.user.getIdTokenResult())
.expirationTime;
const newDate = new Date(expirationTime).getTime();
const token = (await response.user.getIdTokenResult()).token;
await dispatch(authenticate(response.user.uid, token, newDate));
await dispatch(
addUserInfos(
user.get("email"),
user.get("username"),
user.get("firstName"),
user.get("lastName"),
user.get("dateOfBirth")?.toDate(),
user.get("roles"),
user.get("photoUrl"),
user.get("phoneNumber"),
user.get("favorites"),
)
);
const expirationDate = new Date(new Date().getTime() + newDate);
saveDataToStorage(token, response.user.uid, expirationDate);
};
};
But when i want to connect with Google. The app connect me but my global state only contain value userId and token. I need the reload the app for set my global state. However the 2 functions use the same logic.
export const addUserOnFirestore = (id, email, firstName, lastName, photoUrl, response, navigation) => {
return async (dispatch) => {
let isNeedOnboarding = false;
const user = await db
.collection('Users')
.doc(id)
.get();
// Create user in firestore in it not exist
if (!user.exists) {
isNeedOnboarding = true;
console.log('onboard : ', isNeedOnboarding);
await db.collection('Users').doc(id).set({
email: email,
username: '',
firstName: firstName ? firstName : '',
lastName: lastName ? lastName : '',
dateOfBirth: null,
roles: [],
photoUrl: photoUrl ? photoUrl : '',
phoneNumber: '',
favorites: [],
}).then(res => {
console.log("Effectué")
})
.catch(error => {
console.log("firestore error:", error);
});
} else {
console.log('Document data:', user.data());
}
const expirationTime = (await response.user.getIdTokenResult())
.expirationTime;
const newDate = new Date(expirationTime).getTime() * 1000;
const token = (await response.user.getIdTokenResult()).token;
await dispatch(authenticate(token, id, newDate));
if (isNeedOnboarding) {
await dispatch(addUserInfos(email, '', firstName, lastName, '', '', photoUrl, '', []));
navigation.navigate("Onboarding");
} else {
await dispatch(
addUserInfos(
user.get("email"),
user.get("username"),
user.get("firstName"),
user.get("lastName"),
user.get("dateOfBirth")?.toDate(),
user.get("roles"),
user.get("photoUrl"),
user.get("phoneNumber"),
user.get("favorites"),
)
);
}
const expirationDate = new Date(new Date().getTime() + newDate);
saveDataToStorage(token, id, expirationDate);
if (isNeedOnboarding) {
navigation.navigate("Onboarding");
}
};
};
The dispatch function addUserInfo :
const addUserInfos = (
email,
username,
firstName,
lastName,
dateOfBirth,
roles,
photoUrl,
phoneNumber,
favorites,
) => {
return (dispatch) => {
dispatch({
type: ADD_USER_INFO,
payload: {
email: email,
username: username,
firstName: firstName,
lastName: lastName,
dateOfBirth: dateOfBirth,
roles: roles,
photoUrl: photoUrl,
phoneNumber: phoneNumber,
favorites: favorites,
},
});
};
};
And the authenticate :
export const authenticate = (userId, token, expiryTime) => {
return (dispatch) => {
dispatch(setLogoutTimer(expiryTime));
dispatch({ type: AUTHENTICATE, payload: { userId, token } });
};
};
Do you have any idea why it works in the simple login but not when I want to connect with google?
Then why it is only when I reload the application that my global state is updated ?
Thanks in advance.

What is the best way to use Google 0Auth

I'm trying to use Google OAuth in my App, on the Log In page and the Sign Up page, I'm looking for the best way and the easiest! I tried Passport Js, but I'm stuck right now.
I'm using Mongoose right now and I'm signing up and in users perfectly, but now i want to add a feature where the user can sign in using his google account, I'm looking for a way where the app can get the Email the user is using for his google account and then look if the email is already registered if so redirect him to the home page and if not sign his email up, save it to database, and then redirect to the home page.
This is how my Auth.js looks like
//REGISTER
router.post("/register", async (req, res) => {
try {
//generate new password
const salt = await bcrypt.genSalt(10);
const hashedPass = await bcrypt.hash(req.body.password, salt);
//create new user
const newUser = new User ({
username: req.body.username,
firstName: req.body.firstName,
lastName: req.body.lastName,
email: req.body.email,
password: hashedPass,
repeatPassword: hashedPass,
birthday: req.body.birthday,
});
//save user and respond
const user = await newUser.save();
res.status(200).json(user);
} catch (err) {
res.status(500).json(err);
}
});
//LOGIN
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
// if(!user) return res.status(400).json("Wrong credentials!");
!user && res.status(400).json("Wrong credentials!");
const validated = await bcrypt.compare(req.body.password, user.password);
// if(!validated) return res.status(400).json("Wrong credentials!");
!validated && res.status(400).json("Wrong credentials!");
const { password, ...others } = user._doc;
return res.status(200).json(others);
} catch (err) {
return res.status(500).json(err);
}
});
PassportJs configuration I used: but didn't work
passport.use(
new GoogleStrategy(
{
clientID: "MyClientId",
clientSecret: "Myclientsecret",
callbackURL: "/api/auth/google/callback",
},
function (accessToken, refreshToken, profile, done) {
User.find(
{
social: profile.provider,
social_id: profile.id,
},
(err, user) => {
if (user.length === 0) {
const user = new User({
email: profile.email,
username: profile.displayName,
profilePic: profile.photos[0],
social: profile.provider,
social_id: profile.id,
});
const userModel = new User(data);
userModel.save();
done(null, profile);
}
if (err) {
return done(err);
}
},
);
return done(null, profile);
}
)
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
router.get("/login/success", (req, res) => {
if (req.user) {
res.status(200).json({
success: true,
message: "successfull",
user: req.user,
// cookies: req.cookies
});
}
});
router.get("/login/failed", (req, res) => {
res.status(401).json({
success: false,
message: "failure",
});
});
router.get("/google", passport.authenticate("google", { scope: ["profile"] }));
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: CLIENT_URL,
failureRedirect: "/login/failed",
})
);

How to return api errors to Login Component in NextAuth.js

How to return API errors to Login Component in NextAuth.js.Actually, I am trying to pass the Errors back to Login Component in NextAuth(Credentials Provider). I am getting this object in console error: "CredentialsSignin" ok: false status: 401 url: null [[Prototype]]: Object Everything is working fine like I am able to log in, but when I am trying to handle errors coming from APIs, I am unable to handle them.
[...nextauth.js] File
export default (req, res) =>
NextAuth(req, res, {
providers: [
CredentialsProvider({
authorize: async (credentials) => {
try {
const data = {
email: credentials.email,
password: credentials.password
}
const user = await login(data);
console.log("401 Error",user.data);
if (user.data.status==200) {
console.log("200 data",user.data);
return Promise.resolve(user.data);
}else if(user.data.status==401){
// Here I wants to Handle Errors and return them back to My login Compenent
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
jwt: async ({token,user})=>{
if(user){
token.userid = user.id;
token.name = user.username;
token.token = user.token;
}
return token;
},
session: (session,token)=>{
return session;
}
},
secret:"test",
jwt:{
secret:"test",
encryption:true,
},
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60, // 3 hrs
updateAge: 24 * 60 * 60, // 24 hours
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = 'api/auth/login';
const result = await axios.post(url,data,config);
return result;
};
Login Components
const LoginSubmit = async (event) => {
event.preventDefault();
const enteredEmail = inputText.email;
const enteredPassword = inputText.password;
// console.log(enteredEmail);
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("Final Result",result);
};
Yes you can do it :
Try :
// component.js
const res = await signIn("credentials", {
email: inputs.email,
password: inputs.password,
callbackUrl: `/`,
redirect: false,
});
if (res?.error) {
setLoading(false);
Swal.fire("Invalid Login", "username or password is incorrect");
}
and in Nextauth file you must throw an error if a user entred invalid credential!
//[...nextauth].js
const providers = [
Providers.Credentials({
name: "credentials",
authorize: async (credentials) => {
//statr try
try {
const user = await
axios.post(`${API_URL}/auth/local`, {
identifier: credentials.email,
password: credentials.password,
});
if (user) {
return { status: "success",
data:user.data };
}
} catch (e) {
throw new Error("Not found ");
}
},
}),
];

Mongoose FindOneandUpdate - Only update Specific fields leave others with the previous values

I would like to update just the fields that the user chose to update, but right now if I don't fill all the inputs, they became null in the data base.
this is the back end
router.put('/user/:id', async (req, res) => {
let id = req.params.id
const salt = await bcrypt.genSalt(10)
const hashPassword = await bcrypt.hash(req.body.password, salt)
const emailExist = await User.findOne({ email: req.body.email })
if (emailExist) {
return res.status(400).send('Email already exists')
}
//creat user
const update = {
name: req.body.name,
lastName: req.body.lastName,
phone: req.body.phone,
email: req.body.email,
bio: req.body.bio,
password: hashPassword,
role: "basic"
}
User.findByIdAndUpdate(id, { $set: update }, { new: true }, (error, userObj) => {
if (error) {
res.status(400).send(err)
} else {
res.send('user updated')
}
})
})
Front end (react)
const updateUser = async (event) => {
event.preventDefault()
const response = await axios.put(`/userinfo/user/${localStorage.getItem('id')}`, {
name: name,
lastName: lastName,
phone: phone,
email: email,
password: password,
confPassword: confPassword,
bio: bio,
})
history.push('/home-login')
const reload = window.location.reload()
}
Thank you
Try handy methods of lodash NPM, install it and import in your node.js file,
Pick update fields from body:
let update = _.pick(req.body, ["name", "lastName", "phone", "email", "bio"]);
Remove undefined, null and "" empty fields:
update = _.pickBy(update, _.identity);
Merge other fields:
update = _.merge(update, {
password: hashPassword,
role: "basic"
});
Use in query:
User.findByIdAndUpdate(id, { $set: update }, { new: true }, (error, userObj) => {
if (error) res.status(400).send(err)
else res.send('user updated')
})

how to remove unverfied users from the database automatically?

I am making a social networking site with MERN Stack. Firstly, the users could simply sign up with email and password but that meant anyone can use any random email and possibly spam the DB. So I turned to this video https://www.youtube.com/watch?v=76tKpVbjhu8 and implemented it to ask for email verification.
Now the problem is even if a user registers and didn't verify his account another user can't create an account with the same email as it says user already exists. This means if some prankster registered an account with:
name: John Doe
email: johndoe#gmail.com
password: 123456
And couldn't verify his account, if someday the real johndoe comes and tries to register an account, he won't be able to register it as it already exists.
Is there anyway for the user to be automatically deleted if it failed to verify his account during the set time.
Here's my User Schema
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
posts: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}
],
profile: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Profile'
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
isVerified: {
type: Boolean,
default: false
},
date: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
Register route
// #route POST api/users
// #desc REgister a user
// #access Public
router.post(
'/',
[
check('name', 'Please add name')
.not()
.isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check(
'password',
'Please enter a password with 6 or more characters'
).isLength({ min: 6 })
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) {
return res.status(400).json({ msg: 'User already exists' });
}
user = new User({
name,
email,
password
});
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
const payload = {
user: {
id: user.id
}
};
let profile = new Profile({
user: user.id
});
await profile.save();
user.profile = profile.id;
await user.save();
jwt.sign(
payload,
config.get('jwtSecret'),
{
expiresIn: 36000
},
(err, token) => {
if (err) throw err;
const url = `http://localhost:3000/confirmation/${token}`;
transporter.sendMail({
to: user.email,
subject: 'Confirm Email',
html: `Please click this link to confirm your email for signing up for MERN Blog ${url}`
});
res.json({
msg:
'Thanks for signing up! An email has been sent to your email for verification. Go ahead and verify your account'
});
}
);
} catch (err) {
console.log(err.message);
res.status(500).send('Server Error');
}
}
);
module.exports = router;
Confirmation route
router.post('/confirmation', async (req, res) => {
const token = req.body.token;
//Check if not token
if (!token) {
return res.status(401).json({ msg: 'No token, authorization denied' });
}
try {
const decoded = jwt.verify(token, config.get('jwtSecret'));
const user = await User.findByIdAndUpdate(decoded.user.id, {
isVerified: true
});
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{
expiresIn: 36000
},
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (err) {
res.status(401).json({ msg: 'Token is not valid' });
}
});
I think the best way of doing this would be to check the accounts creation date/time, if it has been over X amount of days/weeks && the email is not verified, then dispatch a delete action.

Resources