diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb index 749265c23..383926dfc 100644 --- a/app/channels/application_cable/connection.rb +++ b/app/channels/application_cable/connection.rb @@ -1,7 +1,5 @@ module ApplicationCable class Connection < ActionCable::Connection::Base - include Authentication::SessionLookup - identified_by :current_user def connect @@ -10,11 +8,17 @@ module ApplicationCable private def find_verified_user - if verified_session = find_session_by_cookie - verified_session.user + if session = find_session_by_cookie + session.user else reject_unauthorized_connection end end + + def find_session_by_cookie + if token = cookies.signed[:session_token] + Session.find_signed(token) + end + end end end diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index 1a0d6f376..0d9ed45a4 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -1,80 +1,64 @@ module Authentication extend ActiveSupport::Concern - include SessionLookup included do before_action :require_authentication - helper_method :signed_in? - - protect_from_forgery with: :exception, unless: -> { authenticated_by.bot_key? } + helper_method :authenticated? end class_methods do - def require_unauthenticated_access(**options) - allow_unauthenticated_access **options - before_action :redirect_signed_in_user_to_root, **options - end - def allow_unauthenticated_access(**options) skip_before_action :require_authentication, **options - before_action :restore_authentication, **options + before_action :resume_session, **options end end private - def signed_in? - Current.user.present? + def authenticated? + Current.session.present? end def require_authentication - restore_authentication || request_authentication + resume_session || request_authentication end - def restore_authentication + + def resume_session if session = find_session_by_cookie - resume_session session + set_current_session session end end + def find_session_by_cookie + if token = cookies.signed[:session_token] + Session.find_signed(token) + end + end + + def request_authentication session[:return_to_after_authenticating] = request.url redirect_to new_session_url end - def redirect_signed_in_user_to_root - redirect_to root_url if signed_in? - end - - def start_new_session_for(user) - user.sessions.start!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| - authenticated_as session - end - end - - def resume_session(session) - session.resume user_agent: request.user_agent, ip_address: request.remote_ip - authenticated_as session - end - - def authenticated_as(session) - Current.user = session.user - set_authenticated_by(:session) - cookies.signed.permanent[:session_token] = { value: session.token, httponly: true, same_site: :lax } - end - - def post_authenticating_url + def after_authentication_url session.delete(:return_to_after_authenticating) || root_url end - def reset_authentication + + def start_new_session_for(user) + user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| + set_current_session session + end + end + + def set_current_session(session) + Current.session = session + cookies.signed.permanent[:session_token] = { value: session.signed_id, httponly: true, same_site: :lax } + end + + def terminate_session + Current.session.destroy cookies.delete(:session_token) end - - def set_authenticated_by(method) - @authenticated_by = method.to_s.inquiry - end - - def authenticated_by - @authenticated_by ||= "".inquiry - end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b16c149f9..b50f0b23e 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,32 +1,21 @@ class SessionsController < ApplicationController allow_unauthenticated_access only: %i[ new create ] - rate_limit to: 10, within: 3.minutes, only: :create, with: -> { render_rejection :too_many_requests } + rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." } def new end def create - if user = User.active.authenticate_by(user_params) + if user = User.authenticate_by(params.permit(:email_address, :password)) start_new_session_for user - redirect_to post_authenticating_url + redirect_to after_authentication_url else - render_rejection :unauthorized + redirect_to new_session_url, alert: "Try another email address or password." end end def destroy - reset_authentication - - redirect_to root_url + terminate_session + redirect_to new_session_url end - - private - def user_params - params.require(:user).permit(:email_address, :password) - end - - def render_rejection(status) - flash[:alert] = "Too many requests or unauthorized." - render :new, status: status - end end diff --git a/app/models/current.rb b/app/models/current.rb index 91419049e..2bef56dad 100644 --- a/app/models/current.rb +++ b/app/models/current.rb @@ -1,7 +1,4 @@ class Current < ActiveSupport::CurrentAttributes - attribute :user - - def account - user.account - end + attribute :session + delegate :user, to: :session, allow_nil: true end diff --git a/app/models/session.rb b/app/models/session.rb index c70b6fa10..cf376fb28 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -1,19 +1,3 @@ class Session < ApplicationRecord - ACTIVITY_REFRESH_RATE = 1.hour - - has_secure_token - belongs_to :user - - before_create { self.last_active_at ||= Time.now } - - def self.start!(user_agent:, ip_address:) - create! user_agent: user_agent, ip_address: ip_address - end - - def resume(user_agent:, ip_address:) - if last_active_at.before?(ACTIVITY_REFRESH_RATE.ago) - update! user_agent: user_agent, ip_address: ip_address, last_active_at: Time.now - end - end end diff --git a/app/models/user.rb b/app/models/user.rb index feed3607f..a5379c86f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,15 +1,12 @@ class User < ApplicationRecord belongs_to :account - has_many :sessions, dependent: :destroy - has_many :splats, dependent: :destroy + has_many :sessions, dependent: :destroy has_secure_password validations: false - scope :active, -> { where(active: true) } + has_many :splats, dependent: :destroy - def current? - self == Current.user - end + normalizes :email_address, with: ->(value) { value.strip.downcase } def initials name.scan(/\b\w/).join @@ -24,6 +21,6 @@ class User < ApplicationRecord private def deactived_email_address - email_address&.gsub(/@/, "-deactivated-#{SecureRandom.uuid}@") + email_address.sub(/@/, "-deactivated-#{SecureRandom.uuid}@") end end diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 67ee3d63c..18a37669a 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -1,5 +1,4 @@ <% content_for(:title) { "Sign in" } %> -<% turbo_page_requires_reload %>
" style="--panel-size: 40ch;"> <%#= image_tag "blob.svg", class: "product__logo center colorize--black", size: 130 %> @@ -12,7 +11,7 @@

Fizzy

- <%= form_with model: User.new, url: session_path, class: "flex flex-column gap" do |form| %> + <%= form_with url: session_path, class: "flex flex-column gap" do |form| %>
<%= translation_button(:email_address) %>