Menu
CoddyTech

Проверка пароля

Часть раздела Аутентификация и безопасность путешествия по Node.js на Coddy. Урок 6 из 24.

При входе в систему ключ вычисляется из введённого пароля с использованием сохранённой соли и исходной длины ключа. Новая соль дала бы другой ключ даже для правильного пароля. Исходный пароль никогда не декодируется из хранилища.

// Код приглашения был сохранён в виде salt.keyHex
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);

Сравни полученные последовательности байтов с помощью timingSafeEqual, предварительно убедившись, что их длины совпадают. Это предотвращает ранний выход, зависящий от содержимого и способный раскрыть информацию через время выполнения. Оператор === для Buffer проверяет идентичность, а обычное сравнение строк не гарантирует постоянное время выполнения. В твоей функции для паролей используется разделитель-двоеточие в записи пароля и 64-байтовый ключ. Добавленный тобой маршрут POST /login использует результат, чтобы вернуть 200 или 401.

challenge icon

Задание

Легко

На этом шаге открывается подготовленный для курса проект приватного API заметок, а не файлы проекта из урока Never Store Passwords (включая passwords.js). Ваша предыдущая работа остаётся в своём уроке. Прочитайте предоставленные файлы, прежде чем редактировать passwords.js. В этой копии есть вспомогательные функции для хеширования паролей и предоставленные маршруты отладки.

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

  1. В passwords.js добавьте и экспортируйте асинхронную функцию verifyPassword(password, stored). stored — значение, сохранённое при регистрации, например f2bb...f065:c3f7...dbd2. Возвращайте true, если password — это пароль, из которого оно было получено, и false в противном случае:
    • Возьмите соль из части перед двоеточием.
    • Хешируйте password с этой солью в ключ длиной 64 байта, так же, как это делает hashPassword.
    • Декодируйте сохранённый ключ после двоеточия из шестнадцатеричного формата. Если длины ключей в байтах различаются, возвращайте false, не вызывая timingSafeEqual, как в разделе «Сравнение байтов ключей». В противном случае возвращайте результат сравнения.
  2. В server.js импортируйте verifyPassword рядом с hashPassword. Добавьте POST /login. Он получает такое же тело запроса, как и регистрация, например {"username": "ada", "password": "correct horse"}.
    • Найдите пользователя с таким именем. verifyPassword работает медленно и возвращает промис, поэтому дождитесь его завершения.
    • Если пользователь существует и пароль верный, ответьте со статусом 200 и {"username": "ada"}.
    • В противном случае ответьте со статусом 401 и {"error": "Wrong username or password"}. 401 Unauthorized означает, что запрос не подтвердил личность того, кто его отправил.
    • Отправляйте один и тот же ответ 401 и при неправильном пароле, и если пользователь с таким именем не регистрировался. Тогда тело ответа никогда не сообщит никому, какие имена пользователей существуют.

Попробуйте: нажмите «Запустить». Отправьте POST /signup с {"username": "ada", "password": "correct horse"}: статус 201. Отправьте POST /login с тем же телом: статус 200 и {"username": "ada"}. Отправьте POST /login с {"username": "ada", "password": "wrong horse"}: статус 401 и {"error": "Wrong username or password"}. Отправьте POST /login с {"username": "nobody", "password": "correct horse"}: тот же ответ 401.

Попробуйте сами

import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';

const scryptAsync = promisify(scrypt);

// Преобразует пароль в текст, который сохраняется при регистрации.
export async function hashPassword(password) {
    const salt = randomBytes(16).toString('hex');
    const key = await scryptAsync(password, salt, 64);
    return `${salt}:${key.toString('hex')}`;
}

Все уроки раздела Аутентификация и безопасность

Потренируйтесь самостоятельно: Песочница Express