FEATURE: login by a link from email
Co-authored-by: tgxworld <tgx@discourse.org>
This commit is contained in:
parent
f9280617d0
commit
03b3e57a44
|
@ -20,48 +20,54 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
submit() {
|
|
||||||
if (this.get('submitDisabled')) return false;
|
|
||||||
|
|
||||||
this.set('disabled', true);
|
|
||||||
|
|
||||||
ajax('/session/forgot_password', {
|
|
||||||
data: { login: this.get('accountEmailOrUsername').trim() },
|
|
||||||
type: 'POST'
|
|
||||||
}).then(data => {
|
|
||||||
const escaped = escapeExpression(this.get('accountEmailOrUsername'));
|
|
||||||
const isEmail = this.get('accountEmailOrUsername').match(/@/);
|
|
||||||
let key = 'forgot_password.complete_' + (isEmail ? 'email' : 'username');
|
|
||||||
let extraClass;
|
|
||||||
|
|
||||||
if (data.user_found === true) {
|
|
||||||
key += '_found';
|
|
||||||
this.set('accountEmailOrUsername', '');
|
|
||||||
this.set('offerHelp', I18n.t(key, {email: escaped, username: escaped}));
|
|
||||||
} else {
|
|
||||||
if (data.user_found === false) {
|
|
||||||
key += '_not_found';
|
|
||||||
extraClass = 'error';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.flash(I18n.t(key, {email: escaped, username: escaped}), extraClass);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
this.flash(extractError(e), 'error');
|
|
||||||
}).finally(() => {
|
|
||||||
setTimeout(() => this.set('disabled', false), 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
ok() {
|
ok() {
|
||||||
this.send('closeModal');
|
this.send('closeModal');
|
||||||
},
|
},
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
this.setProperties({ offerHelp: I18n.t('forgot_password.help'), helpSeen: true });
|
this.setProperties({ offerHelp: I18n.t('forgot_password.help'), helpSeen: true });
|
||||||
}
|
},
|
||||||
}
|
|
||||||
|
|
||||||
|
resetPassword() {
|
||||||
|
return this._submit('/session/forgot_password', 'forgot_password.complete');
|
||||||
|
},
|
||||||
|
|
||||||
|
emailLogin() {
|
||||||
|
return this._submit('/u/email-login', 'email_login.complete');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_submit(route, translationKey) {
|
||||||
|
if (this.get('submitDisabled')) return false;
|
||||||
|
this.set('disabled', true);
|
||||||
|
|
||||||
|
ajax(route, {
|
||||||
|
data: { login: this.get('accountEmailOrUsername').trim() },
|
||||||
|
type: 'POST'
|
||||||
|
}).then(data => {
|
||||||
|
const escaped = escapeExpression(this.get('accountEmailOrUsername'));
|
||||||
|
const isEmail = this.get('accountEmailOrUsername').match(/@/);
|
||||||
|
let key = `${translationKey}_${isEmail ? 'email' : 'username'}`;
|
||||||
|
let extraClass;
|
||||||
|
|
||||||
|
if (data.user_found === true) {
|
||||||
|
key += '_found';
|
||||||
|
this.set('accountEmailOrUsername', '');
|
||||||
|
this.set('offerHelp', I18n.t(key, { email: escaped, username: escaped }));
|
||||||
|
} else {
|
||||||
|
if (data.user_found === false) {
|
||||||
|
key += '_not_found';
|
||||||
|
extraClass = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flash(I18n.t(key, { email: escaped, username: escaped }), extraClass);
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
this.flash(extractError(e), 'error');
|
||||||
|
}).finally(() => {
|
||||||
|
this.set('disabled', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,10 +9,16 @@
|
||||||
{{/d-modal-body}}
|
{{/d-modal-body}}
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
{{#unless offerHelp}}
|
{{#unless offerHelp}}
|
||||||
{{d-button action="submit"
|
{{d-button action="resetPassword"
|
||||||
label="forgot_password.reset"
|
label="forgot_password.reset"
|
||||||
disabled=submitDisabled
|
disabled=submitDisabled
|
||||||
class="btn-primary"}}
|
class="btn-primary"}}
|
||||||
|
{{#if siteSettings.enable_local_logins_via_email}}
|
||||||
|
{{d-button action="emailLogin"
|
||||||
|
label="email_login.label"
|
||||||
|
disabled=submitDisabled
|
||||||
|
class="email-login"}}
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{d-button class="btn-large btn-primary"
|
{{d-button class="btn-large btn-primary"
|
||||||
label="forgot_password.button_ok"
|
label="forgot_password.button_ok"
|
||||||
|
|
|
@ -7,9 +7,10 @@ class SessionController < ApplicationController
|
||||||
render body: nil, status: 500
|
render body: nil, status: 500
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action :check_local_login_allowed, only: %i(create forgot_password)
|
before_action :check_local_login_allowed, only: %i(create forgot_password email_login)
|
||||||
|
before_action :rate_limit_login, only: %i(create email_login)
|
||||||
skip_before_action :redirect_to_login_if_required
|
skip_before_action :redirect_to_login_if_required
|
||||||
skip_before_action :preload_json, :check_xhr, only: ['sso', 'sso_login', 'become', 'sso_provider', 'destroy']
|
skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login become sso_provider destroy email_login)
|
||||||
|
|
||||||
ACTIVATE_USER_KEY = "activate_user"
|
ACTIVATE_USER_KEY = "activate_user"
|
||||||
|
|
||||||
|
@ -187,9 +188,6 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
RateLimiter.new(nil, "login-hr-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_hour, 1.hour).performed!
|
|
||||||
RateLimiter.new(nil, "login-min-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_minute, 1.minute).performed!
|
|
||||||
|
|
||||||
params.require(:login)
|
params.require(:login)
|
||||||
params.require(:password)
|
params.require(:password)
|
||||||
|
|
||||||
|
@ -208,7 +206,7 @@ class SessionController < ApplicationController
|
||||||
|
|
||||||
# If the site requires user approval and the user is not approved yet
|
# If the site requires user approval and the user is not approved yet
|
||||||
if login_not_approved_for?(user)
|
if login_not_approved_for?(user)
|
||||||
login_not_approved
|
render json: login_not_approved
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -220,20 +218,31 @@ class SessionController < ApplicationController
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if user.suspended?
|
if payload = login_error_check(user)
|
||||||
failed_to_login(user)
|
render json: payload
|
||||||
return
|
else
|
||||||
|
(user.active && user.email_confirmed?) ? login(user) : not_activated(user)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if ScreenedIpAddress.should_block?(request.remote_ip)
|
def email_login
|
||||||
return not_allowed_from_ip_address(user)
|
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
|
||||||
|
|
||||||
|
if EmailToken.valid_token_format?(params[:token]) && (user = EmailToken.confirm(params[:token]))
|
||||||
|
if login_not_approved_for?(user)
|
||||||
|
@error = login_not_approved[:error]
|
||||||
|
return render layout: 'no_ember'
|
||||||
|
elsif payload = login_error_check(user)
|
||||||
|
@error = payload[:error]
|
||||||
|
return render layout: 'no_ember'
|
||||||
|
else
|
||||||
|
log_on_user(user)
|
||||||
|
redirect_to path("/")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@error = I18n.t('email_login.invalid_token')
|
||||||
|
return render layout: 'no_ember'
|
||||||
end
|
end
|
||||||
|
|
||||||
if ScreenedIpAddress.block_admin_login?(user, request.remote_ip)
|
|
||||||
return admin_not_allowed_from_ip_address(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
(user.active && user.email_confirmed?) ? login(user) : not_activated(user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def forgot_password
|
def forgot_password
|
||||||
|
@ -291,6 +300,18 @@ class SessionController < ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def login_error_check(user)
|
||||||
|
return failed_to_login(user) if user.suspended?
|
||||||
|
|
||||||
|
if ScreenedIpAddress.should_block?(request.remote_ip)
|
||||||
|
return not_allowed_from_ip_address(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ScreenedIpAddress.block_admin_login?(user, request.remote_ip)
|
||||||
|
return admin_not_allowed_from_ip_address(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def login_not_approved_for?(user)
|
def login_not_approved_for?(user)
|
||||||
SiteSetting.must_approve_users? && !user.approved? && !user.admin?
|
SiteSetting.must_approve_users? && !user.approved? && !user.admin?
|
||||||
end
|
end
|
||||||
|
@ -300,7 +321,7 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def login_not_approved
|
def login_not_approved
|
||||||
render json: { error: I18n.t("login.not_approved") }
|
{ error: I18n.t("login.not_approved") }
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_activated(user)
|
def not_activated(user)
|
||||||
|
@ -314,19 +335,21 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_allowed_from_ip_address(user)
|
def not_allowed_from_ip_address(user)
|
||||||
render json: { error: I18n.t("login.not_allowed_from_ip_address", username: user.username) }
|
{ error: I18n.t("login.not_allowed_from_ip_address", username: user.username) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_not_allowed_from_ip_address(user)
|
def admin_not_allowed_from_ip_address(user)
|
||||||
render json: { error: I18n.t("login.admin_not_allowed_from_ip_address", username: user.username) }
|
{ error: I18n.t("login.admin_not_allowed_from_ip_address", username: user.username) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def failed_to_login(user)
|
def failed_to_login(user)
|
||||||
message = user.suspend_reason ? "login.suspended_with_reason" : "login.suspended"
|
message = user.suspend_reason ? "login.suspended_with_reason" : "login.suspended"
|
||||||
|
|
||||||
render json: {
|
{
|
||||||
error: I18n.t(message, date: I18n.l(user.suspended_till, format: :date_only),
|
error: I18n.t(message,
|
||||||
reason: Rack::Utils.escape_html(user.suspend_reason)),
|
date: I18n.l(user.suspended_till, format: :date_only),
|
||||||
|
reason: Rack::Utils.escape_html(user.suspend_reason)
|
||||||
|
),
|
||||||
reason: 'suspended'
|
reason: 'suspended'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -342,6 +365,22 @@ class SessionController < ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rate_limit_login
|
||||||
|
RateLimiter.new(
|
||||||
|
nil,
|
||||||
|
"login-hr-#{request.remote_ip}",
|
||||||
|
SiteSetting.max_logins_per_ip_per_hour,
|
||||||
|
1.hour
|
||||||
|
).performed!
|
||||||
|
|
||||||
|
RateLimiter.new(
|
||||||
|
nil,
|
||||||
|
"login-min-#{request.remote_ip}",
|
||||||
|
SiteSetting.max_logins_per_ip_per_minute,
|
||||||
|
1.minute
|
||||||
|
).performed!
|
||||||
|
end
|
||||||
|
|
||||||
def render_sso_error(status:, text:)
|
def render_sso_error(status:, text:)
|
||||||
@sso_error = text
|
@sso_error = text
|
||||||
render status: status, layout: 'no_ember'
|
render status: status, layout: 'no_ember'
|
||||||
|
|
|
@ -18,7 +18,7 @@ class UsersController < ApplicationController
|
||||||
skip_before_action :check_xhr, only: [
|
skip_before_action :check_xhr, only: [
|
||||||
:show, :badges, :password_reset, :update, :account_created,
|
:show, :badges, :password_reset, :update, :account_created,
|
||||||
:activate_account, :perform_account_activation, :user_preferences_redirect, :avatar,
|
:activate_account, :perform_account_activation, :user_preferences_redirect, :avatar,
|
||||||
:my_redirect, :toggle_anon, :admin_login, :confirm_admin
|
:my_redirect, :toggle_anon, :admin_login, :confirm_admin, :email_login
|
||||||
]
|
]
|
||||||
|
|
||||||
before_action :respond_to_suspicious_request, only: [:create]
|
before_action :respond_to_suspicious_request, only: [:create]
|
||||||
|
@ -37,6 +37,7 @@ class UsersController < ApplicationController
|
||||||
:update_activation_email,
|
:update_activation_email,
|
||||||
:password_reset,
|
:password_reset,
|
||||||
:confirm_email_token,
|
:confirm_email_token,
|
||||||
|
:email_login,
|
||||||
:admin_login,
|
:admin_login,
|
||||||
:confirm_admin]
|
:confirm_admin]
|
||||||
|
|
||||||
|
@ -563,6 +564,7 @@ class UsersController < ApplicationController
|
||||||
elsif params[:token].present?
|
elsif params[:token].present?
|
||||||
if EmailToken.valid_token_format?(params[:token])
|
if EmailToken.valid_token_format?(params[:token])
|
||||||
@user = EmailToken.confirm(params[:token])
|
@user = EmailToken.confirm(params[:token])
|
||||||
|
|
||||||
if @user&.admin?
|
if @user&.admin?
|
||||||
log_on_user(@user)
|
log_on_user(@user)
|
||||||
return redirect_to path("/")
|
return redirect_to path("/")
|
||||||
|
@ -580,6 +582,40 @@ class UsersController < ApplicationController
|
||||||
render layout: false
|
render layout: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_login
|
||||||
|
raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
|
||||||
|
return redirect_to path("/") if current_user
|
||||||
|
|
||||||
|
expires_now
|
||||||
|
params.require(:login)
|
||||||
|
|
||||||
|
RateLimiter.new(nil, "email-login-hour-#{request.remote_ip}", 6, 1.hour).performed!
|
||||||
|
RateLimiter.new(nil, "email-login-min-#{request.remote_ip}", 3, 1.minute).performed!
|
||||||
|
user = User.human_users.find_by_username_or_email(params[:login])
|
||||||
|
user_presence = user.present? && !user.staged
|
||||||
|
|
||||||
|
if user
|
||||||
|
RateLimiter.new(nil, "email-login-hour-#{user.id}", 6, 1.hour).performed!
|
||||||
|
RateLimiter.new(nil, "email-login-min-#{user.id}", 3, 1.minute).performed!
|
||||||
|
|
||||||
|
if user_presence
|
||||||
|
email_token = user.email_tokens.create!(email: user.email)
|
||||||
|
|
||||||
|
Jobs.enqueue(:critical_user_email,
|
||||||
|
type: :email_login,
|
||||||
|
user_id: user.id,
|
||||||
|
email_token: email_token.token
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
json = { result: "ok" }
|
||||||
|
json[:user_found] = user_presence unless SiteSetting.hide_email_address_taken
|
||||||
|
render json: json
|
||||||
|
rescue RateLimiter::LimitExceeded
|
||||||
|
render_json_error(I18n.t("rate_limiter.slow_down"))
|
||||||
|
end
|
||||||
|
|
||||||
def toggle_anon
|
def toggle_anon
|
||||||
user = AnonymousShadowCreator.get_master(current_user) ||
|
user = AnonymousShadowCreator.get_master(current_user) ||
|
||||||
AnonymousShadowCreator.get(current_user)
|
AnonymousShadowCreator.get(current_user)
|
||||||
|
|
|
@ -12,10 +12,11 @@ class UserNotifications < ActionMailer::Base
|
||||||
include Email::BuildEmailHelper
|
include Email::BuildEmailHelper
|
||||||
|
|
||||||
def signup(user, opts = {})
|
def signup(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: "user_notifications.signup",
|
"user_notifications.signup",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def signup_after_approval(user, opts = {})
|
def signup_after_approval(user, opts = {})
|
||||||
|
@ -33,38 +34,51 @@ class UserNotifications < ActionMailer::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_old_email(user, opts = {})
|
def confirm_old_email(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: "user_notifications.confirm_old_email",
|
"user_notifications.confirm_old_email",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_new_email(user, opts = {})
|
def confirm_new_email(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: "user_notifications.confirm_new_email",
|
"user_notifications.confirm_new_email",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def forgot_password(user, opts = {})
|
def forgot_password(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password",
|
user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_login(user, opts = {})
|
||||||
|
build_user_email_token_by_template(
|
||||||
|
"user_notifications.email_login",
|
||||||
|
user,
|
||||||
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_login(user, opts = {})
|
def admin_login(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: "user_notifications.admin_login",
|
"user_notifications.admin_login",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_created(user, opts = {})
|
def account_created(user, opts = {})
|
||||||
build_email(user.email,
|
build_user_email_token_by_template(
|
||||||
template: "user_notifications.account_created",
|
"user_notifications.account_created",
|
||||||
locale: user_locale(user),
|
user,
|
||||||
email_token: opts[:email_token])
|
opts[:email_token]
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_silenced(user, opts = nil)
|
def account_silenced(user, opts = nil)
|
||||||
|
@ -532,6 +546,15 @@ class UserNotifications < ActionMailer::Base
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def build_user_email_token_by_template(template, user, email_token)
|
||||||
|
build_email(
|
||||||
|
user.email,
|
||||||
|
template: template,
|
||||||
|
locale: user_locale(user),
|
||||||
|
email_token: email_token
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def build_summary_for(user)
|
def build_summary_for(user)
|
||||||
@site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n
|
@site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n
|
||||||
@user = user
|
@user = user
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<%if @error%>
|
||||||
|
<div class='alert alert-error'>
|
||||||
|
<%= @error %>
|
||||||
|
</div>
|
||||||
|
<%end%>
|
||||||
|
|
||||||
|
<% content_for :title do %><%=t "email_login.title" %><% end %>
|
||||||
|
|
||||||
|
<%- content_for(:no_ember_head) do %>
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<%= preload_script "ember_jquery" %>
|
||||||
|
<%= render_google_universal_analytics_code %>
|
||||||
|
<%- end %>
|
||||||
|
|
||||||
|
<%- content_for(:head) do %>
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<%- end %>
|
|
@ -1083,6 +1083,16 @@ en:
|
||||||
help: "Email not arriving? Be sure to check your spam folder first.<p>Not sure which email address you used? Enter an email address and we’ll let you know if it exists here.</p><p>If you no longer have access to the email address on your account, please contact <a href='/about'>our helpful staff.</a></p>"
|
help: "Email not arriving? Be sure to check your spam folder first.<p>Not sure which email address you used? Enter an email address and we’ll let you know if it exists here.</p><p>If you no longer have access to the email address on your account, please contact <a href='/about'>our helpful staff.</a></p>"
|
||||||
button_ok: "OK"
|
button_ok: "OK"
|
||||||
button_help: "Help"
|
button_help: "Help"
|
||||||
|
|
||||||
|
email_login:
|
||||||
|
label: "Login With Email"
|
||||||
|
complete_username: "If an account matches the username <b>%{username}</b>, you should receive an email with a magic login link shortly."
|
||||||
|
complete_email: "If an account matches <b>%{email}</b>, you should receive an email with a magic login link shortly."
|
||||||
|
complete_username_found: "We found an account that matches the username <b>%{username}</b>, you should receive an email with a magic login link shortly."
|
||||||
|
complete_email_found: "We found an account that matches <b>%{email}</b>, you should receive an email with a magic login link shortly."
|
||||||
|
complete_username_not_found: "No account matches the username <b>%{username}</b>"
|
||||||
|
complete_email_not_found: "No account matches <b>%{email}</b>"
|
||||||
|
|
||||||
login:
|
login:
|
||||||
title: "Log In"
|
title: "Log In"
|
||||||
username: "User"
|
username: "User"
|
||||||
|
|
|
@ -648,6 +648,10 @@ en:
|
||||||
success: "You successfully changed your password and are now logged in."
|
success: "You successfully changed your password and are now logged in."
|
||||||
success_unapproved: "You successfully changed your password."
|
success_unapproved: "You successfully changed your password."
|
||||||
|
|
||||||
|
email_login:
|
||||||
|
invalid_token: "Sorry, that email login link is too old. Select the Log In button and use 'I forgot my password' to get a new link."
|
||||||
|
title: "Email login"
|
||||||
|
|
||||||
change_email:
|
change_email:
|
||||||
confirmed: "Your email has been updated."
|
confirmed: "Your email has been updated."
|
||||||
please_continue: "Continue to %{site_name}"
|
please_continue: "Continue to %{site_name}"
|
||||||
|
@ -1149,6 +1153,7 @@ en:
|
||||||
sso_allows_all_return_paths: "Do not restrict the domain for return_paths provided by SSO (by default return path must be on current site)"
|
sso_allows_all_return_paths: "Do not restrict the domain for return_paths provided by SSO (by default return path must be on current site)"
|
||||||
|
|
||||||
enable_local_logins: "Enable local username and password login based accounts. (Note: this must be enabled for invites to work)"
|
enable_local_logins: "Enable local username and password login based accounts. (Note: this must be enabled for invites to work)"
|
||||||
|
enable_local_logins_via_email: "Email user logins via email."
|
||||||
allow_new_registrations: "Allow new user registrations. Uncheck this to prevent anyone from creating a new account."
|
allow_new_registrations: "Allow new user registrations. Uncheck this to prevent anyone from creating a new account."
|
||||||
enable_signup_cta: "Show a notice to returning anonymous users prompting them to sign up for an account."
|
enable_signup_cta: "Show a notice to returning anonymous users prompting them to sign up for an account."
|
||||||
enable_yahoo_logins: "Enable Yahoo authentication"
|
enable_yahoo_logins: "Enable Yahoo authentication"
|
||||||
|
@ -1640,6 +1645,7 @@ en:
|
||||||
staged_users_disabled: "You must first enable 'staged users' before enabling this setting."
|
staged_users_disabled: "You must first enable 'staged users' before enabling this setting."
|
||||||
reply_by_email_disabled: "You must first enable 'reply by email' before enabling this setting."
|
reply_by_email_disabled: "You must first enable 'reply by email' before enabling this setting."
|
||||||
sso_url_is_empty: "You must set a 'sso url' before enabling this setting."
|
sso_url_is_empty: "You must set a 'sso url' before enabling this setting."
|
||||||
|
enable_local_logins_disabled: "You must first enable 'enable local logins' before enabling this setting."
|
||||||
|
|
||||||
search:
|
search:
|
||||||
within_post: "#%{post_number} by %{username}"
|
within_post: "#%{post_number} by %{username}"
|
||||||
|
@ -2772,6 +2778,17 @@ en:
|
||||||
Click the following link to choose a new password:
|
Click the following link to choose a new password:
|
||||||
%{base_url}/u/password-reset/%{email_token}
|
%{base_url}/u/password-reset/%{email_token}
|
||||||
|
|
||||||
|
email_login:
|
||||||
|
title: "Email login link"
|
||||||
|
subject_template: "[%{email_prefix}] Email login link"
|
||||||
|
text_body_template: |
|
||||||
|
Somebody asked to login your account on [%{site_name}](%{base_url}).
|
||||||
|
|
||||||
|
If it was not you, you can safely ignore this email.
|
||||||
|
|
||||||
|
Click the following link to login:
|
||||||
|
%{base_url}/session/email-login/%{email_token}
|
||||||
|
|
||||||
set_password:
|
set_password:
|
||||||
title: "Set Password"
|
title: "Set Password"
|
||||||
subject_template: "[%{email_prefix}] Set Password"
|
subject_template: "[%{email_prefix}] Set Password"
|
||||||
|
|
|
@ -301,6 +301,7 @@ Discourse::Application.routes.draw do
|
||||||
get "session/sso_provider" => "session#sso_provider"
|
get "session/sso_provider" => "session#sso_provider"
|
||||||
get "session/current" => "session#current"
|
get "session/current" => "session#current"
|
||||||
get "session/csrf" => "session#csrf"
|
get "session/csrf" => "session#csrf"
|
||||||
|
get "session/email-login/:token" => "session#email_login"
|
||||||
get "composer_messages" => "composer_messages#index"
|
get "composer_messages" => "composer_messages#index"
|
||||||
post "composer/parse_html" => "composer#parse_html"
|
post "composer/parse_html" => "composer#parse_html"
|
||||||
|
|
||||||
|
@ -330,6 +331,7 @@ Discourse::Application.routes.draw do
|
||||||
|
|
||||||
put "#{root_path}/update-activation-email" => "users#update_activation_email"
|
put "#{root_path}/update-activation-email" => "users#update_activation_email"
|
||||||
get "#{root_path}/hp" => "users#get_honeypot_value"
|
get "#{root_path}/hp" => "users#get_honeypot_value"
|
||||||
|
post "#{root_path}/email-login" => "users#email_login"
|
||||||
get "#{root_path}/admin-login" => "users#admin_login"
|
get "#{root_path}/admin-login" => "users#admin_login"
|
||||||
put "#{root_path}/admin-login" => "users#admin_login"
|
put "#{root_path}/admin-login" => "users#admin_login"
|
||||||
get "#{root_path}/admin-login/:token" => "users#admin_login"
|
get "#{root_path}/admin-login/:token" => "users#admin_login"
|
||||||
|
|
|
@ -240,6 +240,10 @@ login:
|
||||||
enable_local_logins:
|
enable_local_logins:
|
||||||
client: true
|
client: true
|
||||||
default: true
|
default: true
|
||||||
|
enable_local_logins_via_email:
|
||||||
|
client: true
|
||||||
|
default: false
|
||||||
|
validator: "EnableLocalLoginsViaEmailValidator"
|
||||||
allow_new_registrations:
|
allow_new_registrations:
|
||||||
client: true
|
client: true
|
||||||
default: true
|
default: true
|
||||||
|
@ -331,7 +335,6 @@ login:
|
||||||
default: 1440
|
default: 1440
|
||||||
min: 1
|
min: 1
|
||||||
max: 175200
|
max: 175200
|
||||||
|
|
||||||
users:
|
users:
|
||||||
min_username_length:
|
min_username_length:
|
||||||
client: true
|
client: true
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
class EnableLocalLoginsViaEmailValidator
|
||||||
|
def initialize(opts = {})
|
||||||
|
@opts = opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_value?(val)
|
||||||
|
return true if val == 'f'
|
||||||
|
SiteSetting.enable_local_logins
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_message
|
||||||
|
I18n.t('site_settings.errors.enable_local_logins_disabled')
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,47 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe EnableLocalLoginsViaEmailValidator do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
describe '#valid_value?' do
|
||||||
|
describe "when 'enable_local_logins' is false" do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_local_logins = false
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when val is false' do
|
||||||
|
it 'should be valid' do
|
||||||
|
expect(subject.valid_value?('f')).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when value is true' do
|
||||||
|
it 'should not be valid' do
|
||||||
|
expect(subject.valid_value?('t')).to eq(false)
|
||||||
|
|
||||||
|
expect(subject.error_message).to eq(I18n.t(
|
||||||
|
'site_settings.errors.enable_local_logins_disabled'
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when 'enable_local_logins' is true" do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_local_logins = true
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when val is false' do
|
||||||
|
it 'should be valid' do
|
||||||
|
expect(subject.valid_value?('f')).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when value is true' do
|
||||||
|
it 'should be valid' do
|
||||||
|
expect(subject.valid_value?('t')).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,7 +8,7 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'become' do
|
describe '#become' do
|
||||||
let!(:user) { Fabricate(:user) }
|
let!(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
it "does not work when not in development mode" do
|
it "does not work when not in development mode" do
|
||||||
|
@ -26,7 +26,7 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.sso_login' do
|
describe '#sso_login' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@sso_url = "http://somesite.com/discourse_sso"
|
@sso_url = "http://somesite.com/discourse_sso"
|
||||||
|
@ -410,7 +410,7 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.sso_provider' do
|
describe '#sso_provider' do
|
||||||
before do
|
before do
|
||||||
SiteSetting.enable_sso_provider = true
|
SiteSetting.enable_sso_provider = true
|
||||||
SiteSetting.enable_sso = false
|
SiteSetting.enable_sso = false
|
||||||
|
@ -470,7 +470,7 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.create' do
|
describe '#create' do
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
@ -515,7 +515,9 @@ describe SessionController do
|
||||||
login: user.username, password: 'sssss'
|
login: user.username, password: 'sssss'
|
||||||
}, format: :json
|
}, format: :json
|
||||||
|
|
||||||
expect(::JSON.parse(response.body)['error']).to be_present
|
expect(::JSON.parse(response.body)['error']).to eq(
|
||||||
|
I18n.t("login.incorrect_username_email_or_password")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -526,7 +528,9 @@ describe SessionController do
|
||||||
login: user.username, password: ('s' * (User.max_password_length + 1))
|
login: user.username, password: ('s' * (User.max_password_length + 1))
|
||||||
}, format: :json
|
}, format: :json
|
||||||
|
|
||||||
expect(::JSON.parse(response.body)['error']).to be_present
|
expect(::JSON.parse(response.body)['error']).to eq(
|
||||||
|
I18n.t("login.incorrect_username_email_or_password")
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -536,14 +540,15 @@ describe SessionController do
|
||||||
user.suspended_at = Time.now
|
user.suspended_at = Time.now
|
||||||
user.save!
|
user.save!
|
||||||
StaffActionLogger.new(user).log_user_suspend(user, "<strike>banned</strike>")
|
StaffActionLogger.new(user).log_user_suspend(user, "<strike>banned</strike>")
|
||||||
|
|
||||||
post :create, params: {
|
post :create, params: {
|
||||||
login: user.username, password: 'myawesomepassword'
|
login: user.username, password: 'myawesomepassword'
|
||||||
}, format: :json
|
}, format: :json
|
||||||
|
|
||||||
error = ::JSON.parse(response.body)['error']
|
expect(JSON.parse(response.body)['error']).to eq(I18n.t('login.suspended_with_reason',
|
||||||
expect(error).to be_present
|
date: I18n.l(user.suspended_till, format: :date_only),
|
||||||
expect(error).to match(/banned/)
|
reason: Rack::Utils.escape_html(user.suspend_reason)
|
||||||
expect(error).not_to match(/<strike>/)
|
))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -881,7 +886,7 @@ describe SessionController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.current' do
|
describe '#current' do
|
||||||
context "when not logged in" do
|
context "when not logged in" do
|
||||||
it "retuns 404" do
|
it "retuns 404" do
|
||||||
get :current, format: :json
|
get :current, format: :json
|
||||||
|
|
|
@ -79,6 +79,28 @@ describe UserNotifications do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.email_login' do
|
||||||
|
let(:email_token) { user.email_tokens.create!(email: user.email).token }
|
||||||
|
subject { UserNotifications.email_login(user, email_token: email_token) }
|
||||||
|
|
||||||
|
it "generates the right email" do
|
||||||
|
expect(subject.to).to eq([user.email])
|
||||||
|
expect(subject.from).to eq([SiteSetting.notification_email])
|
||||||
|
|
||||||
|
expect(subject.subject).to eq(I18n.t(
|
||||||
|
'user_notifications.email_login.subject_template',
|
||||||
|
email_prefix: SiteSetting.title
|
||||||
|
))
|
||||||
|
|
||||||
|
expect(subject.body.to_s).to match(I18n.t(
|
||||||
|
'user_notifications.email_login.text_body_template',
|
||||||
|
site_name: SiteSetting.title,
|
||||||
|
base_url: Discourse.base_url,
|
||||||
|
email_token: email_token
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.digest' do
|
describe '.digest' do
|
||||||
|
|
||||||
subject { UserNotifications.digest(user) }
|
subject { UserNotifications.digest(user) }
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SessionController do
|
||||||
|
let(:email_token) { Fabricate(:email_token) }
|
||||||
|
let(:user) { email_token.user }
|
||||||
|
|
||||||
|
describe '#email_login' do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_local_logins_via_email = true
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'missing token' do
|
||||||
|
it 'returns the right response' do
|
||||||
|
get "/session/email-login"
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'invalid token' do
|
||||||
|
it 'returns the right response' do
|
||||||
|
get "/session/email-login/adasdad"
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to match(
|
||||||
|
I18n.t('email_login.invalid_token')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when token has expired' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
email_token.update!(created_at: 999.years.ago)
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to match(
|
||||||
|
I18n.t('email_login.invalid_token')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'valid token' do
|
||||||
|
it 'returns success' do
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response).to redirect_to("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when local logins via email is disabled' do
|
||||||
|
SiteSetting.enable_local_logins_via_email = false
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when local logins is disabled' do
|
||||||
|
SiteSetting.enable_local_logins = false
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(500)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't log in the user when not approved" do
|
||||||
|
SiteSetting.must_approve_users = true
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
|
I18n.t("login.not_approved")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when admin IP address is not valid" do
|
||||||
|
before do
|
||||||
|
Fabricate(:screened_ip_address,
|
||||||
|
ip_address: "111.111.11.11",
|
||||||
|
action_type: ScreenedIpAddress.actions[:allow_admin]
|
||||||
|
)
|
||||||
|
|
||||||
|
SiteSetting.use_admin_ip_whitelist = true
|
||||||
|
user.update!(admin: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the right response' do
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
|
I18n.t("login.admin_not_allowed_from_ip_address", username: user.username)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when IP address is blocked" do
|
||||||
|
let(:permitted_ip_address) { '111.234.23.11' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Fabricate(:screened_ip_address,
|
||||||
|
ip_address: permitted_ip_address,
|
||||||
|
action_type: ScreenedIpAddress.actions[:block]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the right response' do
|
||||||
|
ActionDispatch::Request.any_instance.stubs(:remote_ip).returns(permitted_ip_address)
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to include(
|
||||||
|
I18n.t("login.not_allowed_from_ip_address", username: user.username)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "fails when user is suspended" do
|
||||||
|
user.update!(
|
||||||
|
suspended_till: 2.days.from_now,
|
||||||
|
suspended_at: Time.zone.now
|
||||||
|
)
|
||||||
|
|
||||||
|
get "/session/email-login/#{email_token.token}"
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
expect(CGI.unescapeHTML(response.body)).to include(I18n.t("login.suspended",
|
||||||
|
date: I18n.l(user.suspended_till, format: :date_only)
|
||||||
|
))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -340,4 +340,65 @@ RSpec.describe UsersController do
|
||||||
expect(response).to redirect_to("/u/#{user.username_lower}/preferences")
|
expect(response).to redirect_to("/u/#{user.username_lower}/preferences")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#email_login' do
|
||||||
|
before do
|
||||||
|
SiteSetting.queue_jobs = true
|
||||||
|
SiteSetting.enable_local_logins_via_email = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "enqueues the right email" do
|
||||||
|
post "/u/email-login.json", params: { login: user.email }
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body)['user_found']).to eq(true)
|
||||||
|
|
||||||
|
job_args = Jobs::CriticalUserEmail.jobs.last["args"].first
|
||||||
|
|
||||||
|
expect(job_args["user_id"]).to eq(user.id)
|
||||||
|
expect(job_args["type"]).to eq("email_login")
|
||||||
|
expect(job_args["email_token"]).to eq(user.email_tokens.last.token)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when enable_local_logins_via_email is disabled' do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_local_logins_via_email = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return the right response' do
|
||||||
|
post "/u/email-login.json", params: { login: user.email }
|
||||||
|
|
||||||
|
expect(response.status).to eq(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when username or email is not valid' do
|
||||||
|
it 'should not enqueue the email to login' do
|
||||||
|
post "/u/email-login.json", params: { login: '@random' }
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body)['user_found']).to eq(false)
|
||||||
|
expect(Jobs::CriticalUserEmail.jobs).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when hide_email_address_taken is true' do
|
||||||
|
it 'should return the right response' do
|
||||||
|
SiteSetting.hide_email_address_taken = true
|
||||||
|
post "/u/email-login.json", params: { login: user.email }
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
expect(JSON.parse(response.body).has_key?('user_found')).to eq(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when user is already logged in" do
|
||||||
|
it 'should redirect to the root path' do
|
||||||
|
sign_in(user)
|
||||||
|
post "/u/email-login.json", params: { login: user.email }
|
||||||
|
|
||||||
|
expect(response).to redirect_to("/")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
|
let userFound = false;
|
||||||
|
|
||||||
|
acceptance("Forgot password", {
|
||||||
|
settings: {
|
||||||
|
enable_local_logins_via_email: true
|
||||||
|
},
|
||||||
|
beforeEach() {
|
||||||
|
const response = object => {
|
||||||
|
return [
|
||||||
|
200,
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
object
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
server.post('/u/email-login', () => { // eslint-disable-line no-undef
|
||||||
|
return response({ "user_found": userFound });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("logging in via email", assert => {
|
||||||
|
visit("/");
|
||||||
|
click("header .login-button");
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.ok(exists('.login-modal'), "it shows the login modal");
|
||||||
|
});
|
||||||
|
|
||||||
|
click('#forgot-password-link');
|
||||||
|
|
||||||
|
fillIn("#username-or-email", 'someuser');
|
||||||
|
click('.email-login');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find(".alert-error").html(),
|
||||||
|
I18n.t('email_login.complete_username_not_found', { username: 'someuser' }),
|
||||||
|
'it should display the right error message'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
fillIn("#username-or-email", 'someuser@gmail.com');
|
||||||
|
click('.email-login');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find(".alert-error").html(),
|
||||||
|
I18n.t('email_login.complete_email_not_found', { email: 'someuser@gmail.com' }),
|
||||||
|
'it should display the right error message'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
fillIn("#username-or-email", 'someuser');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
userFound = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
click('.email-login');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find(".modal-body").html().trim(),
|
||||||
|
I18n.t('email_login.complete_username_found', { username: 'someuser' }),
|
||||||
|
'it should display the right message'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
visit("/");
|
||||||
|
click("header .login-button");
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.ok(exists('.login-modal'), "it shows the login modal");
|
||||||
|
});
|
||||||
|
|
||||||
|
click('#forgot-password-link');
|
||||||
|
fillIn("#username-or-email", 'someuser@gmail.com');
|
||||||
|
click('.email-login');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
find(".modal-body").html().trim(),
|
||||||
|
I18n.t('email_login.complete_email_found', { email: 'someuser@gmail.com' }),
|
||||||
|
'it should display the right message'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue