Я хочу добавить функцию сброса / забытого пароля в мое личное приложение express. js. Я решил реализовать его аналогичным образом, как это делает Django.
По сути, он генерирует уникальный токен на основе пользователя (идентификатор, хешированный пароль, адрес электронной почты, время последнего входа в систему и текущее время). , все смешанное с unqiue паролем и солью). Затем пользователь получает этот токен в своей «ссылке для сброса пароля». Кто-то объяснил это лучше меня в одном из ответов stackoverflow .
А вот исходный код Django PasswordResetTokenGenerator
class
Я опубликую свою реализацию javascript внизу. Было бы неплохо, если бы вы проверили его на возможные fl aws, но это не мой главный вопрос:)
Итак, пользователь получает электронное письмо со ссылкой для сброса пароля. Ссылка выглядит следующим образом https://example.com/reset-password/MQ/58ix7l-35858854f74c35d0c64a5a17bd127f71cd3ad1da
, где:
MQ
- это кодированный пользователем base64 идентификатор (в данном примере - 1) 58ix7l
- это временная метка, закодированная в base36 35858...
является фактическим токеном
Пользователь нажимает на ссылку. Сервер получает запрос GET -> сервер проверяет, существует ли пользователь с таким идентификатором -> затем сервер проверяет правильность токена. Если все в порядке, сервер отправляет пользователю html ответ с формой «установить новый пароль».
До сих пор все было почти точно так же, как django (с небольшими отличиями). Но сейчас я хочу сделать несколько иначе. Django (после получения запроса GET) устанавливает анонимный сеанс, сохраняет токен в сеансе и перенаправляет (302) для сброса формы пароля. На стороне клиента нет никаких признаков токена. Пользователь заполняет форму, POST-запрос отправляется на сервер с новым паролем. Сервер снова проверяет токен (сохраняется в сеансе). Если все правильно - пароль изменен.
По какой-то причине (это усложнит мое приложение :)), я не хочу добавлять анонимный сеанс, я не хочу хранить токен в сеансе.
Я хочу просто взять токен из req.params
-> избежать его -> проверить его действительность -> и отправить пользователю с формой, например:
<form action="/reset-password" method="POST">
<label for="new-password">New password</label><input id="new-password" type="password" name="new-password">
<label for="repeat-new-password">Repeat new password</label><input id="repeat-new-password" type="password" name="repeat-new-password">
<input name="token" type="hidden" value="58ix7l-35858854f74c35d0c64a5a17bd127f71cd3ad1da">
<input type="submit" value="Set new password">
</form>
Пользователь отправляет форму, сервер снова проверяет токен, а затем меняет пароль.
Поэтому после стены текста у меня возникает вопрос:
Безопасно ли хранить токен в html Форма как это?
Я могу подумать об одной возможной угрозе: злой пользователь может отправить кому-то ссылку с <script>alert('boo!')</script>
вместо токена. Но это не должно быть проблемой, если токен проверен и экранирован ранее. Любые другие возможные дыры?
Как я уже говорил, я публикую свою реализацию generateToken
, checkToken
javascript, на всякий случай ...
генерировать -change-password-token. js
const { differenceInSeconds } = require('date-fns');
const makeTokenWithTimestamp = require('../crypto/make-token-with-timestamp');
function generateChangePasswordToken(user) {
const timestamp = differenceInSeconds(new Date(), new Date(2010, 1, 1));
const token = makeTokenWithTimestamp(user, timestamp);
return token;
}
module.exports = generateChangePasswordToken;
verify-change-password-token. js
const crypto = require('crypto');
const { differenceInSeconds } = require('date-fns');
const makeTokenWithTimestamp = require('../crypto/make-token-with-timestamp');
function verifyChangePasswordToken(user, token) {
const timestamp = parseInt(token.split('-')[0], 36);
const difference = differenceInSeconds(new Date(), new Date(2010, 1, 1)) - timestamp;
if (difference > 60 * 60 * 24) {
return false;
}
const newToken = makeTokenWithTimestamp(user, timestamp);
const valid = crypto.timingSafeEqual(Buffer.from(token), Buffer.from(newToken));
if (valid === true) {
return true;
}
return false;
}
module.exports = verifyChangePasswordToken;
make-token-with-timestamp. js
const crypto = require('crypto');
function saltedHmac(keySalt, value, secret) {
const hash = crypto.createHash('sha1').update(keySalt + secret).digest('hex');
const hmac = crypto.createHmac('sha1', hash).update(value).digest('hex');
return hmac;
}
function makeHashValue(user, timestamp) {
const { last_login: lastLogin, id, password } = user;
const loginTimestamp = lastLogin ? lastLogin.getTime() : '';
return String(id) + password + String(loginTimestamp) + String(timestamp);
}
function makeTokenWithTimestamp(user, timestamp) {
const timestamp36 = timestamp.toString(36);
const hashValue = makeHashValue(user, timestamp);
const keySalt = process.env.KEY_SALT;
const secret = process.env.SECRET_KEY;
if (!(keySalt && secret)) {
throw new Error('You need to set KEY_SALT and SECRET_KEY in env variables');
}
const hashString = saltedHmac(keySalt, hashValue, secret);
return `${timestamp36}-${hashString}`;
}
module.exports = makeTokenWithTimestamp;
Thx