feat: present a password challenge on email update flow

isekai-main
Julian Lam 3 years ago
parent 9ee1afbb0f
commit 7fcee42be9

@ -216,5 +216,6 @@
"emailUpdate.intro": "Please enter your email address below. This forum uses your email address for scheduled digest and notifications, as well as for account recovery in the event of a lost password.", "emailUpdate.intro": "Please enter your email address below. This forum uses your email address for scheduled digest and notifications, as well as for account recovery in the event of a lost password.",
"emailUpdate.optional": "<strong>This field is optional</strong>. You are not obligated to provide your email address, but without a validated email you will not be able to recover your account or login with your email.", "emailUpdate.optional": "<strong>This field is optional</strong>. You are not obligated to provide your email address, but without a validated email you will not be able to recover your account or login with your email.",
"emailUpdate.required": "<strong>This field is required</strong>.", "emailUpdate.required": "<strong>This field is required</strong>.",
"emailUpdate.change-instructions": "A confirmation email will be sent to the entered email address with a unique link. Accessing that link will confirm your ownership of the email address and it will become active on your account. At any time, you are able to update your email on file from within your account page." "emailUpdate.change-instructions": "A confirmation email will be sent to the entered email address with a unique link. Accessing that link will confirm your ownership of the email address and it will become active on your account. At any time, you are able to update your email on file from within your account page.",
"emailUpdate.password-challenge": "Please enter your password in order to verify account ownership."
} }

@ -1,6 +1,7 @@
'use strict'; 'use strict';
const winston = require('winston'); const winston = require('winston');
const util = require('util');
const user = require('.'); const user = require('.');
const db = require('../database'); const db = require('../database');
@ -9,6 +10,8 @@ const privileges = require('../privileges');
const plugins = require('../plugins'); const plugins = require('../plugins');
const utils = require('../utils'); const utils = require('../utils');
const sleep = util.promisify(setTimeout);
const Interstitials = module.exports; const Interstitials = module.exports;
Interstitials.email = async (data) => { Interstitials.email = async (data) => {
@ -19,6 +22,7 @@ Interstitials.email = async (data) => {
return data; return data;
} }
const isAdminOrGlobalMod = await user.isAdminOrGlobalMod(data.req.uid);
let email; let email;
if (data.userData.uid) { if (data.userData.uid) {
email = await user.getUserField(data.userData.uid, 'email'); email = await user.getUserField(data.userData.uid, 'email');
@ -29,12 +33,13 @@ Interstitials.email = async (data) => {
data: { data: {
email, email,
requireEmailAddress: meta.config.requireEmailAddress, requireEmailAddress: meta.config.requireEmailAddress,
update: !!data.userData.uid,
}, },
callback: async (userData, formData) => { callback: async (userData, formData) => {
// Validate and send email confirmation // Validate and send email confirmation
if (userData.uid) { if (userData.uid) {
const [isAdminOrGlobalMod, canEdit, current, { allowed, error }] = await Promise.all([ const [isPasswordCorrect, canEdit, current, { allowed, error }] = await Promise.all([
user.isAdminOrGlobalMod(data.req.uid), user.isPasswordCorrect(userData.uid, formData.password, data.req.ip),
privileges.users.canEdit(data.req.uid, userData.uid), privileges.users.canEdit(data.req.uid, userData.uid),
user.getUserField(userData.uid, 'email'), user.getUserField(userData.uid, 'email'),
plugins.hooks.fire('filter:user.saveEmail', { plugins.hooks.fire('filter:user.saveEmail', {
@ -46,6 +51,10 @@ Interstitials.email = async (data) => {
}), }),
]); ]);
if (!isAdminOrGlobalMod && !isPasswordCorrect) {
await sleep(2000);
}
if (formData.email && formData.email.length) { if (formData.email && formData.email.length) {
if (!allowed || !utils.isEmailValid(formData.email)) { if (!allowed || !utils.isEmailValid(formData.email)) {
throw new Error(error); throw new Error(error);
@ -60,6 +69,10 @@ Interstitials.email = async (data) => {
await user.setUserField(userData.uid, 'email', formData.email); await user.setUserField(userData.uid, 'email', formData.email);
await user.email.confirmByUid(userData.uid); await user.email.confirmByUid(userData.uid);
} else if (canEdit) { } else if (canEdit) {
if (!isPasswordCorrect) {
throw new Error('[[error:invalid-password]]');
}
await user.email.sendValidationEmail(userData.uid, { await user.email.sendValidationEmail(userData.uid, {
email: formData.email, email: formData.email,
force: true, force: true,
@ -76,7 +89,7 @@ Interstitials.email = async (data) => {
throw new Error('[[error:invalid-email]]'); throw new Error('[[error:invalid-email]]');
} }
if (current) { if (current.length && (isPasswordCorrect || isAdminOrGlobalMod)) {
// User explicitly clearing their email // User explicitly clearing their email
await user.email.remove(userData.uid, data.req.session.id); await user.email.remove(userData.uid, data.req.session.id);
} }

@ -8,6 +8,14 @@
<div class="form-group"> <div class="form-group">
<label for="email">[[global:email]]</label> <label for="email">[[global:email]]</label>
<input class="form-control" type="text" id="email" name="email" placeholder="{email}" value="{email}" /> <input class="form-control" type="text" id="email" name="email" placeholder="{email}" value="{email}" />
<p class="help-block">[[user:emailUpdate.change-instructions]]</p>
</div> </div>
<p class="help-block">[[user:emailUpdate.change-instructions]]</p>
{{{ if update }}}
<div class="form-group">
<label for="password">[[register:password]]</label>
<input class="form-control" type="password" id="password" name="password" />
<p class="help-block">[[user:emailUpdate.password-challenge]]</p>
</div>
{{{ end }}}
</div> </div>
Loading…
Cancel
Save