ruby-on-rails - user_signed_in - rails admin
Diseñar: realice una solicitud sin restablecer la cuenta regresiva hasta que un usuario cierre la sesión debido a inactividad (4)
Estoy trabajando en una aplicación RoR con Devise. Quiero permitir que los clientes envíen una solicitud al servidor para ver cuánto tiempo queda hasta que el usuario en el cliente se desconecte automáticamente debido a la inactividad (usando el módulo de tiempo de Timeoutable
). No quiero que esta solicitud provoque que Devise restablezca la cuenta regresiva hasta que el usuario cierre la sesión. ¿Cómo puedo configurar esto?
Este es el código que tengo ahora:
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
funciona porque cada vez que RoR recibe una solicitud de un usuario autenticado, Timeoutable#timeout_in
se restablece a cualquier cosa que haya configurado en Devise#timeout_in
. ¿Cómo puedo evitar que se reinicie en check_time_until_logout
y has_user_timed_out
?
Cuando veo esto correctamente (probablemente dependiendo de la versión del Timeoutable
el Timeoutable
maneja el cierre de sesión a través del módulo Timeoutable
.
esto está conectado a Warden, que forma parte de la pila de middleware del rack.
Si miras el código, entonces puedes encontrar esta parte:
unless warden.request.env[''devise.skip_trackable'']
warden.session(scope)[''last_request_at''] = Time.now.utc
end
así que por lo que puedo ver aquí, deberías poder configurar devise.skip_trackable
en true antes de que se devise.skip_trackable
el middleware de devise.skip_trackable
.
Creo que este problema aquí explica cómo usarlo realmente: https://github.com/plataformatec/devise/issues/953
Las dos respuestas principales de anteponer un request.env["devise.skip_trackable"] = true
acción request.env["devise.skip_trackable"] = true
no funcionó para mí inicialmente.
¡Parece que esto solo funciona si está utilizando el authenticate_user!
"clásico" authenticate_user!
Antes de la acción, y no cuando se utilizan rutas autenticadas. Si está utilizando rutas autenticadas en su config/routes.rb
, como por ejemplo:
authenticate :user do
resources :some_resources
end
La suspensión de la acción no parece afectar a la cadena de Devise a tiempo, por lo que debe comentar el bloqueo de authenticate
, utilice un identificador_usuario authenticate_user!
antes de la acción en su app/controllers/application_controller.rb
, y agregue una acción skip_trackable
que establezca skip_trackable
en true en un controlador específico.
Su código para el tiempo restante es incorrecto. El cálculo correcto será:
def check_time_until_logout
@time_left = Devise.timeout_in.seconds - (Time.now.to_i - user_session["last_request_at"]).round
end
Terminé teniendo que hacer varios cambios en mi código. Mostraré con qué terminé cuando terminé, luego explicaré lo que hace:
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
Estos son los cambios que hice:
Usando env["devise.skip_trackable"]
Este es el código que evita que Devise restaure el tiempo que espera antes de cerrar la sesión de un usuario debido a la inactividad:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
Este código cambia un valor de hash que Devise utiliza internamente para decidir si actualizar el valor que almacena para realizar un seguimiento de cuándo tuvo la última actividad el usuario. Específicamente, este es el código de dispositivo con el que estamos interactuando ( link ):
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
(Tenga en cuenta que este código se ejecuta cada vez que Rails procesa una solicitud de un cliente).
Estas líneas cerca del final son interesantes para nosotros:
unless env[''devise.skip_trackable'']
warden.session(scope)[''last_request_at''] = Time.now.utc
end
Este es el código que "reinicia" la cuenta regresiva hasta que el usuario cierre la sesión debido a inactividad. Solo se ejecuta si env[''devise.skip_trackable'']
no es true
, por lo que necesitamos cambiar ese valor antes de que Devise procese la solicitud del usuario.
Para hacer eso, le decimos a Rails que cambie el valor de env[''devise.skip_trackable'']
antes de que haga algo más. De nuevo, desde mi código final:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
Todo lo que está por encima de este punto es lo que necesitaba cambiar para responder mi pregunta. Sin embargo, había otros cambios que necesitaba hacer para que mi código funcionara como quería, así que también los documentaré aquí.
Usando Timeoutable
correctamente
He leído mal la documentación sobre el módulo Timeoutable
, por lo que mi código en mi pregunta tiene otros problemas.
Primero, mi método check_time_until_logout
iba a devolver siempre el mismo valor. Esta es la versión incorrecta de la acción que tuve:
def check_time_until_logout
@time_left = current_user.timeout_in
end
Pensé que Timeoutable#timeout_in
devolvería la cantidad de tiempo hasta que el usuario se desconectara automáticamente. En su lugar, devuelve la cantidad de tiempo que Devise está configurado para esperar antes de cerrar la sesión de los usuarios. Necesitamos calcular cuánto tiempo más nos ha dejado el usuario.
Para calcular esto, necesitamos saber cuándo fue la última vez que el usuario tuvo una actividad que Devise reconoció. Este código, de la fuente de Devise que vimos anteriormente, determina cuándo fue la última vez que el usuario estuvo activo:
last_request_at = warden.session(scope)[''last_request_at'']
Necesitamos obtener un identificador para el objeto devuelto por warden.session(scope)
. Parece que el hash de user_session
, que Devise nos proporciona como los identificadores current_user
y user_signed_in?
, es ese objeto.
Usando el hash user_session
y calculando el tiempo restante, el método check_time_until_logout
se convierte en
def check_time_until_logout
@time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
end
¿También Timeoutable#timedout?
mal la documentación de Timeoutable#timedout?
. Comprueba si el usuario ha agotado el tiempo de espera cuando pasa la última vez que estuvo activo, no cuando pasa la hora actual. El cambio que debemos hacer es sencillo: en lugar de pasar Time.now
, necesitamos pasar el tiempo en el hash user_session
:
def has_user_timed_out
@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
end
Una vez que hice estos tres cambios, mi controlador actuó como yo esperaba.