Ответ 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
Как только я сделал эти три изменения, мой контроллер действовал так, как я ожидал.