-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor: Remove devise_invitable and simplify invitation workflow
- Removed `devise_invitable` gem from the application. - Simplified the invitation process by eliminating the need to invite both the `User` model and the `Membership` model. - Invitations are now handled solely through the `Membership` model. - This refactor addresses several issues: - Reduced complexity in the invitation workflow, making it easier to maintain and understand. - Fixed edge cases where duplicate or inconsistent invitations occurred due to the dual-model approach. - Streamlined user creation and association, ensuring a smoother onboarding experience. - Updated relevant tests and documentation to reflect the new workflow. This change simplifies the codebase and enhances the reliability of the invitation system while maintaining all existing functionality.
- Loading branch information
Showing
60 changed files
with
1,167 additions
and
970 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,79 @@ | ||
class Users::InvitationsController < Devise::InvitationsController | ||
before_action :show_navigation_bars | ||
before_action :delete_user_record, if: :user_should_be_cleared?, only: :create | ||
after_action :confirm_new_user_membership, only: :update # rubocop:disable Rails/LexicallyScopedActionFilter | ||
class Users::InvitationsController < ApplicationController | ||
skip_before_action :authenticate_user!, :redirect_user_with_no_organisation, only: %i[edit update] | ||
before_action :validate_permissions, only: %i[new create] | ||
rescue_from AcceptInvitationForm::InvalidTokenError, with: :invalid_token_handler | ||
# new invitation | ||
def new | ||
@invitation_form = InvitationForm.new | ||
end | ||
|
||
# send the invitation | ||
def create | ||
if user_is_invalid? || user_belongs_to_current_organisation? | ||
self.resource = invite_resource | ||
render(params[:user][:source] == "invite_admin" ? :invite_second_admin : :new) | ||
return | ||
end | ||
|
||
self.resource = invite_resource unless user_belongs_to_other_organisations? | ||
@invitation_form = InvitationForm.new(invitation_params) | ||
if @invitation_form.valid? | ||
@invitation_form.save! | ||
|
||
add_user_to_organisation(organisation) | ||
|
||
redirect_to(memberships_path, notice: "#{invited_user.email} has been invited to join #{organisation.name}") | ||
redirect_to memberships_path, notice: "#{@invitation_form.email} has been invited to join #{current_organisation.name}" | ||
else | ||
render(params.dig(:invitation_form, :source) == "invite_admin" ? :invite_second_admin : :new) | ||
end | ||
end | ||
|
||
def invite_second_admin | ||
@user = User.new | ||
# invitee accepts the invitation - ask name / password if required | ||
def edit | ||
@form = AcceptInvitationForm.new(invitation_token: params[:invitation_token]) | ||
end | ||
|
||
private | ||
|
||
def show_navigation_bars | ||
false if action_name == "invite_second_action" | ||
end | ||
|
||
def organisation | ||
current_organisation | ||
# invitee accepts the invitation | ||
def update | ||
invitation_params = params.require(:accept_invitation_form).permit(:password, :name, :invitation_token) | ||
@form = AcceptInvitationForm.new(invitation_params) | ||
if @form.save | ||
redirect_to new_user_session_path, notice: "You have successfully joined #{@form.organisation_name}. Please log in." | ||
else | ||
render :edit | ||
end | ||
end | ||
|
||
def add_user_to_organisation(organisation) | ||
membership = invited_user.memberships.find_or_create_by!(invited_by_id: current_user.id, organisation:) | ||
membership.permission_level = params[:permission_level] | ||
membership.save! | ||
send_invite_email(membership) if user_has_confirmed_account? | ||
end | ||
def resend | ||
email = params[:email] | ||
user = User.find_by_email(email) | ||
membership = user&.membership_for(current_organisation) | ||
raise "Cannot find membership for user #{email} and organisation #{current_organisation.name}" if user.nil? || membership.nil? | ||
raise "The membership for user #{email} and organisation #{current_organisation.name} has already been confirmed" if membership.confirmed? | ||
|
||
def send_invite_email(membership) | ||
GovWifiMailer.membership_instructions( | ||
invited_user, | ||
user, | ||
membership.invitation_token, | ||
organisation: membership.organisation, | ||
).deliver_now | ||
redirect_to memberships_path | ||
end | ||
|
||
def user_has_confirmed_account? | ||
invited_user.confirmed? | ||
end | ||
|
||
def user_belongs_to_current_organisation? | ||
invited_user&.confirmed? && invited_user.organisations.include?(organisation) | ||
end | ||
|
||
def user_belongs_to_other_organisations? | ||
invited_user.present? && | ||
invited_user.confirmed? && | ||
invited_user.organisations.pluck(:id).exclude?(current_organisation&.id) | ||
end | ||
|
||
def authenticate_inviter! | ||
# https://github.com/scambra/devise_invitable#controller-filter | ||
unless current_user&.can_manage_team?(current_organisation) | ||
redirect_to(root_path) and return | ||
end | ||
end | ||
|
||
def delete_user_record | ||
invited_user.destroy! unless invited_user.nil? | ||
end | ||
|
||
def user_is_invalid? | ||
!invite_params[:email].match? Devise.email_regexp | ||
end | ||
|
||
def invited_user | ||
User.find_by(email: invite_params[:email]) | ||
end | ||
|
||
def resending_invite? | ||
!!params[:resend] | ||
end | ||
|
||
def user_should_be_cleared? | ||
resending_invite? || unconfirmed_user_with_no_org? | ||
def invite_second_admin | ||
@form = InvitationForm.new | ||
end | ||
|
||
def unconfirmed_user_with_no_org? | ||
invited_user_already_exists? && | ||
invited_user_not_confirmed? && | ||
invited_user_has_no_org? && | ||
!invited_user.is_super_admin? | ||
end | ||
private | ||
|
||
def invited_user_already_exists? | ||
!!invited_user | ||
def invalid_token_handler(exception) | ||
redirect_to root_path, alert: exception.message | ||
end | ||
|
||
def invited_user_not_confirmed? | ||
!invited_user.confirmed? | ||
def validate_permissions | ||
redirect_to(root_path) unless current_user.can_manage_team?(current_organisation) | ||
end | ||
|
||
def invited_user_has_no_org? | ||
invited_user.organisations.empty? | ||
def invitation_token | ||
params[:invitation_token] || params.dig(:accept_invitation_form, :invitation_token) | ||
end | ||
|
||
def confirm_new_user_membership | ||
current_user.default_membership.confirm! if current_user | ||
def invitation_params | ||
parameters = params.require(:invitation_form).permit(:email, :permission_level) | ||
parameters.merge(inviter: current_user, organisation: current_organisation) | ||
end | ||
|
||
# Overrides https://github.com/scambra/devise_invitable/blob/master/app/controllers/devise/invitations_controller.rb#L105 | ||
def invite_params | ||
params.require(:user).permit(:email) | ||
def show_navigation_bars | ||
action_name != "invite_second_action" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# frozen_string_literal: true | ||
|
||
module PermissionsHelper | ||
def permissions_radio_data | ||
[ | ||
OpenStruct.new( | ||
value: Membership::Permissions::ADMINISTRATOR, | ||
text: "Administrator", | ||
hint: <<~HINT.html_safe, | ||
<span>View locations and IPs, team members, and logs</span><br> | ||
<span>Manage locations and IPs</span><br> | ||
<span>Add or remove team members</span><br> | ||
<span>View, add and remove certificates</span> | ||
HINT | ||
), | ||
OpenStruct.new( | ||
value: Membership::Permissions::MANAGE_LOCATIONS, | ||
text: "Manage Locations", | ||
hint: <<~HINT.html_safe, | ||
<span>View locations and IPs, team members, and logs</span><br> | ||
<span>Manage locations and IPs</span><br> | ||
<span>Cannot add or remove team members</span><br> | ||
<span>View, add and remove certificates</span> | ||
HINT | ||
), | ||
OpenStruct.new( | ||
value: Membership::Permissions::VIEW_ONLY, | ||
text: "View only", | ||
hint: <<~HINT.html_safe, | ||
<span>View locations and IPs, team members, and logs</span><br> | ||
<span>Cannot manage locations and IPs</span><br> | ||
<span>Cannot add or remove team members</span> | ||
HINT | ||
), | ||
] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.