sessions_controller.rb 3.03 KB
class SessionsController < ApplicationController
  allow_unauthenticated_access only: %i[ new create ]
  rate_limit to: 10, within: 3.minutes, only: :create, with: -> {
    flash.now[:alert] = "操作过频,请稍后重试"
    render inertia: "sessions/New", props: { flash: flash.to_h }
  }

  def new
    render inertia: "sessions/New", props: {
      flash: flash.to_h
    }
  end

  def create
    if user = User.authenticate_by(params.permit(:email_address, :password))
      start_new_session_for user

      # Send notification email for new login if enabled
      # if user.notify_on_new_login? && !from_familiar_device?(user)
      #   SessionMailer.new_login_notification(user, request.remote_ip, request.user_agent).deliver_later
      # end

      redirect_to after_authentication_url
    else
      # For Inertia.js, we need to render the component with flash messages
      flash.now[:alert] = "账户或密码错误"
      render inertia: "sessions/New", props: {
        flash: flash.to_h
      }
    end
  end

  def destroy
    terminate_session
    redirect_to new_session_path, notice: "你已登出"
  end

  def index
    @sessions = current_user.sessions.order(updated_at: :desc)
    render inertia: "sessions/Index", props: {
      sessions: @sessions.as_json(methods: [ :is_current ]),
      flash: flash.to_h
    }
  end

  def show
    @session = current_user.sessions.find(params[:id])
    render inertia: "sessions/Show", props: {
      session: @session.as_json(methods: [ :is_current ]),
      flash: flash.to_h
    }
  rescue ActiveRecord::RecordNotFound
    redirect_to sessions_path, alert: "Session not found."
  end

  def destroy_session
    @session = current_user.sessions.find(params[:id])

    if @session.is_current?
      redirect_to sessions_path, alert: "你不能终止当前会话。"
    else
      @session.destroy
      redirect_to sessions_path, notice: "会话已终止。"
    end
  rescue ActiveRecord::RecordNotFound
    redirect_to sessions_path, alert: "会话未找到。"
  end

  def terminate_all
    current_user.sessions.where.not(id: current_session.id).destroy_all
    redirect_to sessions_path, notice: "所有其他会话已终止。"
  end

  def security
    render inertia: "sessions/Security", props: {
      security_settings: current_user.security_settings,
      flash: flash.to_h
    }
  end

  def update_security
    if current_user.update(security_params)
      redirect_to security_sessions_path, notice: "安全设置已更新。"
    else
      render inertia: "sessions/Security", props: {
        security_settings: current_user.security_settings,
        errors: current_user.errors,
        flash: flash.to_h
      }
    end
  end

  private

  def from_familiar_device?(user)
    # Check if this device has been used before by this user
    user.sessions.where(user_agent: request.user_agent, ip_address: request.remote_ip).exists?
  end

  def security_params
    params.require(:user).permit(
      :require_two_factor,
      :session_timeout_minutes,
      :notify_on_new_login,
      :max_sessions
    )
  end
end