diff --git a/app/assets/javascripts/discourse/templates/modal/not_activated.js.handlebars b/app/assets/javascripts/discourse/templates/modal/not_activated.js.handlebars new file mode 100644 index 00000000000..8df7065fa93 --- /dev/null +++ b/app/assets/javascripts/discourse/templates/modal/not_activated.js.handlebars @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/views/modal/login_view.js b/app/assets/javascripts/discourse/views/modal/login_view.js index c655853dcdd..d55d8f90223 100644 --- a/app/assets/javascripts/discourse/views/modal/login_view.js +++ b/app/assets/javascripts/discourse/views/modal/login_view.js @@ -45,7 +45,10 @@ }).success(function(result) { if (result.error) { _this.set('loggingIn', false); - return _this.flash(result.error, 'error'); + if( result.reason == 'not_activated' ) { + return _this.showView(Discourse.NotActivatedView.create({username: _this.get('loginName'), sentTo: result.sent_to_email, currentEmail: result.current_email})); + } + _this.flash(result.error, 'error'); } else { return window.location.reload(); } diff --git a/app/assets/javascripts/discourse/views/modal/modal_body_view.js b/app/assets/javascripts/discourse/views/modal/modal_body_view.js index 86d9ff6f3ac..f0ae1a6fb7e 100644 --- a/app/assets/javascripts/discourse/views/modal/modal_body_view.js +++ b/app/assets/javascripts/discourse/views/modal/modal_body_view.js @@ -19,7 +19,7 @@ // like an actual ember view flash: function(msg, flashClass) { var $alert; - if (!flashClass) flashClass = "success"; + if (!flashClass) flashClass = "success"; $alert = jQuery('#modal-alert').hide().removeClass('alert-error', 'alert-success'); $alert.addClass("alert alert-" + flashClass).html(msg); return $alert.fadeIn(); diff --git a/app/assets/javascripts/discourse/views/modal/not_activated_view.js b/app/assets/javascripts/discourse/views/modal/not_activated_view.js new file mode 100644 index 00000000000..ab2654e0f81 --- /dev/null +++ b/app/assets/javascripts/discourse/views/modal/not_activated_view.js @@ -0,0 +1,11 @@ + +window.Discourse.NotActivatedView = window.Discourse.ModalBodyView.extend(Discourse.Presence, { + templateName: 'modal/not_activated', + title: Em.String.i18n('log_in'), + emailSent: false, + + sendActivationEmail: function() { + $.get('/users/' + this.get('username') + '/send_activation_email'); + this.set('emailSent', true); + } +}); diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index b8f4c7ff972..0d0d388f44b 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -27,7 +27,7 @@ class SessionController < ApplicationController render_serialized(@user, UserSerializer) return else - render :json => {error: I18n.t("login.not_activated")} + render :json => {error: I18n.t("login.not_activated"), reason: 'not_activated', sent_to_email: @user.email_logs.where(email_type: 'signup').order('created_at DESC').first.try(:to_address), current_email: @user.email} return end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index b81dbf58f54..42fdf3cc73f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -292,6 +292,16 @@ class UsersController < ApplicationController render :layout => 'no_js' end + def send_activation_email + @user = fetch_user_from_params + @email_token = @user.email_tokens.unconfirmed.active.first + if @user + @email_token = @user.email_tokens.create(email: @user.email) if @email_token.nil? + Jobs.enqueue(:user_email, type: :signup, user_id: @user.id, email_token: @email_token.token) + end + render nothing: true + end + def search_users term = params[:term].to_s.strip topic_id = params[:topic_id] diff --git a/app/models/email_token.rb b/app/models/email_token.rb index 8a66bf65388..36813418705 100644 --- a/app/models/email_token.rb +++ b/app/models/email_token.rb @@ -22,6 +22,14 @@ class EmailToken < ActiveRecord::Base 1.week.ago end + def self.unconfirmed + where(confirmed: false) + end + + def self.active + where(expired: false) + end + def self.generate_token SecureRandom.hex(EmailToken.token_length) end diff --git a/app/views/users/send_activation_email.html.erb b/app/views/users/send_activation_email.html.erb new file mode 100644 index 00000000000..c111d5db217 --- /dev/null +++ b/app/views/users/send_activation_email.html.erb @@ -0,0 +1,14 @@ +
+

+ <% if @user %> + <%= t 'login.sent_activate_email_again', email: @user.email %> + <% else %> + <% + # We should never get here because we only show the link to resend + # the activation email if you successfully logged in, but your + # email hasn't been confirmed. + %> + <%= t 'login.incorrect_username_email_or_password' %> + <% end %> +

+
\ No newline at end of file diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 811a7b64f38..eb123fa1e6e 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -233,6 +233,9 @@ en: authenticating: "Authenticating..." awaiting_confirmation: "Your account is awaiting activation, use the forgot password link to issue another activation email." awaiting_approval: "Your account has not been approved by a moderator yet. You will receive an email when it is approved." + not_activated: "You can't log in yet. We sent an activation email to you at {{sentTo}}. Please follow the instructions in the email to activate your account." + resend_activation_email: "Click here to send the activation email again." + sent_activation_email_again: "We sent another activation email to you at your current email address: {{currentEmail}}. It might take a few minutes for it to arrive. Be sure to check your spam folder." google: title: "Log In with Google" message: "Authenticating with Google (make sure pop up blockers are not enabled)" diff --git a/config/routes.rb b/config/routes.rb index 0e0bf55e875..c6ba9843b81 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -96,6 +96,7 @@ Discourse::Application.routes.draw do put 'users/:username/preferences/username' => 'users#username', :format => false, :constraints => {:username => USERNAME_ROUTE_FORMAT} get 'users/:username/avatar(/:size)' => 'users#avatar', :constraints => {:username => USERNAME_ROUTE_FORMAT} get 'users/:username/invited' => 'users#invited', :constraints => {:username => USERNAME_ROUTE_FORMAT} + get 'users/:username/send_activation_email' => 'users#send_activation_email', :constraints => {:username => USERNAME_ROUTE_FORMAT} resources :uploads diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index c5ad0f84396..a975ee8dc5d 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -761,4 +761,46 @@ describe UsersController do end + describe 'send_activation_email' do + context 'for an existing user' do + let(:user) { Fabricate(:user) } + + before do + UsersController.any_instance.stubs(:fetch_user_from_params).returns(user) + end + + context 'with a valid email_token' do + it 'should send the activation email' do + Jobs.expects(:enqueue).with(:user_email, has_entries(type: :signup)) + xhr :get, :send_activation_email, username: user.username + end + end + + context 'without an existing email_token' do + before do + user.email_tokens.each {|t| t.destroy} + user.reload + end + + it 'should generate a new token' do + expect { + xhr :get, :send_activation_email, username: user.username + }.to change{ user.email_tokens(true).count }.by(1) + end + + it 'should send an email' do + Jobs.expects(:enqueue).with(:user_email, has_entries(type: :signup)) + xhr :get, :send_activation_email, username: user.username + end + end + end + + context 'when username does not exist' do + it 'should not send an email' do + Jobs.expects(:enqueue).never + xhr :get, :send_activation_email, username: 'nopenopenopenope' + end + end + end + end