115 lines
3.5 KiB
Elixir
115 lines
3.5 KiB
Elixir
defmodule ToweropsWeb.Plugs.UpdateSessionActivity do
|
|
@moduledoc """
|
|
Updates the last_activity_at timestamp for the current browser session on each request.
|
|
|
|
This plug tracks session activity and enforces an inactivity timeout of 30 minutes.
|
|
If a session has been inactive for longer than the timeout, the user is automatically
|
|
logged out for security purposes.
|
|
|
|
The activity timestamp is used in the Sessions tab of User Settings to show when
|
|
each session was last active. The update happens in a background task to avoid
|
|
slowing down requests.
|
|
"""
|
|
use Gettext, backend: ToweropsWeb.Gettext
|
|
|
|
import Phoenix.Controller, only: [put_flash: 3, redirect: 2]
|
|
import Plug.Conn
|
|
|
|
alias Towerops.Accounts
|
|
|
|
# 30 minutes of inactivity before automatic logout
|
|
@inactivity_timeout_seconds 30 * 60
|
|
|
|
@doc """
|
|
Initializes the plug with options (currently unused).
|
|
"""
|
|
def init(opts), do: opts
|
|
|
|
@doc """
|
|
Updates the last_activity_at timestamp for the current browser session.
|
|
|
|
Checks for session inactivity timeout and logs out the user if the session
|
|
has been inactive for more than 30 minutes.
|
|
|
|
Looks up the session by the token stored in the session cookie, then
|
|
updates the timestamp in a background task.
|
|
"""
|
|
def call(conn, _opts) do
|
|
case get_session(conn, :user_token) do
|
|
nil -> conn
|
|
token -> handle_session_activity(conn, token)
|
|
end
|
|
end
|
|
|
|
# Handle session activity check and timeout
|
|
defp handle_session_activity(conn, token) do
|
|
case check_session_activity(token) do
|
|
:active ->
|
|
# Session is active, update timestamp in background
|
|
Task.start(fn -> update_session_activity(token) end)
|
|
conn
|
|
|
|
:timeout ->
|
|
# Session timed out due to inactivity
|
|
logout_inactive_session(conn, token)
|
|
|
|
:not_found ->
|
|
# Session not found, continue normally
|
|
conn
|
|
end
|
|
end
|
|
|
|
# Check if session is still active or has timed out
|
|
defp check_session_activity(token) do
|
|
case Accounts.get_browser_session_by_token_value(token) do
|
|
nil ->
|
|
:not_found
|
|
|
|
session ->
|
|
if session_timed_out?(session) do
|
|
:timeout
|
|
else
|
|
:active
|
|
end
|
|
end
|
|
end
|
|
|
|
# Check if session has exceeded inactivity timeout
|
|
defp session_timed_out?(session) do
|
|
last_activity = session.last_activity_at || session.inserted_at
|
|
inactive_seconds = DateTime.diff(DateTime.utc_now(), last_activity, :second)
|
|
inactive_seconds > @inactivity_timeout_seconds
|
|
end
|
|
|
|
# Log out user due to inactivity timeout
|
|
defp logout_inactive_session(conn, token) do
|
|
# Delete the session token
|
|
Accounts.delete_user_session_token(token)
|
|
|
|
# Broadcast logout to LiveView socket
|
|
if live_socket_id = get_session(conn, :live_socket_id) do
|
|
ToweropsWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
|
|
end
|
|
|
|
# Clear session and redirect to login
|
|
conn
|
|
|> configure_session(renew: true)
|
|
|> clear_session()
|
|
|> delete_resp_cookie("_towerops_web_user_remember_me")
|
|
|> put_flash(:info, gettext("Your session expired due to inactivity. Please log in again."))
|
|
|> redirect(to: "/users/log-in")
|
|
|> halt()
|
|
end
|
|
|
|
defp update_session_activity(token) do
|
|
case Accounts.get_browser_session_by_token_value(token) do
|
|
nil ->
|
|
# No browser session found (might be an old session from before this feature)
|
|
:ok
|
|
|
|
session ->
|
|
# Update activity timestamp
|
|
Accounts.touch_browser_session(session)
|
|
end
|
|
end
|
|
end
|