Проблема несоответствия пароля с bcryptjs в Angular, NodeJS и MySQL

Недавно у меня возникла проблема, когда я работал над личным проектом с использованием Angular, NodeJS и MySQL. Я выполнял процессы регистрации и входа в систему и решил зашифровать пароль, используя bycrptjs. Проблема в том, что она не работает, потому что при отправке формы регистрации она показывает мне зашифрованный пароль, но после того, как я подтвердил свою учетную запись и вошел в систему, проблема возникла, когда я получил личное предупреждение:

Password doesn't match! 4 attempts remaining!

Я не понял, почему пришел такой ответ, потому что я вставил «Пароль@1234» как для регистрации, так и для входа в систему, как показано на изображениях ниже: Выше страница регистрации. Выше страница входа в систему. Я пытаюсь это проверить, войдя в консоль сервера с обоими паролями, и получилось вот так:

Password stored in DB: $2a$12$.qMLUVnYfaLgQtJU9HWMxuBQea/W8AOGD2NuCHq5Q9OVLXXmBvtwG
Inputted password: $2a$12$UTCJpuu34rc9BiFbCQoHQuhfXpJVfrV5.zIVTfdK/7hPJ/Axygcg6

А потом, когда я попытался пользоваться Интернетом, я понял, что не могу конвертировать эти зашифрованные пароли из соображений безопасности. Вот мой код Angular:

Внешний интерфейс

регистрация.comComponent.ts:

import { CommonModule } 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';

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 };
    };
  }

  signUp(): void {
    if (this.signUpForm.valid) {
      const formData = this.signUpForm.value;
      this.httpClient.post('http://localhost:3000/sign-up', formData).subscribe(
        (response) => {
          const message = (response as SignUpResponse).message;
          this.appendAlert(message, "success", 1);
          this.signUpSuccess = true;
        },
        (error: HttpErrorResponse) => {
          this.appendAlert(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 } };
  };
}

вход в систему.comComponent.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';

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,}$');
  }
  logIn(): void {
    if (this.logInForm.valid) {
      const formData = this.logInForm.value;
      this.httpClient.post('http://localhost:3000/log-in', formData).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;
    }

  }
}

Задний конец

регистрация.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;

// 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,
    }
});

//Encrypt password method
function encryptPassword (password) {
    const saltRounds = 12;
    const salt = bcrypt.genSaltSync(saltRounds);
    const hash = bcrypt.hashSync(password, salt);
    return hash;
}

// 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 encryptedPwd = encryptPassword(userPwd);
    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, encryptedPwd, 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' });
                }
            });
        }
    });
});

// Verify PIN
router.post('/verify-pin', (req, res) => {
    const { userName ,verificationPin } = req.body;
    const sql = 'SELECT * FROM users WHERE name = ? AND verificationPin = ?';
    connection.query(sql, [userName, verificationPin], (err, results) => {
        console.info(sql, userName, verificationPin);
        console.info(results.length);
        if (err) {
            console.error('Error while verifying PIN:', err);
            return res.status(500).json({ error: 'Error while verifying PIN' });
        } else if (results.length > 0) {
            // PIN is correct, update the isVerified field for the user
            const updateSql = 'UPDATE users SET isVerified = 1 WHERE name = ?';
            connection.query(updateSql, [userName], (updateErr, updateResults) => {
                if (updateErr) {
                    console.error('Error while updating verification status:', updateErr);
                    return res.status(500).json({ error: 'Error while updating verification status' });
                } else {
                    console.info('User verified:', updateResults);
                    return res.status(200).json({ message: 'User has been verified successfully! Now you can log in.' });
                }
            });
        } else {
            // PIN is incorrect
            return res.status(401).json({ message: 'Incorrect PIN. Make sure you first copy and paste in the field from the email we sent you.' });
        }
    });
});

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

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

module.exports = router;

вход в систему.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;

//Encrypt password method
function encryptPassword (password) {
    const saltRounds = 12;
    const salt = bcrypt.genSaltSync(saltRounds);
    const hash = bcrypt.hashSync(password, salt);
    return hash;
}

// 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 {
                    const encryptedPwd = encryptPassword(userPwd)
                    console.info("Password stored in DB: "+user.pwd);
                    console.info("Inputted password: "+encryptedPwd);
                    if (bcrypt.compareSync(encryptedPwd, 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;

Я не понимаю, почему он так себя ведет. Если вам нужно что-то еще, пожалуйста, дайте мне знать в комментариях.

Любая помощь будет оценена по достоинству. Спасибо!

Это не то, как использовать хеширование паролей. Вы всегда отправляете пароль в виде открытого текста с внешнего интерфейса на серверный. В серверной части вы хешируете пароль для хранилища. При проверке пароля для входа в систему вы берете пароль в виде открытого текста из внешнего интерфейса и используете специальную функцию библиотеки bcrypt, чтобы сравнить его с сохраненным хешированным паролем.

deceze 17.08.2024 10:36

@deceze Спасибо! Позвольте мне попробовать изменить оба интерфейса и зарегистрироваться, чтобы посмотреть, будет ли решена моя проблема.

Sithila Somaratne 17.08.2024 10:43

@deceze Это странно! Я изменил как интерфейсную, так и серверную часть, как вы мне сказали. Но все равно возникает та же проблема. Позвольте мне обновить код в моем вопросе.

Sithila Somaratne 17.08.2024 10:54

Вот и все! Если кто-то что-то знает по этому вопросу, пожалуйста, ответьте мне.

Sithila Somaratne 17.08.2024 11:00

Вы по-прежнему «шифруете» пароль при входе в систему. Не.

deceze 17.08.2024 11:06

@deceze На самом деле я не шифрую во Front End. Я шифрую в Back End. Позвольте мне обновить код. Я обновил код сейчас.

Sithila Somaratne 17.08.2024 11:08

Вы ВСЕ ЕЩЕ «шифруете» пароль перед сравнением. НЕ. Вы просто хотите bcrypt.compareSync(userPwd, user.pwd), а не encryptedPwd.

deceze 17.08.2024 11:13

@deceze, ты хочешь сказать, что метод CompareSync имеет 2 параметра, первый из которых — незашифрованный пароль, а другой — зашифрованный пароль? Если да, позвольте мне проверить. Также я не могу писать в чатах.

Sithila Somaratne 17.08.2024 11:16

@deceze, действительно ты прав. Спасибо!

Sithila Somaratne 17.08.2024 11:18
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
2
9
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решение слишком простое.

Нет необходимости шифровать пароль во время процесса входа в систему, вы можете напрямую сравнить req.body.password с паролем, сохраненным в базе данных, с помощью метода bcrypt.comapare().

под капотом bcrypt зашифровывает пароль той же солью и сравнивает с зашифрованным паролем.

Надеюсь, вы поняли решение.

Что ж, несмотря на то, что я понял проблему благодаря @deceze, вы ясно помогли мне понять мою проблему, поэтому я принял ваш ответ.

Sithila Somaratne 19.08.2024 18:59

Другие вопросы по теме