mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 12:53:42 +08:00
FEATURE: Allow users to resend/update email from confirmation page
This commit is contained in:
parent
b381372184
commit
12fb20fe1b
|
@ -134,7 +134,6 @@ export function buildResolver(baseName) {
|
|||
if (full.indexOf('connectors') === 0) {
|
||||
return Ember.TEMPLATES[`javascripts/${full}`];
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
resolveTemplate(parsedName) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export default Ember.Component.extend({
|
||||
classNames: 'activation-controls'
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { changeEmail } from 'discourse/lib/user-activation';
|
||||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
accountCreated: null,
|
||||
newEmail: null,
|
||||
|
||||
@computed('newEmail', 'accountCreated.email')
|
||||
submitDisabled(newEmail, currentEmail) {
|
||||
return newEmail === currentEmail;
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeEmail() {
|
||||
const email = this.get('newEmail');
|
||||
changeEmail({ email }).then(() => {
|
||||
this.set('accountCreated.email', email);
|
||||
this.transitionToRoute('account-created.resent');
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
cancel() {
|
||||
this.transitionToRoute('account-created.index');
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { resendActivationEmail } from 'discourse/lib/user-activation';
|
||||
|
||||
export default Ember.Controller.extend({
|
||||
actions: {
|
||||
sendActivationEmail() {
|
||||
resendActivationEmail(this.get('accountCreated.username')).then(() => {
|
||||
this.transitionToRoute('account-created.resent');
|
||||
});
|
||||
},
|
||||
editActivationEmail() {
|
||||
this.transitionToRoute('account-created.edit-email');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
import computed from 'ember-addons/ember-computed-decorators';
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { extractError } from 'discourse/lib/ajax-error';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
import { changeEmail } from 'discourse/lib/user-activation';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
login: Ember.inject.controller(),
|
||||
|
@ -20,13 +19,10 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
changeEmail() {
|
||||
const login = this.get('login');
|
||||
|
||||
ajax(userPath('update-activation-email'), {
|
||||
data: {
|
||||
changeEmail({
|
||||
username: login.get('loginName'),
|
||||
password: login.get('loginPassword'),
|
||||
email: this.get('newEmail')
|
||||
},
|
||||
type: 'PUT'
|
||||
}).then(() => {
|
||||
const modal = this.showModal('activation-resent', {title: 'log_in'});
|
||||
modal.set('currentEmail', this.get('newEmail'));
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
import { resendActivationEmail } from 'discourse/lib/user-activation';
|
||||
|
||||
export default Ember.Controller.extend(ModalFunctionality, {
|
||||
actions: {
|
||||
sendActivationEmail() {
|
||||
ajax(userPath('action/send_activation_email'), {
|
||||
data: { username: this.get('username') },
|
||||
type: 'POST'
|
||||
}).then(() => {
|
||||
resendActivationEmail(this.get('username')).then(() => {
|
||||
const modal = this.showModal('activation-resent', {title: 'log_in'});
|
||||
modal.set('currentEmail', this.get('currentEmail'));
|
||||
}).catch(popupAjaxError);
|
||||
});
|
||||
},
|
||||
|
||||
editActivationEmail() {
|
||||
|
|
15
app/assets/javascripts/discourse/lib/user-activation.js.es6
Normal file
15
app/assets/javascripts/discourse/lib/user-activation.js.es6
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { ajax } from 'discourse/lib/ajax';
|
||||
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||
import { userPath } from 'discourse/lib/url';
|
||||
|
||||
export function resendActivationEmail(username) {
|
||||
return ajax(userPath('action/send_activation_email'), {
|
||||
type: 'POST',
|
||||
data: { username }
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
|
||||
export function changeEmail(data) {
|
||||
return ajax(userPath('update-activation-email'), { data, type: 'PUT' });
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default Ember.Route.extend({
|
||||
setupController(controller) {
|
||||
const accountCreated = this.controllerFor('account-created').get('accountCreated');
|
||||
controller.set('accountCreated', accountCreated);
|
||||
controller.set('newEmail', accountCreated.email);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
export default Ember.Route.extend({
|
||||
setupController(controller) {
|
||||
controller.set(
|
||||
'accountCreated',
|
||||
this.controllerFor('account-created').get('accountCreated')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export default Ember.Route.extend({
|
||||
setupController(controller) {
|
||||
controller.set(
|
||||
'email',
|
||||
this.controllerFor('account-created').get('accountCreated.email')
|
||||
);
|
||||
}
|
||||
});
|
|
@ -64,7 +64,10 @@ export default function() {
|
|||
// User routes
|
||||
this.route('users', { resetNamespace: true, path: '/u' });
|
||||
this.route('password-reset', { path: '/u/password-reset/:token' });
|
||||
this.route('account-created', { path: '/u/account-created' });
|
||||
this.route('account-created', { path: '/u/account-created' }, function() {
|
||||
this.route('resent');
|
||||
this.route('edit-email');
|
||||
});
|
||||
this.route('user', { path: '/u/:username', resetNamespace: true }, function() {
|
||||
this.route('summary');
|
||||
this.route('userActivity', { path: '/activity', resetNamespace: true }, function() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<div id='simple-container'>
|
||||
<div class='account-created'>{{{accountCreated.message}}}</div>
|
||||
<div class='account-created'>
|
||||
{{outlet}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div class='ac-message'>
|
||||
{{activation-email-form email=newEmail}}
|
||||
</div>
|
||||
|
||||
<div class='activation-controls'>
|
||||
{{d-button action="changeEmail"
|
||||
label="login.submit_new_email"
|
||||
disabled=submitDisabled
|
||||
class="btn-primary"}}
|
||||
{{d-button action=(action "cancel") label="cancel" class="edit-cancel"}}
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
<div class='ac-message'>
|
||||
{{{accountCreated.message}}}
|
||||
</div>
|
||||
{{#if accountCreated.username}}
|
||||
{{activation-controls sendActivationEmail=(action "sendActivationEmail")
|
||||
editActivationEmail=(action "editActivationEmail")}}
|
||||
{{/if}}
|
|
@ -0,0 +1,3 @@
|
|||
<div class='ac-message'>
|
||||
{{{i18n 'login.sent_activation_email_again' currentEmail=email}}}
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
{{d-button action=sendActivationEmail
|
||||
label="login.resend_title"
|
||||
icon="envelope"
|
||||
class="btn-primary resend"}}
|
||||
{{d-button action=editActivationEmail
|
||||
label="login.change_email"
|
||||
icon="pencil"
|
||||
class="edit-email"}}
|
|
@ -0,0 +1,2 @@
|
|||
<p>{{i18n "login.provide_new_email"}}</p>
|
||||
{{input value=email class="activate-new-email"}}
|
|
@ -1,6 +1,5 @@
|
|||
{{#d-modal-body}}
|
||||
<p>{{i18n "login.provide_new_email"}}</p>
|
||||
{{input value=newEmail class="activate-new-email"}}
|
||||
{{activation-email-form email=newEmail}}
|
||||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
|
|
|
@ -3,12 +3,6 @@
|
|||
{{/d-modal-body}}
|
||||
|
||||
<div class="modal-footer">
|
||||
{{d-button action="sendActivationEmail"
|
||||
label="login.resend_title"
|
||||
icon="envelope"
|
||||
class="btn-primary resend"}}
|
||||
{{d-button action="editActivationEmail"
|
||||
label="login.change_email"
|
||||
icon="pencil"
|
||||
class="edit-email"}}
|
||||
{{activation-controls sendActivationEmail=(action "sendActivationEmail")
|
||||
editActivationEmail=(action "editActivationEmail")}}
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,15 @@
|
|||
margin: 0 auto;
|
||||
|
||||
.account-created {
|
||||
.ac-message {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.activation-controls {
|
||||
button {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -357,6 +357,7 @@ class UsersController < ApplicationController
|
|||
|
||||
# save user email in session, to show on account-created page
|
||||
session["user_created_message"] = activation.message
|
||||
session[SessionController::ACTIVATE_USER_KEY] = user.id
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
|
@ -534,8 +535,16 @@ class UsersController < ApplicationController
|
|||
def account_created
|
||||
@custom_body_class = "static-account-created"
|
||||
@message = session['user_created_message'] || I18n.t('activation.missing_session')
|
||||
store_preloaded("accountCreated", MultiJson.dump(message: @message))
|
||||
@account_created = { message: @message }
|
||||
|
||||
if session_user_id = session[SessionController::ACTIVATE_USER_KEY]
|
||||
if user = User.where(id: session_user_id.to_i).first
|
||||
@account_created[:username] = user.username
|
||||
@account_created[:email] = user.email
|
||||
end
|
||||
end
|
||||
|
||||
store_preloaded("accountCreated", MultiJson.dump(@account_created))
|
||||
expires_now
|
||||
render "default/empty"
|
||||
end
|
||||
|
@ -573,13 +582,18 @@ class UsersController < ApplicationController
|
|||
def update_activation_email
|
||||
RateLimiter.new(nil, "activate-edit-email-hr-#{request.remote_ip}", 5, 1.hour).performed!
|
||||
|
||||
if params[:username].present?
|
||||
@user = User.find_by_username_or_email(params[:username])
|
||||
raise Discourse::InvalidAccess.new unless @user.present?
|
||||
raise Discourse::InvalidAccess.new unless @user.confirm_password?(params[:password])
|
||||
elsif user_key = session[SessionController::ACTIVATE_USER_KEY]
|
||||
@user = User.where(id: user_key.to_i).first
|
||||
end
|
||||
|
||||
raise Discourse::InvalidAccess.new unless @user.present?
|
||||
raise Discourse::InvalidAccess.new if @user.active?
|
||||
raise Discourse::InvalidAccess.new if current_user.present?
|
||||
|
||||
raise Discourse::InvalidAccess.new unless @user.confirm_password?(params[:password])
|
||||
|
||||
User.transaction do
|
||||
@user.email = params[:email]
|
||||
if @user.save
|
||||
|
@ -598,8 +612,9 @@ class UsersController < ApplicationController
|
|||
RateLimiter.new(nil, "activate-min-#{request.remote_ip}", 6, 1.minute).performed!
|
||||
end
|
||||
|
||||
if params[:username].present?
|
||||
@user = User.find_by_username_or_email(params[:username].to_s)
|
||||
|
||||
end
|
||||
raise Discourse::NotFound unless @user
|
||||
|
||||
if !current_user&.staff? &&
|
||||
|
|
|
@ -1661,7 +1661,7 @@ en:
|
|||
incorrect_username_email_or_password: "Incorrect username, email or password"
|
||||
wait_approval: "Thanks for signing up. We will notify you when your account has been approved."
|
||||
active: "Your account is activated and ready to use."
|
||||
activate_email: "<p>You're almost done! We sent an activation mail to <b>%{email}</b>. Please follow the instructions in the email to activate your account.</p><p>If it doesn't arrive, check your spam folder, or try to log in again to send another activation mail or to input a new email address.</p>"
|
||||
activate_email: "<p>You're almost done! We sent an activation mail to <b>%{email}</b>. Please follow the instructions in the email to activate your account.</p><p>If it doesn't arrive, check your spam folder, send another activation mail or input a new email address.</p>"
|
||||
not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account."
|
||||
not_allowed_from_ip_address: "You can't log in as %{username} from that IP address."
|
||||
admin_not_allowed_from_ip_address: "You can't log in as admin from that IP address."
|
||||
|
|
|
@ -324,6 +324,8 @@ Discourse::Application.routes.draw do
|
|||
post "#{root_path}/read-faq" => "users#read_faq"
|
||||
get "#{root_path}/search/users" => "users#search_users"
|
||||
get "#{root_path}/account-created/" => "users#account_created"
|
||||
get "#{root_path}/account-created/resent" => "users#account_created"
|
||||
get "#{root_path}/account-created/edit-email" => "users#account_created"
|
||||
get({ "#{root_path}/password-reset/:token" => "users#password_reset" }.merge(index == 1 ? { as: :password_reset_token } : {}))
|
||||
get "#{root_path}/confirm-email-token/:token" => "users#confirm_email_token", constraints: { format: 'json' }
|
||||
put "#{root_path}/password-reset/:token" => "users#password_reset"
|
||||
|
|
|
@ -482,6 +482,7 @@ describe UsersController do
|
|||
|
||||
# should save user_created_message in session
|
||||
expect(session["user_created_message"]).to be_present
|
||||
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_present
|
||||
end
|
||||
|
||||
context "and 'must approve users' site setting is enabled" do
|
||||
|
@ -591,6 +592,7 @@ describe UsersController do
|
|||
|
||||
# should save user_created_message in session
|
||||
expect(session["user_created_message"]).to be_present
|
||||
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_present
|
||||
end
|
||||
|
||||
it "shows the 'active' message" do
|
||||
|
@ -676,6 +678,7 @@ describe UsersController do
|
|||
|
||||
# should not change the session
|
||||
expect(session["user_created_message"]).to be_blank
|
||||
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -720,6 +723,7 @@ describe UsersController do
|
|||
|
||||
# should not change the session
|
||||
expect(session["user_created_message"]).to be_blank
|
||||
expect(session[SessionController::ACTIVATE_USER_KEY]).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1872,6 +1876,55 @@ describe UsersController do
|
|||
|
||||
describe '.update_activation_email' do
|
||||
|
||||
context "with a session variable" do
|
||||
|
||||
it "raises an error with an invalid session value" do
|
||||
session[SessionController::ACTIVATE_USER_KEY] = 1234
|
||||
xhr :put, :update_activation_email, { email: 'updatedemail@example.com' }
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "raises an error for an active user" do
|
||||
user = Fabricate(:walter_white)
|
||||
session[SessionController::ACTIVATE_USER_KEY] = user.id
|
||||
xhr :put, :update_activation_email, { email: 'updatedemail@example.com' }
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "raises an error when logged in" do
|
||||
moderator = log_in(:moderator)
|
||||
session[SessionController::ACTIVATE_USER_KEY] = moderator.id
|
||||
xhr :put, :update_activation_email, { email: 'updatedemail@example.com' }
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "raises an error when the new email is taken" do
|
||||
active_user = Fabricate(:user)
|
||||
user = Fabricate(:inactive_user)
|
||||
session[SessionController::ACTIVATE_USER_KEY] = user.id
|
||||
xhr :put, :update_activation_email, { email: active_user.email }
|
||||
expect(response).to_not be_success
|
||||
end
|
||||
|
||||
it "can be updated" do
|
||||
user = Fabricate(:inactive_user)
|
||||
token = user.email_tokens.first
|
||||
|
||||
session[SessionController::ACTIVATE_USER_KEY] = user.id
|
||||
xhr :put, :update_activation_email, { email: 'updatedemail@example.com' }
|
||||
|
||||
expect(response).to be_success
|
||||
|
||||
user.reload
|
||||
expect(user.email).to eq('updatedemail@example.com')
|
||||
expect(user.email_tokens.where(email: 'updatedemail@example.com', expired: false)).to be_present
|
||||
|
||||
token.reload
|
||||
expect(token.expired?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a username and password" do
|
||||
it "raises an error with an invalid username" do
|
||||
xhr :put, :update_activation_email, {
|
||||
username: 'eviltrout',
|
||||
|
@ -1942,4 +1995,46 @@ describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "account_created" do
|
||||
|
||||
it "returns a message when no session is present" do
|
||||
get :account_created
|
||||
created = assigns(:account_created)
|
||||
expect(created).to be_present
|
||||
expect(created[:message]).to eq(I18n.t('activation.missing_session'))
|
||||
expect(created[:email]).to be_blank
|
||||
expect(created[:username]).to be_blank
|
||||
end
|
||||
|
||||
context "when the user account is created" do
|
||||
before do
|
||||
session['user_created_message'] = "Donuts"
|
||||
end
|
||||
|
||||
it "returns the message when set in the session" do
|
||||
get :account_created
|
||||
created = assigns(:account_created)
|
||||
expect(created).to be_present
|
||||
expect(created[:message]).to eq('Donuts')
|
||||
expect(created[:email]).to be_blank
|
||||
expect(created[:username]).to be_blank
|
||||
end
|
||||
|
||||
it "includes user information when the session variable is present " do
|
||||
user = Fabricate(:user, active: false)
|
||||
session[SessionController::ACTIVATE_USER_KEY] = user.id
|
||||
|
||||
get :account_created
|
||||
created = assigns(:account_created)
|
||||
expect(created).to be_present
|
||||
expect(created[:message]).to eq('Donuts')
|
||||
expect(created[:email]).to eq(user.email)
|
||||
expect(created[:username]).to eq(user.username)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -3,15 +3,92 @@ import PreloadStore from 'preload-store';
|
|||
|
||||
acceptance("Account Created");
|
||||
|
||||
test("account created", () => {
|
||||
visit("/u/account-created");
|
||||
test("account created - message", assert => {
|
||||
PreloadStore.store('accountCreated', {
|
||||
message: "Hello World"
|
||||
});
|
||||
visit("/u/account-created");
|
||||
|
||||
andThen(() => {
|
||||
ok(exists('.account-created'));
|
||||
equal(find('.account-created').text(), "Hello World", "it displays the message");
|
||||
assert.ok(exists('.account-created'));
|
||||
assert.equal(
|
||||
find('.account-created .ac-message').text().trim(),
|
||||
"Hello World",
|
||||
"it displays the message"
|
||||
);
|
||||
assert.notOk(exists('.activation-controls'));
|
||||
});
|
||||
});
|
||||
|
||||
test("account created - resend email", assert => {
|
||||
PreloadStore.store('accountCreated', {
|
||||
message: "Hello World",
|
||||
username: 'eviltrout',
|
||||
email: 'eviltrout@example.com'
|
||||
});
|
||||
visit("/u/account-created");
|
||||
|
||||
andThen(() => {
|
||||
assert.ok(exists('.account-created'));
|
||||
assert.equal(
|
||||
find('.account-created .ac-message').text().trim(),
|
||||
"Hello World",
|
||||
"it displays the message"
|
||||
);
|
||||
});
|
||||
|
||||
click('.activation-controls .resend');
|
||||
andThen(() => {
|
||||
assert.equal(currentPath(), "account-created.resent");
|
||||
const email = find('.account-created .ac-message b').text();
|
||||
assert.equal(email, 'eviltrout@example.com');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test("account created - update email - cancel", assert => {
|
||||
PreloadStore.store('accountCreated', {
|
||||
message: "Hello World",
|
||||
username: 'eviltrout',
|
||||
email: 'eviltrout@example.com'
|
||||
});
|
||||
visit("/u/account-created");
|
||||
|
||||
click('.activation-controls .edit-email');
|
||||
andThen(() => {
|
||||
assert.equal(currentPath(), "account-created.edit-email");
|
||||
assert.ok(find('.activation-controls .btn-primary:disabled').length);
|
||||
});
|
||||
|
||||
click('.activation-controls .edit-cancel');
|
||||
andThen(() => {
|
||||
assert.equal(currentPath(), "account-created.index");
|
||||
});
|
||||
});
|
||||
|
||||
test("account created - update email - submit", assert => {
|
||||
PreloadStore.store('accountCreated', {
|
||||
message: "Hello World",
|
||||
username: 'eviltrout',
|
||||
email: 'eviltrout@example.com'
|
||||
});
|
||||
visit("/u/account-created");
|
||||
|
||||
click('.activation-controls .edit-email');
|
||||
andThen(() => {
|
||||
assert.ok(find('.activation-controls .btn-primary:disabled').length);
|
||||
});
|
||||
|
||||
fillIn('.activate-new-email', 'newemail@example.com');
|
||||
andThen(() => {
|
||||
assert.notOk(find('.activation-controls .btn-primary:disabled').length);
|
||||
});
|
||||
|
||||
click('.activation-controls .btn-primary');
|
||||
andThen(() => {
|
||||
assert.equal(currentPath(), "account-created.resent");
|
||||
const email = find('.account-created .ac-message b').text();
|
||||
assert.equal(email, 'newemail@example.com');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user