FEATURE: hide_email_address_taken forces use of email in forgot password form (#15362)

* FEATURE: hide_email_address_taken forces use of email in forgot password form

This strengthens this site setting which is meant to be used to harden sites
that are experiencing abuse on forgot password routes.

Previously we would only deny letting people know if forgot password worked on not
New change also bans usage of username for forgot password when enabled
This commit is contained in:
Sam 2021-12-20 12:54:10 +11:00 committed by GitHub
parent 1cdb5b7e4a
commit b6c3e9aa03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 7 deletions

View File

@ -15,7 +15,15 @@ export default Controller.extend(ModalFunctionality, {
@discourseComputed("accountEmailOrUsername", "disabled") @discourseComputed("accountEmailOrUsername", "disabled")
submitDisabled(accountEmailOrUsername, disabled) { submitDisabled(accountEmailOrUsername, disabled) {
return isEmpty((accountEmailOrUsername || "").trim()) || disabled; if (disabled) {
return true;
}
if (this.siteSettings.hide_email_address_taken) {
return (accountEmailOrUsername || "").indexOf("@") === -1;
} else {
return isEmpty((accountEmailOrUsername || "").trim());
}
}, },
onShow() { onShow() {

View File

@ -3,8 +3,13 @@
{{#if offerHelp}} {{#if offerHelp}}
{{html-safe offerHelp}} {{html-safe offerHelp}}
{{else}} {{else}}
<label for="username-or-email">{{i18n "forgot_password.invite"}}</label> {{#if siteSettings.hide_email_address_taken}}
{{text-field value=accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}} <label for="username-or-email">{{i18n "forgot_password.invite_no_username"}}</label>
{{text-field value=accountEmailOrUsername placeholderKey="email" id="username-or-email" autocorrect="off" autocapitalize="off"}}
{{else}}
<label for="username-or-email">{{i18n "forgot_password.invite"}}</label>
{{text-field value=accountEmailOrUsername placeholderKey="login.email_placeholder" id="username-or-email" autocorrect="off" autocapitalize="off"}}
{{/if}}
{{/if}} {{/if}}
{{/d-modal-body}} {{/d-modal-body}}
<div class="modal-footer"> <div class="modal-footer">

View File

@ -434,7 +434,11 @@ class SessionController < ApplicationController
RateLimiter.new(nil, "forgot-password-hr-#{request.remote_ip}", 6, 1.hour).performed! RateLimiter.new(nil, "forgot-password-hr-#{request.remote_ip}", 6, 1.hour).performed!
RateLimiter.new(nil, "forgot-password-min-#{request.remote_ip}", 3, 1.minute).performed! RateLimiter.new(nil, "forgot-password-min-#{request.remote_ip}", 3, 1.minute).performed!
user = User.find_by_username_or_email(normalized_login_param) if SiteSetting.hide_email_address_taken
user = User.find_by_email(Email.downcase(normalized_login_param))
else
user = User.find_by_username_or_email(normalized_login_param)
end
if user if user
RateLimiter.new(nil, "forgot-password-login-day-#{user.username}", 6, 1.day).performed! RateLimiter.new(nil, "forgot-password-login-day-#{user.username}", 6, 1.day).performed!
@ -449,7 +453,8 @@ class SessionController < ApplicationController
end end
json = success_json json = success_json
unless SiteSetting.hide_email_address_taken
if !SiteSetting.hide_email_address_taken
json[:user_found] = user_presence json[:user_found] = user_presence
end end

View File

@ -1870,6 +1870,7 @@ en:
title: "Password Reset" title: "Password Reset"
action: "I forgot my password" action: "I forgot my password"
invite: "Enter your username or email address, and we'll send you a password reset email." invite: "Enter your username or email address, and we'll send you a password reset email."
invite_no_username: "Enter your email address, and we'll send you a password reset email."
reset: "Reset Password" reset: "Reset Password"
complete_username: "If an account matches the username <b>%{username}</b>, you should receive an email with instructions on how to reset your password shortly." complete_username: "If an account matches the username <b>%{username}</b>, you should receive an email with instructions on how to reset your password shortly."
complete_email: "If an account matches <b>%{email}</b>, you should receive an email with instructions on how to reset your password shortly." complete_email: "If an account matches <b>%{email}</b>, you should receive an email with instructions on how to reset your password shortly."

View File

@ -1669,7 +1669,7 @@ en:
allowed_email_domains: "A pipe-delimited list of email domains that users MUST register accounts with. WARNING: Users with email domains other than those listed will not be allowed!" allowed_email_domains: "A pipe-delimited list of email domains that users MUST register accounts with. WARNING: Users with email domains other than those listed will not be allowed!"
normalize_emails: "Check if normalized email is unique. Normalized email removes all dots from the username and everything between + and @ symbols." normalize_emails: "Check if normalized email is unique. Normalized email removes all dots from the username and everything between + and @ symbols."
auto_approve_email_domains: "Users with email addresses from this list of domains will be automatically approved." auto_approve_email_domains: "Users with email addresses from this list of domains will be automatically approved."
hide_email_address_taken: "Don't inform users that an account exists with a given email address during signup and from the forgot password form." hide_email_address_taken: "Don't inform users that an account exists with a given email address during signup or during forgot password flow. Require full email for 'forgotten password' requests."
log_out_strict: "When logging out, log out ALL sessions for the user on all devices" log_out_strict: "When logging out, log out ALL sessions for the user on all devices"
version_checks: "Ping the Discourse Hub for version updates and show new version messages on the <a href='%{base_path}/admin' target='_blank'>/admin</a> dashboard" version_checks: "Ping the Discourse Hub for version updates and show new version messages on the <a href='%{base_path}/admin' target='_blank'>/admin</a> dashboard"
new_version_emails: "Send an email to the contact_email address when a new version of Discourse is available." new_version_emails: "Send an email to the contact_email address when a new version of Discourse is available."

View File

@ -521,7 +521,9 @@ login:
default: "" default: ""
type: list type: list
list_type: simple list_type: simple
hide_email_address_taken: false hide_email_address_taken:
client: true
default: false
log_out_strict: false log_out_strict: false
pending_users_reminder_delay_minutes: pending_users_reminder_delay_minutes:
min: -1 min: -1

View File

@ -2058,6 +2058,29 @@ describe SessionController do
end end
describe '#forgot_password' do describe '#forgot_password' do
context 'when hide_email_address_taken is set' do
before do
SiteSetting.hide_email_address_taken = true
end
it 'denies for username' do
post "/session/forgot_password.json",
params: { login: user.username }
expect(response.status).to eq(200)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(0)
end
it 'allows for email' do
post "/session/forgot_password.json",
params: { login: user.email }
expect(response.status).to eq(200)
expect(Jobs::CriticalUserEmail.jobs.size).to eq(1)
end
end
it 'raises an error without a username parameter' do it 'raises an error without a username parameter' do
post "/session/forgot_password.json" post "/session/forgot_password.json"
expect(response.status).to eq(400) expect(response.status).to eq(400)