Интеграция с PostgreSQL через pg и Sequelize

При построении серверных приложений на Node.js одной из ключевых задач является безопасное хранение паролей пользователей. Использование библиотеки password-hash в связке с PostgreSQL позволяет организовать простую и надёжную схему аутентификации, не прибегая к самостоятельной реализации криптографических алгоритмов.

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


Формирование хеша пароля с использованием password-hash

Библиотека password-hash предоставляет простой API для создания и проверки хешей. При регистрации пользователя пароль преобразуется в защищённую строку:

const passwordHash = require('password-hash');

const plainPassword = 'user_password_123';
const hashedPassword = passwordHash.generate(plainPassword);

Полученный hashedPassword уже содержит информацию о соли и алгоритме, используемом для генерации.

Ключевые особенности формата хеша:

  • включает соль внутри строки;
  • содержит метаданные алгоритма;
  • подходит для прямого хранения в базе данных без дополнительной обработки.

Структура таблицы пользователей в PostgreSQL

Для хранения данных пользователей используется стандартная таблица, где пароль представлен как текстовое поле:

CRE ATE   TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

Поле password_hash предназначено исключительно для хранения результата функции password-hash.generate.


Работа с PostgreSQL через библиотеку pg

Низкоуровневая интеграция выполняется через пакет pg. Он обеспечивает прямое взаимодействие с PostgreSQL без ORM.

Подключение к базе данных

const { Pool } = require('pg');

const pool = new Pool({
    user: 'db_user',
    host: 'localhost',
    database: 'app_db',
    password: 'secure_password',
    port: 5432,
});

Сохранение пользователя с хешированным паролем

const passwordHash = require('password-hash');

async function createUser(email, password) {
    const hash = passwordHash.generate(password);

    const query = `
        INS ERT IN TO users (email, password_hash)
        VALUES ($1, $2)
        RETURNING id, email
    `;

    const values = [email, hash];

    const result = await pool.query(query, values);
    return result.rows[0];
}

Проверка пароля при авторизации

При входе пользователя выполняется выборка записи и проверка пароля через password-hash.verify.

async function authenticateUser(email, password) {
    const query = `SEL ECT * FR OM users WHERE email = $1`;
    const result = await pool.query(query, [email]);

    if (result.rows.length === 0) {
        return null;
    }

    const user = result.rows[0];

    const isValid = passwordHash.verify(password, user.password_hash);

    if (!isValid) {
        return null;
    }

    return user;
}

Механизм проверки полностью скрывает детали хеширования, так как библиотека извлекает соль и параметры алгоритма из сохранённой строки.


Интеграция с Sequelize

ORM Sequelize упрощает работу с моделями и миграциями, сохраняя совместимость с PostgreSQL и хешированием паролей.

Определение модели пользователя

const { Model, DataTypes } = require('sequelize');

class User extends Model {}

User.init({
    email: {
        type: DataTypes.STRING,
        unique: true,
        allowNull: false,
    },
    password_hash: {
        type: DataTypes.TEXT,
        allowNull: false,
    }
}, {
    sequelize,
    modelName: 'User',
    tableName: 'users',
    timestamps: true
});

Хеширование пароля через хуки Sequelize

Sequelize позволяет автоматизировать процесс хеширования с помощью hooks:

const passwordHash = require('password-hash');

User.beforeCreate((user) => {
    user.password_hash = passwordHash.generate(user.password_hash);
});

При создании записи в модель перед сохранением пароль автоматически преобразуется в хеш.


Проверка пароля в методах модели

Логика аутентификации может быть вынесена в метод модели:

User.prototype.validatePassword = function(password) {
    return passwordHash.verify(password, this.password_hash);
};

Использование:

const user = await User.findOne({ where: { email } });

if (!user || !user.validatePassword(password)) {
    return null;
}

Обновление пароля пользователя

При изменении пароля необходимо повторно выполнять хеширование:

User.beforeUpdate((user) => {
    if (user.changed('password_hash')) {
        user.password_hash = passwordHash.generate(user.password_hash);
    }
});

Это предотвращает сохранение открытого текста при обновлении записи.


Особенности хранения и безопасности

При работе с password-hash и PostgreSQL важно учитывать ряд практических аспектов:

  • хеш всегда должен храниться в отдельном поле типа TEXT;
  • прямое сравнение паролей в SQL-запросах недопустимо;
  • повторное хеширование одного и того же пароля даёт разные результаты из-за соли;
  • проверка выполняется только через verify, а не через сравнение строк.

Работа с миграциями Sequelize

При использовании миграций структура таблицы фиксируется отдельно от логики приложения:

module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable('users', {
            id: {
                type: Sequelize.INTEGER,
                autoIncrement: true,
                primaryKey: true
            },
            email: {
                type: Sequelize.STRING,
                allowNull: false,
                unique: true
            },
            password_hash: {
                type: Sequelize.TEXT,
                allowNull: false
            },
            created_at: {
                type: Sequelize.DATE,
                defaultValue: Sequelize.NOW
            }
        });
    }
};

Сценарии масштабирования

При росте нагрузки архитектура с PostgreSQL и Sequelize остаётся устойчивой при соблюдении следующих принципов:

  • индексация поля email для ускорения поиска;
  • использование connection pool в pg;
  • разделение логики аутентификации и бизнес-логики;
  • минимизация количества запросов при авторизации.

Комбинирование pg и Sequelize в одном проекте

Допустимо одновременное использование низкоуровневого pg и ORM Sequelize:

  • pg применяется для сложных или оптимизированных SQL-запросов;
  • Sequelize используется для стандартных CRUD-операций и моделей.

При этом хеширование паролей остаётся независимым слоем и одинаково работает в обеих схемах интеграции.


Типовые ошибки при работе с password-hash

В практических реализациях часто встречаются следующие проблемы:

  • сохранение пароля до хеширования;
  • повторное хеширование уже хешированного значения;
  • использование === вместо verify;
  • попытка извлечь исходный пароль из хеша;
  • хранение паролей вне защищённого поля базы данных.

Такие ошибки приводят к нарушению безопасности и некорректной аутентификации.


Производительность операций хеширования

Генерация и проверка хеша являются CPU-зависимыми операциями. При массовой регистрации или авторизации следует учитывать:

  • увеличение времени ответа при высоком RPS;
  • необходимость масштабирования через горизонтальные инстансы;
  • возможную замену алгоритма на более современный (bcrypt/argon2) при росте требований к безопасности.