Yup: how to compare the current value against the initial value in a `test` - yup

Can you somehow create a .test that can have access to the initial value for a given field
For example an async "is email available" validation where the initial email value should be considered valid
const schema = Yup.object({
email: Yup.string()
.test('isAvailableAsync', 'The provided email is not available', async function (newValue) {
// with alias the value is undefined
// const initial = this.parent.emailInitial;
const initial = this.parent.initialEmailRef; // it's always the same with `newValue`
// email unchanged no need to do async call
if (newValue == initial) return true;
const result = await myAsyncCheck(email);
return result.exists == false;
})
initialEmailRef: Yup.ref('email'), // tried with ref
})
.from('email', 'emailInitial', true) // tried with alias

I ended up making a factory for the schema, something like this:
const getMySchema = ({ initialEmail }) => Yup.object({
email: Yup.string()
.test('isAvailableAsync', 'The provided email is not available', async (newValue) => {
// email unchanged or returned to initial value no need to do async call
if (newValue == initialEmail) return true;
const result = await myAsyncCheck(email);
return result.exists == false;
})
})
And then use it like:
const schema = getMySchema({ initialEmail: user.email });

Related

Login post with Bcrypt always return false

Bcrypt.compare returns false no matter what
I'm currently working on a login/register feature in NextJS that utilizes bcrypt to hash and compare user passwords. I'm able to register a user with a hashed password, but when attempting to log in with bcrypt.compare(), the comparison always returns false, even when the entered password matches the hashed password.
The issue lies in this line: const isPasswordMatched = await bcrypt.compare(password, user.password);, where the compare() method is used to compare the entered password with the hashed password. Despite the method's implementation, it's not working as expected.
api/auth/[...nextauth].ts for login
const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
async authorize(credentials, req) {
await connectDB();
const { email, password }: Icredential = credentials;
// Find user by email
const user = await User.findOne({ email: email });
if (user === null) {
throw new Error('Cannot find user');
}
// Check if password from input matches with the one from db
// This is the line of the concern
const isPasswordMatched = await bcrypt.compare(password, user.password);
console.log(`Comparing ${password} to ${user.password}`);
console.log("match ?", isPasswordMatched);
// Throw error when it doesn't
if (!isPasswordMatched)
// if (password !== '123')
{
throw new Error('Invalid email or password');
}
// Return authorized user
return user;
},
credentials: undefined
}),
],
};
export default NextAuth(authOptions);
api/register for register
const registerHandler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "POST") {
try {
const { user: _regiUser } = req.body;
console.log(_regiUser)
//Check if user exists
await connectDB()
const existingUser = await User.findOne({ email: _regiUser.email }).exec();
console.log("existingUser", existingUser);
//Throw error when email is already in use
if (existingUser) {
throw new Error("Email already used");
}
//Password encrypted
const hashedPassword: string = await bcrypt.hashSync( _regiUser.password, 10 );
console.log("_regiUser.password", _regiUser.password, hashedPassword)
console.log(hashedPassword)
//Replace text password with encrypted password
_regiUser.password = hashedPassword;
console.log(_regiUser)
//Add user on database
await User.create(_regiUser)
res.end()
} catch (e: any) {
console.log(e.message)
}
}
};
export default registerHandler;
Login logic was completely correct, but I had
wrong User model like following:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
lowercase: true
},
password: {
type: String,
required: true,
lowercase: true //remove this to make it work
}
});
look at password entity, because I copy pasted from email entity, i had a wrong configuration for password. So hash stored in lowercase and this is the very reason why i got error no matter what. smh...
You're creating your password hashes using the hashSync() method (not async) but trying to run the async .compare() method when logging-in. Check out the examples.
For the comparison, you should be using:
bcrypt.compareSync(myPlaintextPassword, hash);
Otherwise, I recommend using the async/await bcrypt.hash and bcrypt.compare methods. If you want to use await bcrypto.compare(...), create your hash using:
await bcrypt.hash(password, 10);

React-query is not updating the state

I recently started to use react query, but I don't quite understand yet how the state works under the hood.
I have a query function that logs in the user:
async function signin(
model: AuthenticationControllerSignInRequest | null
): Promise<any> {
if (model) {
queryClient.invalidateQueries()
const response = await CalyxApi.authApi().authenticationControllerSignIn(
model
);
LocalStorage.set(LOCAL_STORAGE_KEY.AUTH, response.data.authToken);
return response.data.authToken
}
return loadFromStorage()
}
Inside I use loadFromStorage function that gets authToken from local storage.
async function loadFromStorage(): Promise<AuthTokenModel | undefined> {
const storedAuth = LocalStorage.get(LOCAL_STORAGE_KEY.AUTH);
if (storedAuth) {
if (new Date(storedAuth.validUntil) < new Date()) {
LocalStorage.remove(LOCAL_STORAGE_KEY.AUTH)
return undefined;
}
return storedAuth;
} else {
return undefined;
}
}
In my Login components I use the query hook passing in signin function and formik that refetches on submit:
...
const { data: auth, refetch, isLoading: authLoading } = useQuery(['auth', signinModel], () => authActions.signin(signinModel), { enabled: false });
const formik = useFormik({
validateOnChange: false,
validateOnBlur: false,
initialValues: {
email: '',
password: '',
},
validationSchema: loginFormSchema,
onSubmit: async (values) => {
await setSigninModel({
email: values.email,
password: values.password
})
await refetch()
}
});
...
This works just fine. I am able to authenticate the user which should prompt another function that fetches the user from DB:
const { data: auth } = useQuery(['auth'], () => authActions.signin(null))
const userId = auth?.userId;
console.log('useUserActions: ', userId)
async function fetchUser(): Promise<UserModel | undefined> {
if (!userId) {
errorSuccessActions.throwError('USER ID IS UNDEFINED');
return
}
const result = await CalyxApi.userApi().userControllerGetUser(userId)
if (result.data) {
const user = result.data.user
return user
}
errorSuccessActions.throwError('USER NOT FOUND IN DB');
return
}
function useFetchUser(reactQueryOptions?: {}) {
return useQuery<UserModel | undefined>(
["user", userId],
() => fetchUser(), {
...reactQueryOptions, refetchOnWindowFocus: false,
onError: (err) => errorSuccessActions.throwError(err),
onSuccess: (data) => {
queryClient.setQueryData(['user'], data);
},
initialData: () => {
const user: UserModel | undefined = queryClient.getQueryData('user')
if (user) {
return user
}
return undefined
}
})
}
This expects userId that I get from ´auth´ state. Problem is that I actually don't get it after signin function fires. I only get the state updated if I reload the page or i refocus on the tab.
I have a console.log that should log the userId but it always returns undefined. Only when I refocus on the window will it return the userId prompting to fetch the user.
I am not sure what am I missing to get the updated auth state and to get the userId right after I sign in.

Can't save to MongoDB when using beforeEach() in jestjs

I was writing a couple of tests to my test database but I can't seem to save a document('add user to database' test) . When I run the test I get the correct length of the number of users but when I actually go to the database I only see one user document(firstUser) and cannot see the newUser document. I think it is an issue with the beforeEach function as when I remove it, everything works with the only issue being the same user being repeatedly added to the database when the test is run.
Test Code
const supertest = require('supertest')
const mongoose = require('mongoose')
const app = require('../app')
const User = require('../models/user')
const bcrypt = require('bcrypt')
const api = supertest(app)
beforeEach(async () => {
await User.deleteMany({})
const passwordHash = await bcrypt.hash('12345',10)
const firstUser = new User(
{
username: "Big Mark",
password: passwordHash,
name:"Mark"
}
)
await firstUser.save()
})
describe('user tests', () => {
// Cannot see this document in mongoDB
test('add user to database', async () => {
const newUser = {
username: 'smart',
password: 'dvsgfd',
name: 'Kevin'
}
const result = await api.post('/api/users').send(newUser).expect(201)
const length = await api.get('/api/users')
expect(length._body).toHaveLength(2)
console.log("length is", length)
})
test('see if fetching works', async () => {
const fetchedUsers = await api.get('/api/users').expect(201)
})
// test('returns 404 error if username already exists', async () => {
// })
})
afterAll(() => {
mongoose.connection.close()
},100000)
Router Code
const userRouter = require('express').Router()
const User = require('../models/user')
const bcrypt = require('bcrypt')
userRouter.post('/', async (request,response) => {
console.log("request body is", request.body)
const {username,password,name} = request.body
const alreadyThere = await User.findOne({username})
if(alreadyThere == null && username.length > 2 && password.length > 2) {
const saltRounds = 10 //How many times password it gonna get hashed (Ex: 2^n times)
const passwordHash = await bcrypt.hash(password,saltRounds)
const user = new User(
{
username,
password: passwordHash,
name
}
)
const savedUser = await user.save()
console.log("SavedUser is", savedUser)
response.status(201).json({savedUser})
}
else {
response.status(404).json({error: "Username must be unique"})
}
})
userRouter.get('/', async(request,response) => {
const users = await User.find({})
response.status(201).json(users)
})
module.exports = userRouter

Why my firebase update function is not always working?

Im trying to figuring out why my firebase funtion for cheat is always creating but when like open the chat where it call create function immediately send a message the message will not be saved, because my function is not ready so how can I sole this?.
Heres my function .
export const onConversationCreated = functions.firestore.
document("Conversations/{conversationID}").onCreate((snapshot, context) => {
const data = snapshot.data();
const conversationID = context.params.conversationID;
if (data) {
const members = data.members;
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((m: string) => {
return admin.firestore().
collection("profile").doc(m).get().then((_doc) => {
const userData = _doc.data();
if (userData) {
return admin.firestore().collection("profile")
.doc(uid).collection("Conversations").doc(m).create({
"conversationID": conversationID,
"url": userData.url,
"name": userData.username,
"unseenCount": 0,
});
}
return null;
}).catch(() => {
return null;
});
});
}
}
return null;
});
export const onConversationUpdated = functions.firestore
.document("Conversations/{conversationID}").onUpdate((change, context) => {
const data = change?.after.data();
if (data) {
const members = data.members;
const lastMessage = data.messages[data.messages.length - 1];
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((u: string) => {
return admin.firestore().collection("meinprofilsettings")
.doc(uid).collection("Conversation").doc(u).update({
"lastMessage": lastMessage.message,
"timestamp": lastMessage.timestamp,
"type": lastMessage.type,
"lastmessageuid": lastMessage.senderID,
"unseenCount": admin.firestore.FieldValue.increment(1),
});
});
}
}
return null;
});
So again creating is correct working. its just need some time . And when I immediately when calling create function write a message and send it this message will not be saved until the create function is finished then I have to send again the message
enter image description here
The reason for your bug is that you don't await the execution of your async task correctly. Unfortunately the forEach doesn't support async so we need to revrite your code to something like this:
export const onConversationCreated = functions.firestore
.document("Conversations/{conversationID}")
.onCreate((snapshot, context) => {
const data = snapshot.data();
const promises: Promise<any>[] = [];
const conversationID = context.params.conversationID;
if (data) {
const members = data.members;
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((m: string) => {
promises.push(
admin
.firestore()
.collection("profile")
.doc(m)
.get()
.then((_doc) => {
const userData = _doc.data();
if (userData) {
return admin
.firestore()
.collection("profile")
.doc(uid)
.collection("Conversations")
.doc(m)
.create({
conversationID: conversationID,
url: userData.url,
name: userData.username,
unseenCount: 0,
});
}
return null;
})
);
});
}
}
return Promise.all(promises);
});
export const onConversationUpdated = functions.firestore
.document("Conversations/{conversationID}")
.onUpdate((change, context) => {
const data = change?.after.data();
const promises: Promise<any>[] = [];
if (data) {
const members = data.members;
const lastMessage = data.messages[data.messages.length - 1];
for (let index = 0; index < members.length; index++) {
const uid = members[index];
const remainingUserIDs = members.filter((u: string) => u !== uid);
remainingUserIDs.forEach((u: string) => {
promises.push(
admin
.firestore()
.collection("meinprofilsettings")
.doc(uid)
.collection("Conversation")
.doc(u)
.update({
lastMessage: lastMessage.message,
timestamp: lastMessage.timestamp,
type: lastMessage.type,
lastmessageuid: lastMessage.senderID,
unseenCount: admin.firestore.FieldValue.increment(1),
})
);
});
}
}
return Promise.all(promises);
});
We use Promise.all() to even run all your async tasks in parallel to finish the function faster and save on execution time.

Mongoose findOne variables

I have a function which passes a key val and then gets added as arguments for a .findOne() mongoose function.
getByKey = async (key, val) => {
console.log(key, val);
const user = await UserSchema.findOne({
key: val
});
console.log(user);
return user;
};
The problem is, I think mongoose is actually searching the collection for the word key instead of the what it stands for ie: "username" or "age"
It is looking for 'key' as opposed to the key your passing. You can accomplish what you're trying to do by doing something like this.
var query = {}
query[key] = value;
And then pass that query to your findOne function. See below.
getByKey = async (key, val) => {
var query = {}
query[key] = value;
const user = await UserSchema.findOne(query);
console.log(user);
return user;
};
You also might want to consider adding a callback function to in your findOne and try to log the data if it was found.
const user = await UserSchema.findOne(query, function(err, data){
if (err){
console.log(err)
} else {
console.log(data)
}
});
You can use Computed property names to handle this. Example:
const user = await UserSchema.findOne({
[key]: val
});