diff --git a/app/controllers/users/invitations_controller.rb b/app/controllers/users/invitations_controller.rb deleted file mode 100644 index d2026c64e2..0000000000 --- a/app/controllers/users/invitations_controller.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -class Users::InvitationsController < Devise::InvitationsController - # GET /users/invitation/accept?invitation_token=abcdef123456 - def edit - set_minimum_password_length - # Ensure the invitation_token is set on the resource from the URL parameter - resource.invitation_token = params[:invitation_token] - - # Removed logging of invitation tokens for security reasons - - - render :edit - end - - # PUT /users/invitation - def update - # Removed logging of invitation tokens for security reasons - super - end - - protected - - # Permit the invitation_token parameter - def update_resource_params - params.require(resource_name).permit(:invitation_token, :password, :password_confirmation) - end -end diff --git a/app/views/devise/invitations/edit.html.erb b/app/views/devise/invitations/edit.html.erb index 24d5348055..bb47799bf3 100644 --- a/app/views/devise/invitations/edit.html.erb +++ b/app/views/devise/invitations/edit.html.erb @@ -3,7 +3,7 @@ <%= form_with(model: resource, as: resource_name, url: invitation_path(resource_name), local: true, html: {method: :put}) do |f| %> <%= render "/shared/error_messages", resource: resource %> - <%= f.hidden_field :invitation_token %> + <%= f.hidden_field :invitation_token, readonly: true %> <% if f.object.class.require_password_on_accepting %>
diff --git a/config/routes.rb b/config/routes.rb index e486ce17cf..6f849a7f53 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,7 +5,7 @@ mount Rswag::Api::Engine => "/api-docs" devise_for :all_casa_admins, path: "all_casa_admins", controllers: {sessions: "all_casa_admins/sessions"} - devise_for :users, controllers: {sessions: "users/sessions", passwords: "users/passwords", invitations: "users/invitations"} + devise_for :users, controllers: {sessions: "users/sessions", passwords: "users/passwords"} authenticate :all_casa_admins do mount PgHero::Engine, at: "pg_dashboard", constraints: lambda { |request| admin = request.env["warden"].user(:all_casa_admin) diff --git a/spec/requests/users/invitations_controller_spec.rb b/spec/requests/users/invitations_controller_spec.rb deleted file mode 100644 index eb403449e7..0000000000 --- a/spec/requests/users/invitations_controller_spec.rb +++ /dev/null @@ -1,236 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe "Users::InvitationsController", type: :request do - let(:casa_org) { create(:casa_org) } - let(:volunteer) { create(:volunteer, casa_org: casa_org) } - - describe "GET /users/invitation/accept" do - context "with valid invitation_token" do - before do - volunteer.invite! - end - - it "sets invitation_token on the resource" do - get accept_user_invitation_path(invitation_token: volunteer.raw_invitation_token) - - expect(response).to have_http_status(:success) - expect(response.body).to include("Set my password") - expect(response.body).to include(volunteer.raw_invitation_token) - end - - it "renders the edit template" do - get accept_user_invitation_path(invitation_token: volunteer.raw_invitation_token) - - expect(response).to render_template(:edit) - end - end - - context "without invitation_token" do - it "redirects away" do - get accept_user_invitation_path - - # Devise may redirect to root or sign_in depending on configuration - expect(response).to have_http_status(:redirect) - end - end - end - - describe "PUT /users/invitation" do - let(:valid_password) { "Password123!" } - - context "with valid invitation_token and password" do - before do - volunteer.invite! - end - - let(:params) do - { - user: { - invitation_token: volunteer.raw_invitation_token, - password: valid_password, - password_confirmation: valid_password - } - } - end - - it "accepts the invitation" do - expect { - put user_invitation_path, params: params - }.to change { volunteer.reload.invitation_accepted_at }.from(nil) - end - - it "sets the password" do - put user_invitation_path, params: params - - volunteer.reload - expect(volunteer.valid_password?(valid_password)).to be true - end - - it "signs in the user" do - put user_invitation_path, params: params - - expect(controller.current_user).to eq(volunteer) - end - - it "redirects after acceptance" do - put user_invitation_path, params: params - - expect(response).to redirect_to(authenticated_user_root_path) - end - end - - context "with invalid invitation_token" do - let(:params) do - { - user: { - invitation_token: "invalid_token", - password: valid_password, - password_confirmation: valid_password - } - } - end - - it "does not accept the invitation" do - put user_invitation_path, params: params - - expect(response).to have_http_status(:success) - expect(response.body).to include("Invitation token is invalid") - end - end - - context "with blank invitation_token" do - let(:params) do - { - user: { - invitation_token: "", - password: valid_password, - password_confirmation: valid_password - } - } - end - - it "shows validation error" do - put user_invitation_path, params: params - - expect(response).to have_http_status(:success) - expect(response.body).to include("Invitation token") - end - end - - context "with mismatched passwords" do - before do - volunteer.invite! - end - - let(:params) do - { - user: { - invitation_token: volunteer.raw_invitation_token, - password: valid_password, - password_confirmation: "DifferentPassword123!" - } - } - end - - it "does not accept the invitation" do - expect { - put user_invitation_path, params: params - }.not_to change { volunteer.reload.invitation_accepted_at } - end - - it "shows validation error" do - put user_invitation_path, params: params - - expect(response).to have_http_status(:success) - expect(response.body).to include("Password confirmation") - end - end - - context "with password too short" do - before do - volunteer.invite! - end - - let(:params) do - { - user: { - invitation_token: volunteer.raw_invitation_token, - password: "short", - password_confirmation: "short" - } - } - end - - it "does not accept the invitation" do - expect { - put user_invitation_path, params: params - }.not_to change { volunteer.reload.invitation_accepted_at } - end - - it "shows validation error" do - put user_invitation_path, params: params - - expect(response).to have_http_status(:success) - expect(response.body).to include("Password is too short") - end - end - - context "with expired invitation" do - before do - volunteer.invite! - travel_to 2.years.from_now - end - - after do - travel_back - end - - let(:params) do - { - user: { - invitation_token: volunteer.raw_invitation_token, - password: valid_password, - password_confirmation: valid_password - } - } - end - - it "does not accept the invitation" do - expect { - put user_invitation_path, params: params - }.not_to change { volunteer.reload.invitation_accepted_at } - end - - it "shows validation error" do - put user_invitation_path, params: params - - expect(response).to have_http_status(:success) - expect(response.body).to include("Invitation token is invalid") - end - end - end - - describe "parameter sanitization" do - before do - volunteer.invite! - end - - it "permits invitation_token in update" do - params = { - user: { - invitation_token: volunteer.raw_invitation_token, - password: "Password123!", - password_confirmation: "Password123!", - extra_param: "should_not_be_permitted" - } - } - - put user_invitation_path, params: params - - # If the invitation_token was properly permitted, the invitation should be accepted - expect(volunteer.reload.invitation_accepted_at).to be_present - end - end -end diff --git a/spec/views/devise/invitations/edit.html.erb_spec.rb b/spec/views/devise/invitations/edit.html.erb_spec.rb deleted file mode 100644 index a01a7bb57e..0000000000 --- a/spec/views/devise/invitations/edit.html.erb_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe "devise/invitations/edit.html.erb", type: :view do - let(:casa_org) { create(:casa_org) } - let(:volunteer) { create(:volunteer, casa_org: casa_org) } - - before do - volunteer.invite! - assign(:resource, volunteer) - assign(:resource_name, :user) - assign(:devise_mapping, Devise.mappings[:user]) - assign(:minimum_password_length, 6) - - # Set the invitation_token on the resource as the controller does - volunteer.invitation_token = volunteer.raw_invitation_token - - # Allow the class to respond to require_password_on_accepting - allow(volunteer.class).to receive(:require_password_on_accepting).and_return(true) - - render - end - - it "uses form_with with local: true" do - # form_with local: true should not have data-remote="true" - expect(rendered).not_to have_selector('form[data-remote="true"]') - end - - it "includes invitation_token field" do - expect(rendered).to match(/invitation_token/) - end - - it "does not have readonly attribute on invitation_token field" do - expect(rendered).not_to match(/invitation_token.*readonly/) - end - - it "includes password fields" do - expect(rendered).to match(/password/) - expect(rendered).to match(/password_confirmation/) - end - - it "includes submit button" do - expect(rendered).to have_button("Set my password") - end - - it "uses PUT method" do - expect(rendered).to have_selector('form[method="post"]') # Rails uses POST with _method=put - expect(rendered).to have_field("_method", type: :hidden, with: "put") - end - - it "posts to invitation_path" do - expect(rendered).to have_selector("form[action='#{user_invitation_path}']") - end -end