Проверка пароля
Часть раздела Аутентификация и безопасность путешествия по 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.
Задание
ЛегкоНа этом шаге открывается подготовленный для курса проект приватного API заметок, а не файлы проекта из урока Never Store Passwords (включая passwords.js). Ваша предыдущая работа остаётся в своём уроке. Прочитайте предоставленные файлы, прежде чем редактировать passwords.js. В этой копии есть вспомогательные функции для хеширования паролей и предоставленные маршруты отладки.
Теперь при регистрации сохраняются хеши с солью. Добавьте вход, который проверяет пароль на соответствие сохранённому при регистрации.
- В
passwords.jsдобавьте и экспортируйте асинхронную функциюverifyPassword(password, stored).stored— значение, сохранённое при регистрации, напримерf2bb...f065:c3f7...dbd2. Возвращайтеtrue, еслиpassword— это пароль, из которого оно было получено, иfalseв противном случае:- Возьмите соль из части перед двоеточием.
- Хешируйте
passwordс этой солью в ключ длиной 64 байта, так же, как это делаетhashPassword. - Декодируйте сохранённый ключ после двоеточия из шестнадцатеричного формата. Если длины ключей в байтах различаются, возвращайте
false, не вызываяtimingSafeEqual, как в разделе «Сравнение байтов ключей». В противном случае возвращайте результат сравнения.
- В
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