Browse Source

Prevent repeated reset code guesses with a simple count.

master
Zed A. Shaw 1 week ago
parent
commit
42698af33c
  1. 6
      api/password_reset.js
  2. 12
      migrations/20210911191222_user_reset_count.cjs
  3. 14
      queues/mail.js

6
api/password_reset.js

@ -6,6 +6,7 @@ import { send_reset, send_reset_finished } from '../lib/queues.js';
const log = logging.create("/api/password_reset.js");
// this doesn't change so just do it here
const RESET_MAX = 10;
export const post = async (req, res) => {
const api = new API(req, res);
@ -22,7 +23,10 @@ export const post = async (req, res) => {
assert(code, "Reset code required.");
// they have a code submitted, so check it
if(user.reset_code !== code) {
if(user.reset_count > RESET_MAX) {
return api.error(400, "Can't reset your password at this time.");
} else if(user.reset_code !== code) {
await User.update({id: user.id}, { reset_count: user.reset_count + 1 });
return api.error(400, "Reset code doesn't match.");
} else {
// SECURITY: encrypt the password here so that it's not left in the queue unencrypted

12
migrations/20210911191222_user_reset_count.cjs

@ -0,0 +1,12 @@
exports.up = async (knex) => {
// using async/await lets you work with multiple tables in one up/down
await knex.schema.alterTable('user', (table) => {
table.integer("reset_count").defaultsTo(0).notNullable();
});
}
exports.down = async (knex) => {
return await knex.schema.alterTable('user', (table) => {
table.dropColumn("reset_count");
});
}

14
queues/mail.js

@ -55,13 +55,14 @@ export const reset = async (job) => {
differenceInHours(Date.now(), user.reset_sent_at) < RESET_HOURS_MAX)
{
// prevent repeated high speed reset attempts by using the advanced technolog of...time
log.warn(`User ${user.id} reset request time remaining not long enough ${time_remaining}`);
// TODO: we now simply skip this user's request but maybe flag them to potentially block?
log.warn(`User ${user.id} reset request time remaining not long enough, ignoring their request.`);
// update the reset count so they can only attempt this a certain number of times
await User.update({id: user.id}, { reset_count: user.reset_count + 1 });
} else {
// reset is good, all variables are good, so process the request
user.reset_code = User.random_hex(RESET_CODE_SIZE);
await User.update({email}, {reset_code: user.reset_code, reset_sent_at: Date.now()});
await User.update({id: user.id}, {reset_code: user.reset_code, reset_sent_at: Date.now()});
const reset_data = {
company,
user,
@ -95,7 +96,12 @@ export const reset_finished = async (job) => {
const user = job.data.user;
// update the user's password now and then send the email
await User.update({id: user.id}, {password: user.password, reset_code: null, reset_sent_at: null});
await User.update({id: user.id}, {
password: user.password,
reset_code: null,
reset_sent_at: null,
reset_count: 0
});
const reset_finished_data = {
company,

Loading…
Cancel
Save