error when updating user password with bcrypt in mongodb - database

const {password, value} = body.password;
// compares the old and new password
const passwordMatch = await bcrypt.compare(password, student.password);
if(!passwordMatch) return res.status(400).json({message:"passwords do not match"})
//hashes the password
const salt = await bcrypt.genSalt();
const hashedPassword = await bcrypt.hash(value, salt);
student.password = hashedPassword;
am using "save()" to save the document
below is the error am getting
"error": {
"errors": {
"password": {
"stringValue": "\"{ password: 'abcde', value: '123456' }\"",
"kind": "string",
"value": {
"password": "abcde",
"value": "123456"
},
"path": "password",
"reason": null
}
},
"_message": "Secondary validation failed",
"message": "Secondary validation failed: password: Cast to string failed for value \"{ password: 'abcde', value: '123456' }\" at path \"password\""
}
any help please? am using node, express and mongodb. Am also using postman for testing. I couldn't wrap my head around on what could be causing this problem.
below is the structure of the input (i.e req.body)
{
"password": {
"password": "abcde",
"value": "123456"
}
}
where
password = old password (i.e password existing in the database)
value = new password to be changed to

Assuming that youre getting the students._id from somewhere
router.patch("/", async (req, res) => {
try {
const { password, newPassword } = req.body;
const validPass = await bcrypt.compare(password, student.password);
if (!validPass) return res.status(400).json("Invalid Password");
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
await Student.findOneAndUpdate(
{ _id: student._id },
{ $set: { password: hashedPassword } },{ useFindAndModify: false }
);
res.status(200).send("Password Updated");
} catch (err) {
res.status(400).send({ message: err });
}
});
You'd be looking at something like this, let me know if this helps

Related

Storing user data using jwt

I am fetching user data from the graphql backend through the apollo client. My goal is to keep the user logged in, so I signed jwt token with all user data, passed it to localStorage(only the token), decoded it, and passed all values to redux-store.
UserModel:
userSchema.methods.createJWT = function (payload) {
return jwt.sign({ ...payload }, process.env.JWT_SECRET, {
expiresIn: '1d',
});
};
userLogin:
await user.save();
return {
...user._doc,
id: user._id,
token: user.createJWT(user._doc),
}
reduxSlice
const userSlice = createSlice({
name: 'user',
initialState: {
userInfo: localStorage.getItem('jwtToken')
? jwtDecode(localStorage.getItem('jwtToken'))
: null,
},
reducers: {
loginUser: (state, action) => {
localStorage.setItem('jwtToken', action.payload.token);
state.userInfo = action.payload;
},
I am wondering if this is ok that the token is holding too much info
like:
{
"_id": "62a9ee3878c4979fedb471c5",
"username": "***",
"email": "***",
"password": "$2a$12$hN2lfCtEbqOOFSlHpapyfuxYAHdEGUYKeHY4BMK1YvYOtSG7zHwcS",
"isAdmin": false,
"shippingAddress": [],
"createdAt": "2022-06-15T14:35:36.877Z",
"updatedAt": "2022-06-16T09:04:59.367Z",
"__v": 0,
"firstName": "***",
"lastName": "***",
"shoeSize": 4,
"iat": 1655371413,
"exp": 1655457813
}
There is another effective way to save user data and keep him logged in?
it's not recommended (actually very dangerous) to return all information with the jwt token especially password. I think userId is enough!
But you can also return username, firstName, lastName, etc.
But in some cases even returning email address is not a good approach for some user privacy reasons.
I mean by that you have only to get the userId once there is a user and the credentials are correct, then :
const userToken = {
userId: user._id,
username: user.username,
};
return {
user,
token: user.createJWT(userData)
};
Now after signing the jwt token , you can set whatever data from user inside some redux state or even react context , (choose your favorite) , but DON'T set any password in the localStorage.
Update: at the end you should store the user from the payload like this :
state.userInfo = action.payload.user;
Btw you should check the localStorage only to get the token and verify it , then based on userId you need to fetch the user and store it, here is an example :
const getUser = React.useCallback(async (userId) => {
try {
const res = await axios.post('/auth/login', {userId}, {
credentials: 'include',
withCredentials: true
});
const { accessToken, user } = res.data;
setState((currentState: IState) => ({
...currentState,
user,
loading: false,
isAuth: !!accessToken,
accessToken
}));
} catch (err) {
setState((currentState: IState) => ({
...currentState,
user: null,
loading: false,
isAuth: false,
accessToken: ''
}));
}
}, []);
useEffect(() => {
getUser(userId);
}, [getUser]);
useEffect(() => {
const jwtToken = localStorage.getItem('jwtToken');
if (jwtToken && jwt_decode(jwtToken)) {
const { exp } = jwt_decode(jwtToken);
const currentTime = Date.now() / 1000;
if (exp < currentTime) {
getUserById(userId);
}
}
}, [getUser]);

Mern stack bcrypt password works many times, and suddenly doesn't match

i have a strange problem and don't find the same so i search some help :) :
I make a mern stack social media, and first all is okay with register new user and logged in.
I'm on development of the app, so i login and logout a lot. And after a few times, i don't count, maybe 20 or 30, the password is not recognized, and i can't login with the account.
So i make a new user to continue my work, and the same thing happen after a few logins.
Here is my userSchema
const mongoose = require("mongoose");
const {isEmail} = require("validator");
const bcrypt = require("bcrypt");
function validEmail(email) {
if(!isEmail(email)) {
return false;
}
else {
return true
}
}
const userSchema = new mongoose.Schema(
{
pseudo: {
type: String,
required: true,
minLength: 3,
maxLength: 20,
unique: true,
trim: true
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
unique: true,
validate: validEmail
},
password: {
type: String,
required: true,
maxLength: 1024,
minLength: 6
},
dimension: {
type: String,
},
galaxy: {
type: String,
},
picture: {
type: String,
default: "./uploads/profil/random-user.png"
},
bio: {
type: String,
maxLength: 1024
},
followers: {
type: [String],
},
following: {
type: [String]
},
likes: {
type: [String]
}
},
{
timestamps: true,
}
)
// Play function before save into display: block
userSchema.pre("save", async function(next) {
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hash(this.password, salt);
next();
})
userSchema.statics.login = async function(email, password) {
const user = await this.findOne({email});
console.log(user)
if(user) {
const auth = await bcrypt.compare(password, user.password);
if(auth) {
return user;
} else {
throw Error("Incorrect password");
}
}
throw Error("Incorrect email");
}
const UserModel = mongoose.model("user", userSchema);
module.exports = UserModel;
And here the login function in my auth.controller.js:
module.exports.signIn = async (req, res) => {
const { email, password } = req.body;
try {
const user = await UserModel.login(email, password);
const token = createToken(user._id);
res.cookie("jwt", token, {
maxAge: maxAge, sameSite: "none",
secure: true,
})
res.status(200).json({ user: user._id })
}
catch (err) {
const errors = signInErrors(err);
res.status(200).json({ errors });
// console.log(err)
}
}
It's my first project with back-end side, and i follow a tutorial from 2020 to make it, so maybe there is new best practices to do it now. But it seems very strange that it works fine with new user, and not after some connections. I hope someone have an idea to try to help me :)
I find this and try, seems to work for me after 2 days, with adding a condition on the pre hook like this:
userSchema.pre("save", async function(next) {
if(!this.isModified("password")) {
return next()
}
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hashSync(this.password, salt);
next();
})
Hope it help someone

Next-Auth with firebase Authentication

just wanna have my custom credential provider which authenticate the entered username and password with Firebase Authentication on sign in page
pages/api/auth/[...nextauth].ts
import NextAuth from "next-auth"
import { getDatabase } from "firebase/database"
import { DB } from "../../../constants/firebase"
import { FirebaseAdapter } from "#next-auth/firebase-adapter"
import * as firestoreFunctions from "firebase/firestore"
import CredentialsProvider from "next-auth/providers/credentials"
export default NextAuth({
session: {
strategy: "database",
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
username: {
label: "Username",
type: "text",
placeholder: "somebody#gmail.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const database = getDatabase()
console.log(database)
const user = {
id: 1,
usename: "j",
password: "123456789",
}
if (
credentials?.username === user.usename &&
credentials.password === "123456789"
) {
return user
}
return null
},
}),
],
adapter: FirebaseAdapter({
db: DB,
...firestoreFunctions,
}),
// pages: {
// signIn: "/auth/signin",
// signOut: "/auth/signout",
// error: "/auth/error", // Error code passed in query string as ?error=
// verifyRequest: "/auth/verify-request", // (used for check email message)
// newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest)
// },
callbacks: {
async jwt({ token, user }) {
if (user) {
token.email = user.email
}
return token
},
async session({ session, token, user }) {
if (token) {
session.user!.email = token.email
}
return session
},
redirect({ url, baseUrl }) {
if (url.startsWith(baseUrl)) return url
else if (url.startsWith("/"))
return new URL(url, baseUrl).toString()
return baseUrl
},
},
})
firebase.ts
import { initializeApp, getApp, getApps } from "firebase/app"
import { getAnalytics } from "firebase/analytics"
import { getFirestore } from "#firebase/firestore"
import { getStorage } from "#firebase/storage"
import getFirebaseObject from "./firebaseConfig"
const app = !getApps.length ? initializeApp(getFirebaseObject()) : getApp()
const DB = getFirestore(app)
const storages = getStorage()
const analytics = getAnalytics(app)
export { app, DB, analytics, storages }
as you see
const user = {
id: 1,
usename: "j",
password: "123456789",
}
in fact except of these static data wanna search and get right user info from the Firebase
I know there are a some other way of doing this but I like working with next-auth for last change wanna make sure there's a spot of light in this was ;)
i found this public repository where the author does something similar to what you want to achieve, which is create a custom token with your database credentials.
May be this repository can help you. It has a few errors, but it gave me a general idea about what to do, as I had a similar case.
try {
if (user !== null) {
await customTokenSignIn(user.id, user.email);
(await getUser(user.id)) ??
(await createUser(toReqUser(user, account)));
const data = await getUser(user.id);
setResUser(user, data as ResUser);
return true;
}
return false;
} catch (e) {
console.error(e);
return false;
}
const customTokenSignIn = async (id: string, email: string) => {
const hash = toHash(id);
const customToken = await adminAuth.createCustomToken(hash);
await auth.signInWithCustomToken(customToken).then((res) => {
res.user?.updateEmail(email);
});
await adminAuth.setCustomUserClaims(hash, { sid: id });
await createUserToken({ id: id, firebaseUid: hash });
};

adding users to firestore with the same uid that it was created with

I am trying to add new users to firestore with the same uid, when they are creatd with createUserWithEmailAndPassword. however I can add users to my user collection, but the id its given is not the same id as the uid. this is how I do it:
const auth = getAuth(db);
const app = getFirestore();
const handleSubmit = useCallback(
async (event) => {
event.preventDefault();
const { email, password, fname, lname } = event.target.elements;
// console.log("clicked", email.value, password.value);
const auth = getAuth(db);
const app = getFirestore();
createUserWithEmailAndPassword(auth, email.value, password.value)
.then(async (userCredential) => {
// Signed in
const user = userCredential.user;
try {
const docRef = await addDoc(collection(app, "users"), {
firstName: fname.value,
lastName: lname.value,
email: email.value,
age: 0,
bday: "",
});
console.log("Document written with ID: ", docRef.id);
} catch (e) {
console.error("Error adding document: ", e);
}
//=================
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ..
});
},
[history]
);
the ref id it a different id than my uid.
what do I have to do to make it save the user with the same id.
also I have tried this
app.collection("users")
doc(user.uid)
.set({...})
but this generates an error!
If you check the Web version 9 (modular) code sample in the documentation on setting a document, you'll see that setting the document goes like this:
import { doc, setDoc } from "firebase/firestore";
// Add a new document in collection "cities"
await setDoc(doc(db, "cities", "LA"), {
name: "Los Angeles",
state: "CA",
country: "USA"
});
So in your scenario, that'd be:
const docRef = doc(app, "users", user.uid)
await setDoc(docRef, {
firstName: fname.value,
lastName: lname.value,
email: email.value,
age: 0,
bday: "",
});

Next Auth: Redirect page before success login

I want make a custom login use next Auth.
What I want is, after check my email and password true, I want redirect to google auth page and after google auth true I want set the token to session and redirect to home. Is it possible to make it like that?
//This is the flow
Login -> google Auth -> Home
This is login page
//Front End
const handler = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const res = await signIn("credentials", {
redirect: false,
data: emailRef.current?.value,
password: passwordRef.current?.value,
});
if (res?.error) {
toast.error(res?.error);
setLoading(false);
} else {
Router.replace("/");
Router.events.on("routeChangeComplete", success);
}
};
This is the API Page
//API
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials: credentialsData, req: NextApiRequest) {
let client;
try {
client = await ConnectDatabase();
} catch (error) {
throw new Error("Failed connet to database.");
}
const checkEmail = await client
.db()
.collection("users")
.findOne({ email: credentials.data });
const checkPhone = await client
.db()
.collection("users")
.findOne({ phone: credentials.data });
let validData = {
password: "",
email: "",
};
if (!checkEmail && !checkPhone) {
client.close();
throw new Error("Email atau No HP tidak terdaftar.");
} else if (checkEmail) {
validData = checkEmail;
} else if (checkPhone) {
validData = checkPhone;
}
const checkPassword = await VertifyPassword(
credentials.password,
validData.password
);
if (!checkPassword) {
client.close();
throw new Error("Password Salah.");
}
client.close();
return validData;
},
}),
],
callbacks: {
async session(session) {
const data = await getSelectedUser(session.user!.email!);
session.user = data.userData;
return Promise.resolve(session);
},
},
});
In above Example I don't use google auth, so after login success it immediately redirects to home

Resources