Password mismatch issue with bcryptjs in Angular, NodeJS, and MySQL

I’ve recently got myself in a problem while I was doing a personal project using Angular, NodeJS and MySQL.
I was doing the sign up and log in processes and I decided to encrypt password using bycrptjs.
The problem is that it is not working because when the sign up form is submitted it shows me an encrypted password but after I verified my account and went to the log in the problem came when I received my personal alert:

Password doesn't match! 4 attempts remaining!

I didn’t understood why it came such response, because I inserted “Password@1234” for both sign up and log in as shown in the images below:
Above is the sign up page
Above is the log in page
I try to verify this by logging into the server console both passwords and it came like this:

Password stored in DB: $2a$12$7c4L8iqE2d3EpFenFoaxI.iWEvWybPy1Ab8Dxe7Qv4pNOcByNEC1i
Inputted password: $2a$12$Z04YqWKs7YVJ8aYDumuDBuPSpE/H90SAUR0hiosmUw6iJ4Hh7NUCa

And then, when I tried to surf in Internet I realized that I cannot convert those encrypted passwords due to security purposes.
Here’s my Angular code:
Front End
sign-up.component.ts:

import { CommonModule, DatePipe } from '@angular/common';
import { HttpClient, HttpErrorResponse, HttpClientModule } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, ValidationErrors, ValidatorFn, Validators } from '@angular/forms';
import * as bcrypt from 'bcryptjs';

interface SignUpResponse {
  message: string;
  status: number
}

@Component({
  selector: 'app-sign-up',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, HttpClientModule],
  templateUrl: './sign-up.component.html',
  styleUrl: './sign-up.component.css'
})
export class SignUpComponent implements OnInit {
  hide: boolean = true;
  signUpForm!: FormGroup;
  logInForm!: FormGroup;
  signUpSuccess: boolean = false;
  verifyPinForm: any;
  alertPlaceholder1!: HTMLDivElement;
  alertPlaceholder2!: HTMLDivElement;

  constructor(private httpClient: HttpClient, private fb: FormBuilder) {}

  ngOnInit(): void {
    this.signUpForm = this.fb.group({
      userName: ['', Validators.required],
      userEmail: ['', [Validators.required, Validators.email]],
      userContact: ['', Validators.required],
      userDOB: ['', [Validators.required, minimumAgeValidator(18)]],
      userPwd: ['', [Validators.required, Validators.minLength(8), this.passwordStrengthValidator()]],
      confirmPwd: ['', [Validators.required, Validators.minLength(8), this.passwordStrengthValidator()]]
    }, { validators: this.fieldsMatchValidator('userPwd', 'confirmPwd') });
    this.verifyPinForm = this.fb.group({
      pin: ['', Validators.required]
    });
    this.alertPlaceholder1 = document.getElementById('liveAlertPlaceholder1') as HTMLDivElement;
    this.alertPlaceholder2 = document.getElementById('liveAlertPlaceholder2') as HTMLDivElement;
  }

  passwordStrengthValidator(): ValidatorFn {
    return Validators.pattern('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\W_])[A-Za-z\d\W_].{8,}$');
  }

  fieldsMatchValidator(...fields: string[]): ValidatorFn {
    return (group: AbstractControl): { [key: string]: any } | null => {
      let isValid = true;
      for (let i = 0; i < fields.length; i += 2) {
        let field = group.get(fields[i]);
        let matchingField = group.get(fields[i + 1]);
        if (field && matchingField && field.value !== matchingField.value) {
          isValid = false;
          matchingField.setErrors({ fieldsDoNotMatch: true });
        } else {
          matchingField?.setErrors(null);
        }
      }
      return isValid ? null : { 'fieldsDoNotMatch': true };
    };
  }

  encryptPassword(password: string): string {
    const saltRounds = 12;
    const salt = bcrypt.genSaltSync(saltRounds);
    const hash = bcrypt.hashSync(password, salt);
    return hash;
  }

  signUp(): void {
    if (this.signUpForm.valid) {
      const formWithEncryptedPwd = {
        ...this.signUpForm.value,
        confirmPwd: null,
        userPwd: this.encryptPassword(this.signUpForm.value.userPwd)
      };
      this.httpClient.post('http://localhost:3000/sign-up', formWithEncryptedPwd).subscribe(
        (response) => {
          const message = (response as SignUpResponse).message;
          this.appendAlert(message, "success", 1);
          this.signUpSuccess = true;
        },
        (error: HttpErrorResponse) => {
          this.appendAlert(error.error.message, "danger", 1)
        }
      );
    }
  }

  verifyPin(): void {
    const pin = this.verifyPinForm.get('pin')?.value;
    if (pin) {
      this.httpClient
        .post('http://localhost:3000/verify-pin', {
          userName: this.signUpForm.value.userName,
          verificationPin: pin,
        })
        .subscribe(
          (response: any) => {
            this.appendAlert(response.message, 'success', 2);
          },
          (error: HttpErrorResponse) => {
            this.appendAlert(error.error.message, 'danger', 2);
          }
        );
    }
  }

  appendAlert = (message: any, type: any, option: number): void => {
    const wrapper = document.createElement('div')
    if (type === 'success') {
      wrapper.innerHTML = [
        `<div class="alert alert-${type} alert-dismissible" role="alert">`,
        `   <div><i class="bi bi-check-circle-fill"></i> ${message}</div>`,
        '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
        '</div>'
      ].join('')
    } else {
      wrapper.innerHTML = [
        `<div class="alert alert-${type} alert-dismissible" role="alert">`,
        `   <div><i class="bi bi-x-circle-fill"></i> ${message}</div>`,
        '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
        '</div>'
      ].join('')
    }
    switch (option) {
      case 1:
        this.alertPlaceholder1.append(wrapper);
        break;
      case 2:
        this.alertPlaceholder2.append(wrapper);
        break;
      default:
        alert("ERROR! SOMETHING WENT WRONG!")
        break;
    }
  }
}

export function minimumAgeValidator(minAge: number) {
  return (control: AbstractControl): ValidationErrors | null => {
    const birthDate = new Date(control.value);
    const today = new Date();
    let age = today.getFullYear() - birthDate.getFullYear();
    const monthDifference = today.getMonth() - birthDate.getMonth();

    if (monthDifference < 0 || (monthDifference === 0 && today.getDate() < birthDate.getDate())) {
      age--;
    }

    return age >= minAge ? null : { 'minimumAge': { value: minAge } };
  };
}

log-in.component.ts:

import { CommonModule } from '@angular/common';
import { HttpClient, HttpErrorResponse, HttpClientModule } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, ValidatorFn, Validators } from '@angular/forms';
import * as bcrypt from 'bcryptjs';

interface LogInResponse {
  message: string;
  status: number
}

@Component({
  selector: 'app-log-in',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, HttpClientModule],
  templateUrl: './log-in.component.html',
  styleUrl: './log-in.component.css'
})
export class LogInComponent implements OnInit{
  logInForm!: FormGroup;
  hide: boolean = true;
  logInSuccess: boolean = false;
  alertPlaceholder!: HTMLDivElement;

  constructor(private httpClient: HttpClient, private fb: FormBuilder) { }

  ngOnInit(): void {
    this.logInForm = this.fb.group({
      userName: ['', Validators.required],
      userPwd: ['', [Validators.required, Validators.minLength(8), this.passwordStrengthValidator()]],
    });
    this.alertPlaceholder = document.getElementById('liveAlertPlaceholder3') as HTMLDivElement;
  }
  passwordStrengthValidator(): ValidatorFn {
    return Validators.pattern('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[\W_])[A-Za-z\d\W_].{8,}$');
  }
  encryptPassword(password: string): string {
    const saltRounds = 12;
    const salt = bcrypt.genSaltSync(saltRounds);
    const hash = bcrypt.hashSync(password, salt);
    return hash;
  }
  logIn(): void {
    if (this.logInForm.valid) {
      const encryptedPassword = this.encryptPassword(this.logInForm.value.userPwd);
      const formWithEncryptedPassword = {
        ...this.logInForm.value,
        userPwd: encryptedPassword
      };
      this.httpClient.post('http://localhost:3000/log-in', formWithEncryptedPassword).subscribe(
        (response) => {
          const message = (response as LogInResponse).message;
          this.appendAlert(message, "success", 1);
          this.logInSuccess = true;
        },
        (error: HttpErrorResponse) => {
          this.appendAlert(error.error.message, "danger", 1)
          if (error.error.message.includes('Account locked')) {
            this.logInForm.disable();
            setTimeout(() => this.logInForm.enable(), 300000); // 5 minutes
          }
        }
      );

    }
  }
  appendAlert = (message: any, type: any, option: number): void => {
    const wrapper = document.createElement('div')
    if (type === 'success') {
      wrapper.innerHTML = [
        `<div class="alert alert-${type} alert-dismissible" role="alert">`,
        `   <div><i class="bi bi-check-circle-fill"></i> ${message}</div>`,
        '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
        '</div>'
      ].join('')
    } else {
      wrapper.innerHTML = [
        `<div class="alert alert-${type} alert-dismissible" role="alert">`,
        `   <div><i class="bi bi-x-circle-fill"></i> ${message}</div>`,
        '   <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>',
        '</div>'
      ].join('')
    }
    switch (option) {
      case 1:
        this.alertPlaceholder.append(wrapper);
        break;
      default:
        alert("ERROR! SOMETHING WENT WRONG!")
        break;
    }

  }
}

Back End
sign-up.js:

"use strict"
const express = require('express');
const router = express.Router();
const connection = require('./db/connection');
const crypto = require('crypto');
const nodemailer = require('nodemailer');

// Environment variables should be used here
const emailUser = process.env.EMAIL_USER;
const emailPass = process.env.EMAIL_PASS;

// Configure the email transport using the default SMTP transport and your email account details
const transporter = nodemailer.createTransport({
    service: 'gmail', // Use your email provider
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
        user: emailUser, // Your email address
        pass: emailPass // Your email password
    },
    tls: {
        rejectUnauthorized: true,
    }
});

// Sign-up endpoint
router.post('/sign-up', (req, res) => {
    const { userName, userEmail, userContact, userDOB, userPwd} = req.body;
    if (!userName || !userEmail || !userContact || !userDOB || !userPwd ) {
        return res.status(400).json({ error: 'All fields are required' });
    }

    const verificationPin = crypto.randomInt(100000, 1000000).toString();
    const isVerified = 0;

    const sql = 'INSERT INTO users (name, email, contactNumber, dob, pwd, verificationPin, isVerified) VALUES (?, ?, ?, ?, ?, ?, ?)';
    const userValues = [userName, userEmail, userContact, userDOB, userPwd, verificationPin, isVerified];

    connection.execute(sql, userValues, (err, _results) => {
        if (err) {
            console.error(err);
            return res.status(500).json({ error: 'Error while inserting data' });
        } else {
            sendEmail(userEmail, userName, verificationPin, (emailError) => {
                if (emailError) {
                    return res.status(500).json({ message: 'Something went wrong while sending email' });
                } else {
                    return res.status(200).json({ message: 'User has been created successfully! Now check your email to insert PIN below' });
                }
            });
        }
    });
});

let sendEmail = (usrEmail, usrName, verifyPin, callback) => {
    const mailOptions = {
        from: emailUser,
        to: usrEmail,
        subject: 'Verify Your Account',
        text: `Hello ${usrName},nnWelcome to BeQuick!nnYour verification PIN is ${verifyPin}. Please enter this PIN to verify your account.nnThank you!nBeQuick Company.`
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            console.error('Error while sending email:', error);
            callback(error, null);
        } else {
            console.log('Email sent:', info.response);
            callback(null, info.response);
        }
    });
};

module.exports = router;

log-in.js:

"use strict";
const express = require('express');
const router = express.Router();
const connection = require('./db/connection');
const crypto = require('crypto');
const nodemailer = require('nodemailer');
const bcrypt = require('bcryptjs');

// Environment variables should be used here
const emailUser = process.env.EMAIL_USER;
const emailPass = process.env.EMAIL_PASS;

// Log-in endpoint
router.post('/log-in', (req, res) => {
    const { userName, userPwd } = req.body;
    const sql = 'SELECT * FROM users WHERE name = ?';

    connection.execute(sql, [userName], (err, results) => {
        if (err) {
            console.error('Error while logging: ', err);
            return res.status(500).json({ error: 'Oops! Something went wrong! Please try again later.' });
        } else {
            if (results.length === 0) {
                return res.status(400).json({ message: `Username doesn't exist! You may sign up or try again.` });
            } else {
                const user = results[0];
                const now = new Date();

                if (user.lock_until && now < new Date(user.lock_until)) {
                    return res.status(400).json({ message: `Account locked. Try again later. Account will be unlocked at ${user.lock_until}`});
                } else {
                    console.log("Password stored in DB: "+user.pwd);
                    console.log("Inputted password: "+userPwd);
                    if (bcrypt.compareSync(userPwd, user.pwd)) {
                        connection.execute('UPDATE users SET failed_attempts = 0, lock_until = NULL WHERE name = ?', [userName]);
                        return res.status(200).json({ message: 'You successfully logged in!' });
                    }
                     else {
                        let failedAttempts = user.failed_attempts + 1;
                        let lockUntil = null;

                        if (failedAttempts >= 5) {
                            lockUntil = new Date(now.getTime() + 5 * 60000); // 5 minutes lock
                        }

                        connection.execute('UPDATE users SET failed_attempts = ?, lock_until = ? WHERE name = ?', [failedAttempts, lockUntil, userName]);
                        return res.status(400).json({ message: `Password doesn't match! ${5 - failedAttempts} attempts remaining!` });
                    }
                }
            }
        }
    });
});

module.exports = router;

connection.js:

"use strict"
require('dotenv').config();
const mysql = require('mysql2');

//MySQL connection
const connection = mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASS,
    database: process.env.DB_NAME
});

connection.connect(err => {
    if (err) throw err;
    console.log('Connected to the MySQL server.');
});

module.exports = connection;

I am not understanding why does it behave like this.
If you need anything else, please let me know in comments.

Any help would be appreciated. Thanks!

2

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật