Разработать - сделать запрос без сброса обратного отсчета до тех пор, пока пользователь не выйдет из-за неактивности

Я работаю над RoR-приложением с Devise. Я хочу, чтобы клиенты отправляли запрос на сервер, чтобы узнать, сколько времени осталось до тех пор, пока пользователь на клиенте не будет автоматически отключен из-за неактивности (используя модуль Timeoutable). Я не хочу, чтобы этот запрос вызывал Devise reset обратный отсчет до тех пор, пока пользователь не выйдет из системы. Как я могу настроить это?

Это код, который у меня есть сейчас:

class SessionTimeoutController < ApplicationController
  before_filter :authenticate_user!

  # Calculates the number of seconds until the user is
  # automatically logged out due to inactivity. Unlike most
  # requests, it should not reset the timeout countdown itself.
  def check_time_until_logout
    @time_left = current_user.timeout_in
  end

  # Determines whether the user has been logged out due to
  # inactivity or not. Unlike most requests, it should not reset the
  # timeout countdown itself.
  def has_user_timed_out
    @has_timed_out = current_user.timedout? (Time.now)
  end

  # Resets the clock used to determine whether to log the user out
  # due to inactivity.
  def reset_user_clock
    # Receiving an arbitrary request from a client automatically
    # resets the Devise Timeoutable timer.
    head :ok
  end
end

SessionTimeoutController#reset_user_clock работает, потому что каждый раз, когда RoR получает запрос от аутентифицированного пользователя, Timeoutable#timeout_in есть reset к тому, что я настроил в Devise#timeout_in. Как предотвратить reset в check_time_until_logout и has_user_timed_out?

Ответы

Ответ 1

Мне пришлось внести несколько изменений в мой код. Я покажу, с чем закончил, когда закончил, а затем объясню, что он делает:

class SessionTimeoutController < ApplicationController
  # These are what prevent check_time_until_logout and
  # reset_user_clock from resetting users' Timeoutable
  # Devise "timers"
  prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
  def skip_timeout
    request.env["devise.skip_trackable"] = true
  end

  skip_before_filter :authenticate_user!, only: [:has_user_timed_out]

  def check_time_until_logout
    @time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
  end

  def has_user_timed_out
    @has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
  end

  def reset_user_clock
    # Receiving an arbitrary request from a client automatically
    # resets the Devise Timeoutable timer.
    head :ok
  end
end

Это изменения, которые я сделал:

Использование env["devise.skip_trackable"]

Это код, который не позволяет Devise сбросить время ожидания до выхода из системы из-за неактивности:

  prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
  def skip_timeout
    request.env["devise.skip_trackable"] = true
  end

Этот код изменяет хэш-значение, которое Devise использует внутри, чтобы решить, следует ли обновлять значение, которое он хранит, чтобы отслеживать, когда пользователь последний раз имел активность. В частности, это код разработки, с которым мы взаимодействуем (ссылка):

Warden::Manager.after_set_user do |record, warden, options|
  scope = options[:scope]
  env   = warden.request.env

  if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false
    last_request_at = warden.session(scope)['last_request_at']

    if record.timedout?(last_request_at) && !env['devise.skip_timeout']
      warden.logout(scope)
      if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout
        record.reset_authentication_token!
      end
      throw :warden, :scope => scope, :message => :timeout
    end

    unless env['devise.skip_trackable']
      warden.session(scope)['last_request_at'] = Time.now.utc
    end
  end
end

(Обратите внимание, что этот код выполняется каждый раз, когда Rails обрабатывает запрос от клиента.)

Эти строки ближе к концу нам интересны:

    unless env['devise.skip_trackable']
      warden.session(scope)['last_request_at'] = Time.now.utc
    end

Это код, который "сбрасывает" обратный отсчет до тех пор, пока пользователь не выйдет из системы из-за неактивности. Он выполняется только в том случае, если env['devise.skip_trackable'] не true, поэтому нам нужно изменить это значение, прежде чем Devise обрабатывает запрос пользователя.

Для этого мы говорим Rails, чтобы изменить значение env['devise.skip_trackable'], прежде чем он сделает что-нибудь еще. Опять же, из моего последнего кода:

  prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
  def skip_timeout
    request.env["devise.skip_trackable"] = true
  end

Все выше этого момента - это то, что мне нужно было изменить, чтобы ответить на мой вопрос. Были еще несколько изменений, которые мне нужно было сделать, чтобы мой код работал так, как я хотел, поэтому я также запишу их здесь.

Использование Timeoutable Правильно

Я неправильно прочитал документацию о модуле Timeoutable, поэтому у моего кода в моем вопросе есть еще пара проблем.

Во-первых, мой метод check_time_until_logout всегда возвращал одно и то же значение. Это неправильная версия действия, которое у меня было:

def check_time_until_logout
  @time_left = current_user.timeout_in
end

Я думал, что Timeoutable#timeout_in вернет время, пока пользователь не будет автоматически выведен из системы. Вместо этого он возвращает количество времени, в течение которого Devise настроен на ожидание до выхода из системы пользователей. Нам нужно рассчитать, насколько дольше пользователь оставил себя.

Чтобы вычислить это, нам нужно знать, когда пользователь в последний раз имел активность, распознанную Devise. Этот код из исходного кода, который мы рассмотрели выше, определяет, когда пользователь был последним:

    last_request_at = warden.session(scope)['last_request_at']

Нам нужно получить дескриптор объекта, возвращаемого warden.session(scope). Похоже на хеш user_session, который Devise предоставляет нам, как ручками current_user и user_signed_in?, является этот объект.

Используя хэш user_session и вычисляя оставшееся время, метод check_time_until_logout становится

  def check_time_until_logout
    @time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
  end

Я также неправильно читаю документацию для Timeoutable#timedout?. Он проверяет, не истечет ли пользователь, когда вы проходите в момент последнего использования пользователем, а не когда вы проходите в текущее время. Изменение, которое нам нужно сделать, является простым: вместо передачи в Time.now нам нужно пройти за время в user_session хеш:

  def has_user_timed_out
    @has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
  end

Как только я сделал эти три изменения, мой контроллер действовал так, как я ожидал.

Ответ 2

когда я вижу это правильно (возможно, в зависимости от разрабатываемой версии), devise обрабатывает выход из системы через модуль Timeoutable.

это подключается к начальнику, который является частью стелла для промежуточного программного обеспечения.

если вы посмотрите на код, вы можете найти эту часть:

unless warden.request.env['devise.skip_trackable']
  warden.session(scope)['last_request_at'] = Time.now.utc
end

поэтому из того, что я вижу здесь, вы должны установить devise.skip_trackable в true до того, как будет вызвано промежуточное программное обеспечение начальника.

Я думаю, что этот вопрос объясняет, как на самом деле использовать его: https://github.com/plataformatec/devise/issues/953

Ответ 3

Два ведущих ответа на добавление действия request.env["devise.skip_trackable"] = true для меня сначала не сработали.

Кажется, что это работает, только если вы используете "классический" authenticate_user! перед действием, а не при использовании аутентифицированных маршрутов. Если вы используете аутентифицированные маршруты в config/routes.rb, например:

authenticate :user do
  resources :some_resources
end

Превращение действия не влияет на цепочку разработки во времени, поэтому вы должны прокомментировать блокировку authenticate, используйте authenticate_user! перед действием вместо этого в app/controllers/application_controller.rb и добавьте действие prepend, которое устанавливает skip_trackable в true в конкретном контроллере.

Ответ 4

Ваш код на оставшееся время неверен. Правильный расчет будет:

def check_time_until_logout
  @time_left = Devise.timeout_in.seconds - (Time.now.to_i - user_session["last_request_at"]).round
end