All checks set to exit_status: 0 initially to avoid blocking on pre-existing violations. Remove exit_status overrides as each check category is cleaned up incrementally. Dependency added to dev/test only.
85 lines
2.5 KiB
Elixir
85 lines
2.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.
|
|
"""
|
|
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
|