FIX: rate limit password reset email
This commit is contained in:
parent
582ec5954f
commit
e0a82d3088
|
@ -6,25 +6,40 @@ export default DiscourseController.extend(ModalFunctionality, {
|
||||||
|
|
||||||
// You need a value in the field to submit it.
|
// You need a value in the field to submit it.
|
||||||
submitDisabled: function() {
|
submitDisabled: function() {
|
||||||
return this.blank('accountEmailOrUsername');
|
return this.blank('accountEmailOrUsername') || this.get('disabled');
|
||||||
}.property('accountEmailOrUsername'),
|
}.property('accountEmailOrUsername', 'disabled'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
submit: function() {
|
submit: function() {
|
||||||
if (!this.get('accountEmailOrUsername')) return false;
|
var self = this;
|
||||||
|
|
||||||
Discourse.ajax("/session/forgot_password", {
|
if (this.get('submitDisabled')) return false;
|
||||||
|
|
||||||
|
this.set('disabled', true);
|
||||||
|
|
||||||
|
var success = function() {
|
||||||
|
// don't tell people what happened, this keeps it more secure (ensure same on server)
|
||||||
|
var escaped = Handlebars.Utils.escapeExpression(self.get('accountEmailOrUsername'));
|
||||||
|
if (self.get('accountEmailOrUsername').match(/@/)) {
|
||||||
|
self.flash(I18n.t('forgot_password.complete_email', {email: escaped}));
|
||||||
|
} else {
|
||||||
|
self.flash(I18n.t('forgot_password.complete_username', {username: escaped}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var fail = function(e) {
|
||||||
|
self.flash(e.responseJSON.errors[0], 'alert-error');
|
||||||
|
};
|
||||||
|
|
||||||
|
Discourse.ajax('/session/forgot_password', {
|
||||||
data: { login: this.get('accountEmailOrUsername') },
|
data: { login: this.get('accountEmailOrUsername') },
|
||||||
type: 'POST'
|
type: 'POST'
|
||||||
|
}).then(success, fail).finally(function(){
|
||||||
|
setTimeout(function(){
|
||||||
|
self.set('disabled',false);
|
||||||
|
}, 10*1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// don't tell people what happened, this keeps it more secure (ensure same on server)
|
|
||||||
var escaped = Handlebars.Utils.escapeExpression(this.get('accountEmailOrUsername'));
|
|
||||||
if (this.get('accountEmailOrUsername').match(/@/)) {
|
|
||||||
this.flash(I18n.t('forgot_password.complete_email', {email: escaped}));
|
|
||||||
} else {
|
|
||||||
this.flash(I18n.t('forgot_password.complete_username', {username: escaped}));
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require_dependency 'rate_limiter'
|
||||||
|
|
||||||
class SessionController < ApplicationController
|
class SessionController < ApplicationController
|
||||||
|
|
||||||
skip_before_filter :redirect_to_login_if_required
|
skip_before_filter :redirect_to_login_if_required
|
||||||
|
@ -93,6 +95,9 @@ class SessionController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
user = User.find_by_username_or_email(params[:login])
|
user = User.find_by_username_or_email(params[:login])
|
||||||
if user.present?
|
if user.present?
|
||||||
email_token = user.email_tokens.create(email: user.email)
|
email_token = user.email_tokens.create(email: user.email)
|
||||||
|
@ -100,6 +105,9 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
# always render of so we don't leak information
|
# always render of so we don't leak information
|
||||||
render json: {result: "ok"}
|
render json: {result: "ok"}
|
||||||
|
|
||||||
|
rescue RateLimiter::LimitExceeded
|
||||||
|
render_json_error(I18n.t("rate_limiter.slow_down"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def current
|
def current
|
||||||
|
|
|
@ -292,6 +292,7 @@ en:
|
||||||
|
|
||||||
|
|
||||||
rate_limiter:
|
rate_limiter:
|
||||||
|
slow_down: "You have performed this action too many times, try again later"
|
||||||
too_many_requests: "We have a daily limit on how many times that action can be taken. Please wait %{time_left} before trying again."
|
too_many_requests: "We have a daily limit on how many times that action can be taken. Please wait %{time_left} before trying again."
|
||||||
hours:
|
hours:
|
||||||
one: "1 hour"
|
one: "1 hour"
|
||||||
|
|
|
@ -19,7 +19,7 @@ class RateLimiter
|
||||||
|
|
||||||
def initialize(user, key, max, secs)
|
def initialize(user, key, max, secs)
|
||||||
@user = user
|
@user = user
|
||||||
@key = "l-rate-limit:#{@user.id}:#{key}"
|
@key = "l-rate-limit:#{@user && @user.id}:#{key}"
|
||||||
@max = max
|
@max = max
|
||||||
@secs = secs
|
@secs = secs
|
||||||
end
|
end
|
||||||
|
@ -71,6 +71,6 @@ class RateLimiter
|
||||||
end
|
end
|
||||||
|
|
||||||
def rate_unlimited?
|
def rate_unlimited?
|
||||||
!!(RateLimiter.disabled? || @user.staff?)
|
!!(RateLimiter.disabled? || (@user && @user.staff?))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -360,7 +360,7 @@ describe SessionController do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
it "returns a 500 if local logins are disabled" do
|
it "returns a 500 if local logins are disabled" do
|
||||||
SiteSetting.stubs(:enable_local_logins).returns(false)
|
SiteSetting.enable_local_logins = false
|
||||||
xhr :post, :forgot_password, login: user.username
|
xhr :post, :forgot_password, login: user.username
|
||||||
response.code.to_i.should == 500
|
response.code.to_i.should == 500
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue