# frozen_string_literal: true say 'Setting up custom authentication...', :green say ' Adding bcrypt gem...', :cyan gem 'bcrypt', '~> 3.1' # Generate models after bundle install after_bundle do say ' Generating User model...', :cyan generate :model, 'User email:string:uniq password_digest:string role:integer' say ' Generating Session model...', :cyan generate :model, 'Session user:references token:string:uniq ip_address:string user_agent:string' end # User model with has_secure_password and roles file 'app/models/user.rb', <<~RUBY, force: true class User < ApplicationRecord include PublicIdentifiable set_public_id_prefix :usr has_secure_password enum :role, { user: 0, admin: 1, super_admin: 2, owner: 3 }, default: :user normalizes :email, with: ->(email) { email.strip.downcase } validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP } validates :password, length: { minimum: 8 }, if: -> { password.present? } validates :role, presence: true # Helper method to check if user has any admin access def admin_or_above? admin? || super_admin? || owner? end # Helper method to check if user has super admin or owner access def super_admin_or_above? super_admin? || owner? end end RUBY file 'app/models/session.rb', <<~RUBY, force: true class Session < ApplicationRecord belongs_to :user before_create :generate_token validates :token, presence: true, uniqueness: true private def generate_token self.token = SecureRandom.urlsafe_base64(32) end end RUBY say ' Creating Current model...', :cyan file 'app/models/current.rb', <<~RUBY class Current < ActiveSupport::CurrentAttributes attribute :session, :user delegate :user, to: :session, allow_nil: true end RUBY say ' Creating Authentication concern...', :cyan file 'app/controllers/concerns/authentication.rb', <<~RUBY module Authentication extend ActiveSupport::Concern included do before_action :authenticate helper_method :signed_in?, :current_user end private def authenticate if (session_record = find_session_by_cookie) Current.session = session_record end end def require_authentication redirect_to sign_in_path, alert: 'Please sign in to continue.' unless signed_in? end def require_admin require_authentication return if current_user&.admin_or_above? redirect_to root_path, alert: 'You are not authorized to access this page.' end def require_super_admin require_authentication return if current_user&.super_admin? redirect_to root_path, alert: 'You are not authorized to access this page.' end def signed_in? Current.session.present? end def current_user Current.user end def find_session_by_cookie Session.find_by(token: cookies.signed[:session_token]) end def start_session(user) session_record = user.sessions.create!( ip_address: request.remote_ip, user_agent: request.user_agent ) cookies.signed.permanent[:session_token] = { value: session_record.token, httponly: true, same_site: :lax } Current.session = session_record end def end_session Current.session&.destroy cookies.delete(:session_token) end end RUBY say ' Adding to ApplicationController...', :cyan inject_into_class 'app/controllers/application_controller.rb', 'ApplicationController', <<~RUBY include Authentication RUBY say ' Creating SessionsController...', :cyan file 'app/controllers/sessions_controller.rb', <<~RUBY class SessionsController < ApplicationController skip_before_action :authenticate, only: %i[new create] def new redirect_to root_path if signed_in? end def create if (user = User.find_by(email: params[:email])&.authenticate(params[:password])) start_session(user) redirect_to root_path, notice: 'Signed in successfully.' else flash.now[:alert] = 'Invalid email or password.' render :new, status: :unprocessable_entity end end def destroy end_session redirect_to root_path, notice: 'Signed out successfully.' end end RUBY say ' Creating RegistrationsController...', :cyan file 'app/controllers/registrations_controller.rb', <<~RUBY class RegistrationsController < ApplicationController skip_before_action :authenticate, only: %i[new create] def new redirect_to root_path if signed_in? @user = User.new end def create @user = User.new(user_params) if @user.save start_session(@user) redirect_to root_path, notice: 'Account created successfully.' else render :new, status: :unprocessable_entity end end private def user_params params.require(:user).permit(:email, :password, :password_confirmation) end end RUBY say ' Creating sign in view...', :cyan file 'app/views/sessions/new.html.erb', <<~ERB
Or <%= link_to 'create a new account', sign_up_path, class: 'font-medium text-canopy-green hover:text-fresh-leaf' %>
Already have an account? <%= link_to 'Sign in', sign_in_path, class: 'font-medium text-canopy-green hover:text-fresh-leaf' %>