Allow email address updates on account settings page
This commit is contained in:
parent
b1a693e7cf
commit
134c81460a
@ -1,7 +1,7 @@
|
|||||||
class SettingsController < ApplicationController
|
class SettingsController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_main_nav_section
|
before_action :set_main_nav_section
|
||||||
before_action :set_settings_section, only: ['show', 'update']
|
before_action :set_settings_section, only: [:show, :update, :update_email]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
redirect_to setting_path(:profile)
|
redirect_to setting_path(:profile)
|
||||||
@ -21,6 +21,25 @@ class SettingsController < ApplicationController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_email
|
||||||
|
if current_user.valid_ldap_authentication?(email_params[:current_password])
|
||||||
|
current_user.email = email_params[:email]
|
||||||
|
|
||||||
|
if current_user.update email: email_params[:email]
|
||||||
|
redirect_to setting_path(:account), flash: {
|
||||||
|
notice: 'Please confirm your new address using the confirmation link we just sent you.'
|
||||||
|
}
|
||||||
|
else
|
||||||
|
@validation_errors = current_user.errors
|
||||||
|
render :show, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
else
|
||||||
|
redirect_to setting_path(:account), flash: {
|
||||||
|
error: 'Password did not match your current password. Try again.'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def reset_password
|
def reset_password
|
||||||
current_user.send_reset_password_instructions
|
current_user.send_reset_password_instructions
|
||||||
sign_out current_user
|
sign_out current_user
|
||||||
@ -49,4 +68,8 @@ class SettingsController < ApplicationController
|
|||||||
:xmpp_exchange_contacts_with_invitees
|
:xmpp_exchange_contacts_with_invitees
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_params
|
||||||
|
params.require(:user).permit(:email, :current_password)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = [ "emailField", "editEmailButton" ]
|
||||||
|
static values = { validationFailed: Boolean }
|
||||||
|
|
||||||
|
connect () {
|
||||||
|
if (this.validationFailedValue) return;
|
||||||
|
|
||||||
|
this.emailFieldTarget.disabled = true;
|
||||||
|
this.element.querySelectorAll(".initial-hidden").forEach(el => {
|
||||||
|
el.classList.add("hidden");
|
||||||
|
})
|
||||||
|
this.element.querySelectorAll(".initial-visible").forEach(el => {
|
||||||
|
el.classList.remove("hidden");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editEmail (evt) {
|
||||||
|
this.emailFieldTarget.disabled = false;
|
||||||
|
this.emailFieldTarget.select();
|
||||||
|
this.editEmailButtonTarget.classList.add("hidden");
|
||||||
|
this.element.querySelectorAll(".initial-hidden").forEach(el => {
|
||||||
|
el.classList.remove("hidden");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-2 <%= custom_class %>"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 291 B After Width: | Height: | Size: 312 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-3"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit-3 <%= custom_class %>"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 317 B After Width: | Height: | Size: 338 B |
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit <%= custom_class %>"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||||
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 386 B |
@ -1,13 +1,44 @@
|
|||||||
<section>
|
<%= tag.section data: {
|
||||||
|
controller: "settings--account--email",
|
||||||
|
"settings--account--email-validation-failed-value": @validation_errors.present?
|
||||||
|
} do %>
|
||||||
<h3>E-Mail</h3>
|
<h3>E-Mail</h3>
|
||||||
<p class="mb-2">
|
<%= form_for(current_user, url: update_email_settings_path, method: "post") do |f| %>
|
||||||
<%= label :email, 'Address', class: 'font-bold' %>
|
<%= hidden_field_tag :section, "account" %>
|
||||||
</p>
|
<p class="mb-2">
|
||||||
<p class="flex gap-1 mb-2 sm:w-3/5">
|
<%= f.label :email, 'Address', class: 'font-bold' %>
|
||||||
<input type="text" id="email" class="grow"
|
</p>
|
||||||
value=<%= current_user.email %> disabled="disabled" />
|
<p class="mb-2 flex gap-1 sm:w-3/5">
|
||||||
</p>
|
<%= f.email_field :email, class: "grow", data: {
|
||||||
</section>
|
'settings--account--email-target': 'emailField'
|
||||||
|
}, required: true %>
|
||||||
|
<button type="button" id="edit-email"
|
||||||
|
class="btn-md btn-icon btn-blue shrink-0 hidden initial-visible"
|
||||||
|
data-settings--account--email-target="editEmailButton"
|
||||||
|
data-action="settings--account--email#editEmail"
|
||||||
|
title="Edit email address">
|
||||||
|
<span class="">
|
||||||
|
<%= render partial: "icons/edit-3", locals: {
|
||||||
|
custom_class: "text-white h-4 w-4 inline" } %>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<% if @validation_errors.present? && @validation_errors[:email].present? %>
|
||||||
|
<p class="error-msg"><%= @validation_errors[:email].first %></p>
|
||||||
|
<% end %>
|
||||||
|
<div class="initial-hidden">
|
||||||
|
<p class="mt-4 mb-2">
|
||||||
|
<%= f.label :current_password, 'Current password', class: 'font-bold' %>
|
||||||
|
</p>
|
||||||
|
<p class="sm:w-3/5">
|
||||||
|
<%= f.password_field :current_password, class: "w-full", required: true %>
|
||||||
|
</p>
|
||||||
|
<p class="mt-6">
|
||||||
|
<%= f.submit "Update", class: "btn-md btn-blue" %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<section>
|
<section>
|
||||||
<h3>Password</h3>
|
<h3>Password</h3>
|
||||||
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
|
<p class="mb-8">Use the following button to request an email with a password reset link:</p>
|
||||||
|
@ -28,6 +28,7 @@ Rails.application.routes.draw do
|
|||||||
|
|
||||||
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
|
resources :settings, param: 'section', only: ['index', 'show', 'update'] do
|
||||||
collection do
|
collection do
|
||||||
|
post 'update_email'
|
||||||
post 'reset_password'
|
post 'reset_password'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
55
spec/features/settings/account_spec.rb
Normal file
55
spec/features/settings/account_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Account settings', type: :feature do
|
||||||
|
let(:user) { create :user }
|
||||||
|
let(:geraint) { create :user, id: 2, cn: 'geraint', email: "lamagliarosa@example.com" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
login_as user, :scope => :user
|
||||||
|
geraint.save!
|
||||||
|
|
||||||
|
allow_any_instance_of(User).to receive(:valid_ldap_authentication?)
|
||||||
|
.with("invalid password").and_return(false)
|
||||||
|
allow_any_instance_of(User).to receive(:valid_ldap_authentication?)
|
||||||
|
.with("valid password").and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Update email address fails with invalid password' do
|
||||||
|
visit setting_path(:account)
|
||||||
|
fill_in 'Address', with: "lamagliarosa@example.com"
|
||||||
|
fill_in 'Current password', with: "invalid password"
|
||||||
|
click_button "Update"
|
||||||
|
|
||||||
|
expect(current_url).to eq(setting_url(:account))
|
||||||
|
expect(user.reload.unconfirmed_email).to be_nil
|
||||||
|
within ".flash-msg" do
|
||||||
|
expect(page).to have_content("did not match your current password")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Update email address fails when new address already taken' do
|
||||||
|
visit setting_path(:account)
|
||||||
|
fill_in 'Address', with: "lamagliarosa@example.com"
|
||||||
|
fill_in 'Current password', with: "valid password"
|
||||||
|
click_button "Update"
|
||||||
|
|
||||||
|
expect(current_url).to eq(setting_url(:update_email))
|
||||||
|
expect(user.reload.unconfirmed_email).to be_nil
|
||||||
|
within ".error-msg" do
|
||||||
|
expect(page).to have_content("has already been taken")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Update email address works' do
|
||||||
|
visit setting_path(:account)
|
||||||
|
fill_in 'Address', with: "lamagliabianca@example.com"
|
||||||
|
fill_in 'Current password', with: "valid password"
|
||||||
|
click_button "Update"
|
||||||
|
|
||||||
|
expect(current_url).to eq(setting_url(:account))
|
||||||
|
expect(user.reload.unconfirmed_email).to eq("lamagliabianca@example.com")
|
||||||
|
within ".flash-msg" do
|
||||||
|
expect(page).to have_content("Please confirm your new address")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -103,9 +103,16 @@ RSpec.describe User, type: :model do
|
|||||||
|
|
||||||
describe "#devise_after_confirmation" do
|
describe "#devise_after_confirmation" do
|
||||||
include ActiveJob::TestHelper
|
include ActiveJob::TestHelper
|
||||||
after { clear_enqueued_jobs }
|
|
||||||
|
|
||||||
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org" }
|
let(:user) { create :user, cn: "willherschel", ou: "kosmos.org", email: "will@hrsch.el" }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(user).to receive(:ldap_entry).and_return({
|
||||||
|
uid: "willherschel", ou: "kosmos.org", mail: "will@hrsch.el"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
after { clear_enqueued_jobs }
|
||||||
|
|
||||||
it "enables default services" do
|
it "enables default services" do
|
||||||
expect(user).to receive(:enable_service).with(%w[ discourse gitea mediawiki xmpp ])
|
expect(user).to receive(:enable_service).with(%w[ discourse gitea mediawiki xmpp ])
|
||||||
@ -124,10 +131,11 @@ RSpec.describe User, type: :model do
|
|||||||
let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" }
|
let(:guest) { create :user, id: 2, cn: "isaacnewton", ou: "kosmos.org", email: "newt@example.com" }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
# TODO remove when defaults are implemented
|
|
||||||
user.update! preferences: { xmpp_exchange_contacts_with_invitees: true }
|
|
||||||
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
Invitation.create! user: user, invited_user_id: guest.id, used_at: DateTime.now
|
||||||
allow_any_instance_of(User).to receive(:enable_service)
|
allow_any_instance_of(User).to receive(:enable_service)
|
||||||
|
allow(guest).to receive(:ldap_entry).and_return({
|
||||||
|
uid: "isaacnewton", ou: "kosmos.org", mail: "newt@example.com"
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
it "enqueues jobs to exchange XMPP contacts between inviter and invitee" do
|
it "enqueues jobs to exchange XMPP contacts between inviter and invitee" do
|
||||||
@ -138,5 +146,31 @@ RSpec.describe User, type: :model do
|
|||||||
expect(job["arguments"][1]['_aj_globalid']).to eq('gid://akkounts/User/2')
|
expect(job["arguments"][1]['_aj_globalid']).to eq('gid://akkounts/User/2')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "for email address update of existing account" do
|
||||||
|
before do
|
||||||
|
allow(user).to receive(:ldap_entry)
|
||||||
|
.and_return({ uid: "willherschel", ou: "kosmos.org", mail: "willyboy@aol.com" })
|
||||||
|
allow(user).to receive(:dn)
|
||||||
|
.and_return("cn=willherschel,ou=kosmos.org,cn=users,dc=kosmos,dc=org")
|
||||||
|
allow(LdapManager::UpdateEmail).to receive(:call)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates the LDAP 'mail' attribute" do
|
||||||
|
expect(LdapManager::UpdateEmail).to receive(:call)
|
||||||
|
.with("cn=willherschel,ou=kosmos.org,cn=users,dc=kosmos,dc=org", "will@hrsch.el")
|
||||||
|
user.send :devise_after_confirmation
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not re-enable default services" do
|
||||||
|
expect(user).not_to receive(:enable_service)
|
||||||
|
user.send :devise_after_confirmation
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not enqueue any delayed jobs" do
|
||||||
|
user.send :devise_after_confirmation
|
||||||
|
expect(enqueued_jobs).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user