import { API } from "../lib/api.js"; import logging from "../lib/logging.js"; import assert from "assert"; import { User } from "../lib/models.js"; import { send_reset, send_reset_finished } from '../lib/queues.js'; import differenceInHours from 'date-fns/differenceInHours/index.js'; const rules_request = { email: 'required|email', } const rules_finish = { code: 'required', password: 'required|same:password_repeat', password_repeat: 'required', } const log = logging.create("/api/password_reset.js"); // this doesn't change so just do it here const RESET_MAX = 10; const RESET_HOURS_MAX = 10; export const post = async (req, res) => { const api = new API(req, res); const finalize = req.body.finalize; const rules = finalize ? rules_finish : rules_request const form = api.validate(rules); try { if(!form._valid) { // abort with errors return api.validation_error(res, form); } else { assert(form._valid, "Form is invalid but shouldn't be."); const { email } = form; const user = await User.first({email}); if(!user) { return api.error(400, "User with that email doesn't exist."); } else if(finalize) { const { code, password } = form; if(user.reset_count > RESET_MAX) { return api.error(400, "Too many reset attempts in a short time. Contact support to unlock your account."); } else if(user.reset_code !== code.trim()) { 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 user.password = User.encrypt_password(password); send_reset_finished(user, req.ip, req.headers['user-agent']); return api.reply(200, {message: "OK"}); } } else if(user.reset_sent_at && differenceInHours(Date.now(), user.reset_sent_at) < RESET_HOURS_MAX) { return api.error(400, `Only one request every ${RESET_HOURS_MAX} hours allowed.`); } else { // new request, send out a code const user_req = { email, id: user.id}; send_reset(user_req, req.ip, req.headers['user-agent']); return api.reply(200, {message: "OK"}); } } } catch(error) { log.error(error); return api.error(500, "Internal Server Error"); } }