From 39757a43dc08ad44e27a370be16bd555d1dbca05 Mon Sep 17 00:00:00 2001 From: Dan Milne Date: Fri, 24 Oct 2025 23:26:07 +1100 Subject: [PATCH] Add an invite system --- app/controllers/admin/users_controller.rb | 16 ++++++++++-- app/controllers/invitations_controller.rb | 31 +++++++++++++++++++++++ app/controllers/sessions_controller.rb | 6 ++++- app/mailers/invitations_mailer.rb | 6 +++++ app/views/invitations/show.html.erb | 22 ++++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 app/controllers/invitations_controller.rb create mode 100644 app/mailers/invitations_mailer.rb create mode 100644 app/views/invitations/show.html.erb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c8365a6..3ca54c8 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,6 +1,6 @@ module Admin class UsersController < BaseController - before_action :set_user, only: [:show, :edit, :update, :destroy] + before_action :set_user, only: [:show, :edit, :update, :destroy, :resend_invitation] def index @users = User.order(created_at: :desc) @@ -16,9 +16,11 @@ module Admin def create @user = User.new(user_params) @user.password = SecureRandom.alphanumeric(16) if user_params[:password].blank? + @user.status = :pending_invitation if @user.save - redirect_to admin_users_path, notice: "User created successfully." + InvitationsMailer.invite_user(@user).deliver_later + redirect_to admin_users_path, notice: "User created successfully. Invitation email sent to #{@user.email_address}." else render :new, status: :unprocessable_entity end @@ -46,6 +48,16 @@ module Admin end end + def resend_invitation + unless @user.pending_invitation? + redirect_to admin_users_path, alert: "Cannot send invitation. User is not pending invitation." + return + end + + InvitationsMailer.invite_user(@user).deliver_later + redirect_to admin_users_path, notice: "Invitation email resent to #{@user.email_address}." + end + def destroy # Prevent admin from deleting themselves if @user == Current.session.user diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb new file mode 100644 index 0000000..2dae64b --- /dev/null +++ b/app/controllers/invitations_controller.rb @@ -0,0 +1,31 @@ +class InvitationsController < ApplicationController + allow_unauthenticated_access + before_action :set_user_by_invitation_token, only: %i[ show update ] + + def show + # Show the password setup form + end + + def update + if @user.update(params.permit(:password, :password_confirmation)) + @user.update!(status: :active) + @user.sessions.destroy_all + redirect_to new_session_path, notice: "Your account has been set up successfully. Please sign in." + else + redirect_to invite_path(params[:token]), alert: "Passwords did not match." + end + end + + private + + def set_user_by_invitation_token + @user = User.find_by_invitation_login_token!(params[:token]) + + # Check if user is still pending invitation + unless @user.pending_invitation? + redirect_to new_session_path, alert: "This invitation has already been used or is no longer valid." + end + rescue ActiveSupport::MessageVerifier::InvalidSignature + redirect_to new_session_path, alert: "Invitation link is invalid or has expired." + end +end \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 26da98a..65915f1 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -23,7 +23,11 @@ class SessionsController < ApplicationController # Check if user is active unless user.active? - redirect_to signin_path, alert: "Your account is not active. Please contact an administrator." + if user.pending_invitation? + redirect_to signin_path, alert: "Please check your email for an invitation to set up your account." + else + redirect_to signin_path, alert: "Your account is not active. Please contact an administrator." + end return end diff --git a/app/mailers/invitations_mailer.rb b/app/mailers/invitations_mailer.rb new file mode 100644 index 0000000..4943ccc --- /dev/null +++ b/app/mailers/invitations_mailer.rb @@ -0,0 +1,6 @@ +class InvitationsMailer < ApplicationMailer + def invite_user(user) + @user = user + mail subject: "You're invited to join Clinch", to: user.email_address + end +end \ No newline at end of file diff --git a/app/views/invitations/show.html.erb b/app/views/invitations/show.html.erb new file mode 100644 index 0000000..70edaf4 --- /dev/null +++ b/app/views/invitations/show.html.erb @@ -0,0 +1,22 @@ +
+ <% if alert = flash[:alert] %> +

<%= alert %>

+ <% end %> + +

Welcome to Clinch!

+

You've been invited to join Clinch. Please create your password to complete your account setup.

+ + <%= form_with url: invite_path(params[:token]), method: :put, class: "contents" do |form| %> +
+ <%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %> +
+ +
+ <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Confirm your password", maxlength: 72, class: "block shadow-sm rounded-md border border-gray-400 focus:outline-solid focus:outline-blue-600 px-3 py-2 mt-2 w-full" %> +
+ +
+ <%= form.submit "Create Account", class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %> +
+ <% end %> +
\ No newline at end of file