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. """ 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 revokes the token if the session has been inactive for more than 30 minutes. All database work runs in a background task so the request is not blocked. When a timed-out session is detected, the token is deleted so the next request will be unauthenticated and redirected to login by the auth pipeline. 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 -> live_socket_id = get_session(conn, :live_socket_id) _ = Task.Supervisor.start_child(Towerops.TaskSupervisor, fn -> handle_session_activity(token, live_socket_id) end) conn end end @doc false def handle_session_activity(token, live_socket_id) do case Accounts.get_browser_session_by_token_value(token) do nil -> :ok session -> if session_timed_out?(session) do revoke_inactive_session(token, live_socket_id) else Accounts.touch_browser_session(session) end end end @doc false def 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 @doc false def revoke_inactive_session(token, live_socket_id) do Accounts.delete_user_session_token(token) if live_socket_id do ToweropsWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{}) end end end