remember me, password reset

This commit is contained in:
2026-01-30 10:08:41 +01:00
parent 4e5c25adbf
commit 20ce18ca74
18 changed files with 457 additions and 25 deletions
@@ -14,9 +14,7 @@ class Admin::InvitationsController < Admin::BaseController
def create
@invitation = User.new(invitation_params)
@invitation.invitation_token = SecureRandom.urlsafe_base64(32)
@invitation.invitation_sent_at = Time.current
@invitation.invited_by = current_user
@invitation.invite_by(current_user)
@invitation.password = SecureRandom.urlsafe_base64(16)
if @invitation.save
@@ -37,10 +35,7 @@ class Admin::InvitationsController < Admin::BaseController
return
end
@invitation.update!(
invitation_token: SecureRandom.urlsafe_base64(32),
invitation_sent_at: Time.current
)
@invitation.invite_by!(current_user)
InvitationMailer.invite(@invitation).deliver_later
+1 -5
View File
@@ -32,11 +32,7 @@ class Admin::RequestsController < Admin::BaseController
@entry = Entry.find(params[:id])
@user = @entry.requested_by
@user.update!(
invitation_token: SecureRandom.urlsafe_base64(32),
invitation_sent_at: Time.current,
invited_by: current_user
)
@user.invite_by!(current_user)
@entry.update!(status: :approved)
InvitationMailer.invite(@user, approved_entry: @entry).deliver_later
+38 -1
View File
@@ -4,6 +4,10 @@ class ApplicationController < ActionController::Base
# Changes to the importmap will invalidate the etag for HTML responses
stale_when_importmap_changes
SESSION_TIMEOUT = 3.days
before_action :check_session_timeout
helper_method :supported_languages, :current_user, :logged_in?, :admin?, :reviewer_or_admin?,
:contributor_or_above?, :setup_completed?
@@ -14,7 +18,40 @@ class ApplicationController < ActionController::Base
end
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
return @current_user if defined?(@current_user)
# First check session
if session[:user_id]
@current_user = User.find_by(id: session[:user_id])
# Then check remember me cookie
elsif cookies.signed[:remember_token]
user = User.find_by_valid_remember_token(cookies.signed[:remember_token])
if user
session[:user_id] = user.id
@current_user = user
else
# Invalid or expired remember token, clear it
cookies.delete(:remember_token)
end
end
@current_user
end
def check_session_timeout
return unless logged_in?
return if cookies.signed[:remember_token].present?
if session[:last_activity_at].present?
last_activity = Time.parse(session[:last_activity_at])
if last_activity < SESSION_TIMEOUT.ago
reset_session
redirect_to login_path, alert: "Your session has expired. Please sign in again."
return
end
end
session[:last_activity_at] = Time.current.to_s
end
def logged_in?
@@ -0,0 +1,79 @@
class PasswordResetsController < ApplicationController
RESET_TOKEN_EXPIRY = 1.hour
def new
# Show request password reset form
end
def create
@user = User.find_by(email: params[:email]&.downcase&.strip)
if @user&.invitation_accepted_at.present?
@user.update!(
reset_password_token: SecureRandom.urlsafe_base64(32),
reset_password_sent_at: Time.current
)
PasswordResetMailer.reset(@user).deliver_later
else
@user.invite_by!
InvitationMailer.invite(@user).deliver_later
end
redirect_to login_path, notice: "If that email address is in our system, you will receive password reset instructions."
end
def edit
@user = User.find_by(reset_password_token: params[:token])
if @user.nil?
redirect_to login_path, alert: "Invalid password reset link."
elsif password_reset_expired?(@user)
redirect_to new_password_reset_path, alert: "Password reset link has expired. Please request a new one."
end
end
def update
@user = User.find_by(reset_password_token: params[:token])
if @user.nil?
redirect_to login_path, alert: "Invalid password reset link."
return
end
if password_reset_expired?(@user)
redirect_to new_password_reset_path, alert: "Password reset link has expired. Please request a new one."
return
end
if params[:password].blank?
flash.now[:alert] = "Password cannot be blank."
render :edit, status: :unprocessable_entity
return
end
if params[:password] != params[:password_confirmation]
flash.now[:alert] = "Password confirmation doesn't match."
render :edit, status: :unprocessable_entity
return
end
@user.password = params[:password]
@user.password_confirmation = params[:password_confirmation]
@user.reset_password_token = nil
@user.reset_password_sent_at = nil
if @user.save
session[:user_id] = @user.id
redirect_to root_path, notice: "Your password has been reset successfully."
else
flash.now[:alert] = @user.errors.full_messages.join(", ")
render :edit, status: :unprocessable_entity
end
end
private
def password_reset_expired?(user)
user.reset_password_sent_at < RESET_TOKEN_EXPIRY.ago
end
end
+3 -1
View File
@@ -26,7 +26,9 @@ class SessionsController < ApplicationController
end
def destroy
session[:user_id] = nil
current_user&.forget_me if cookies.signed[:remember_token]
reset_session
cookies.delete(:remember_token)
redirect_to root_path, notice: "You have been logged out."
end
end