diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 0ff5442f..04959e9a 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,4 +1,17 @@ module ApplicationCable class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + set_current_user || reject_unauthorized_connection + end + + private + + def set_current_user + if session ||= Session.find_by(id: cookies.signed[:session_id]) + self.current_user = session.user + end + end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index b9811248..8c4c5e2d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,28 +1,12 @@ class ApplicationController < ActionController::Base + include Authentication # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. allow_browser versions: :modern include Pagy::Backend add_flash_types :success, :notice - helper_method :current_user rescue_from CanCan::AccessDenied do - flash[:alert] = t("flash.un_authorize") - - if current_user.nil? - redirect_to login_path - else - redirect_to request.referer || thaalis_all_path(CURR_YR) - end - end - - private - - def current_user - @current_user ||= User.select(:id, :slug).find(session[:user_id]) if session[:user_id] - end - - def logged_in? - redirect_to thaalis_all_path(CURR_YR), notice: t("flash.active_session") if session[:user_id] + return_to_default_path(type: :alert, msg: "flash.un_authorize") end end diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb new file mode 100644 index 00000000..181379f2 --- /dev/null +++ b/app/controllers/concerns/authentication.rb @@ -0,0 +1,64 @@ +module Authentication + extend ActiveSupport::Concern + + included do + before_action :require_authentication + helper_method :authenticated?, :current_user + end + + class_methods do + def allow_unauthenticated_access(**options) + skip_before_action :require_authentication, **options + end + end + + private + + def authenticated? + resume_session + end + + def require_authentication + resume_session || request_authentication + end + + def resume_session + Current.session ||= find_session_by_cookie + end + + def find_session_by_cookie + Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id] + end + + def request_authentication + session[:return_to_after_authenticating] = request.url + redirect_to login_path, alert: t("flash.un_authorize") + end + + def after_authentication_url + return_to_url = session.delete(:return_to_after_authenticating) + return_to_url.present? ? "#{return_to_url}.html" : thaalis_all_path(CURR_YR, format: :html) + end + + def start_new_session_for(user) + user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| + Current.session = session + cookies.signed.permanent[:session_id] = {value: session.id, httponly: true, same_site: :lax} + end + end + + def terminate_session + Rails.cache.delete("user_#{current_user.id}_role") + Current.session.destroy + cookies.delete(:session_id) + end + + def return_to_default_path(type: :notice, msg: "flash.active_session") + flash[type] = t(msg) + redirect_to thaalis_all_path(CURR_YR) + end + + def current_user + @current_user ||= Current.user + end +end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 21ab591e..c32536fd 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,5 +1,6 @@ class PagesController < ApplicationController - before_action :logged_in? + allow_unauthenticated_access + before_action :return_to_default_path, if: -> { authenticated? } def home end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 4cb354f8..4506568f 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,26 +1,25 @@ class SessionsController < ApplicationController - before_action :logged_in?, only: :new + allow_unauthenticated_access only: %i[new create] + before_action :return_to_default_path, only: %i[new create], if: -> { authenticated? } + # rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." } def new end def create - user = User.authenticate_by(user_params) - if user - session[:user_id] = user.id + if user ||= User.authenticate_by(user_params) + start_new_session_for user respond_to do |format| - format.all { redirect_to thaalis_all_path(CURR_YR, format: :html), success: t(".success") } + format.all { redirect_to after_authentication_url, success: t(".success") } end else - flash.now.alert = t(".error") - render :new, status: :not_found + redirect_to login_path, alert: t(".error") end end def destroy - Rails.cache.delete("user_#{current_user.id}_role") - session[:user_id] = nil + terminate_session redirect_to login_path, success: t(".success") end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index a25faee1..8493ca3e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -38,7 +38,7 @@ def destroy if current_user.is?("admin") && current_user.id != @user&.id redirect_to users_path, success: t(".success") else - session[:user_id] = nil + terminate_session redirect_to login_path, success: t(".success") end end diff --git a/app/models/current.rb b/app/models/current.rb new file mode 100644 index 00000000..2bef56da --- /dev/null +++ b/app/models/current.rb @@ -0,0 +1,4 @@ +class Current < ActiveSupport::CurrentAttributes + attribute :session + delegate :user, to: :session, allow_nil: true +end diff --git a/app/models/session.rb b/app/models/session.rb new file mode 100644 index 00000000..cf376fb2 --- /dev/null +++ b/app/models/session.rb @@ -0,0 +1,3 @@ +class Session < ApplicationRecord + belongs_to :user +end diff --git a/app/models/user.rb b/app/models/user.rb index 1d65f77e..1f649297 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,7 @@ class User < ApplicationRecord rolify has_secure_password + has_many :sessions, dependent: :destroy # * Callbacks include NameCallback diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 01541f98..4311e7f8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -27,7 +27,7 @@
<%= render "shared/flash_messages" if flash.present? %> - <% if current_user.present? %> + <% if authenticated? %>