From fd4f8b2342f5b36f44cce190dafc4ff605a532b7 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 28 Jan 2026 16:50:12 -0600 Subject: [PATCH] totp debugging --- lib/towerops/accounts.ex | 22 +++++++-- lib/towerops_web/live/device_live/show.ex | 36 +++++++++----- lib/towerops_web/live/graph_live/show.ex | 59 ++++++++++++++++------- 3 files changed, 84 insertions(+), 33 deletions(-) diff --git a/lib/towerops/accounts.ex b/lib/towerops/accounts.ex index bf1b19b2..74aad1ce 100644 --- a/lib/towerops/accounts.ex +++ b/lib/towerops/accounts.ex @@ -196,13 +196,29 @@ defmodule Towerops.Accounts do Verifies a TOTP code against a secret. Returns true if the code is valid within the time window, false otherwise. - Allows for +/- 1 time step (30 seconds) to handle clock drift. + Allows for +/- 4 time steps (120 seconds / 2 minutes) to handle clock drift. """ def verify_totp(secret, code) when is_binary(secret) and is_binary(code) do current_time = System.system_time(:second) - # NimbleTOTP.valid? by default checks current time +/- 1 time step (30s) - result = NimbleTOTP.valid?(secret, code) + # Check multiple time windows to handle clock drift + # Check ±4 time steps (±120 seconds / 2 minutes) + time_windows = [ + current_time, + current_time - 30, + current_time + 30, + current_time - 60, + current_time + 60, + current_time - 90, + current_time + 90, + current_time - 120, + current_time + 120 + ] + + result = + Enum.any?(time_windows, fn time -> + NimbleTOTP.valid?(secret, code, time: time) + end) if !result do require Logger diff --git a/lib/towerops_web/live/device_live/show.ex b/lib/towerops_web/live/device_live/show.ex index 417e58c9..43b99930 100644 --- a/lib/towerops_web/live/device_live/show.ex +++ b/lib/towerops_web/live/device_live/show.ex @@ -41,7 +41,9 @@ defmodule ToweropsWeb.DeviceLive.Show do @impl true def handle_params(%{"id" => id} = params, _, socket) do - # Check if device exists before setting up subscriptions + organization = socket.assigns.current_organization + + # Check if device exists and verify organization access case Devices.get_device(id) do nil -> {:noreply, @@ -49,19 +51,29 @@ defmodule ToweropsWeb.DeviceLive.Show do |> put_flash(:error, "Device not found") |> push_navigate(to: ~p"/devices")} - _device -> - _ = - if connected?(socket) do - _ = Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{id}") - Process.send_after(self(), :refresh_data, 10_000) - end + device -> + # Preload site and organization to check access + device = Repo.preload(device, site: :organization) - tab = Map.get(params, "tab", "overview") + if device.site.organization_id == organization.id do + _ = + if connected?(socket) do + _ = Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{id}") + Process.send_after(self(), :refresh_data, 10_000) + end - socket - |> load_equipment_data(id) - |> assign(:active_tab, tab) - |> then(&{:noreply, &1}) + tab = Map.get(params, "tab", "overview") + + socket + |> load_equipment_data(id) + |> assign(:active_tab, tab) + |> then(&{:noreply, &1}) + else + {:noreply, + socket + |> put_flash(:error, "You don't have access to this device") + |> push_navigate(to: ~p"/devices")} + end end end diff --git a/lib/towerops_web/live/graph_live/show.ex b/lib/towerops_web/live/graph_live/show.ex index ab2a267c..d8906b33 100644 --- a/lib/towerops_web/live/graph_live/show.ex +++ b/lib/towerops_web/live/graph_live/show.ex @@ -4,6 +4,7 @@ defmodule ToweropsWeb.GraphLive.Show do alias Towerops.Devices alias Towerops.Monitoring + alias Towerops.Repo alias Towerops.Snmp @impl true @@ -13,27 +14,49 @@ defmodule ToweropsWeb.GraphLive.Show do @impl true def handle_params(%{"id" => device_id, "sensor_type" => sensor_type} = params, _, socket) do - _ = - if connected?(socket) do - Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}") - end + organization = socket.assigns.current_organization - range = Map.get(params, "range", "24h") - interface_id = Map.get(params, "interface_id") - sensor_id = Map.get(params, "sensor_id") - storage_id = Map.get(params, "storage_id") + # Verify device exists and user has access + case Devices.get_device(device_id) do + nil -> + {:noreply, + socket + |> put_flash(:error, "Device not found") + |> push_navigate(to: ~p"/devices")} - socket = - socket - |> assign(:device_id, device_id) - |> assign(:sensor_type, sensor_type) - |> assign(:range, range) - |> assign(:interface_id, interface_id) - |> assign(:sensor_id, sensor_id) - |> assign(:storage_id, storage_id) - |> load_graph_data() + device -> + # Preload site and organization to check access + device = Repo.preload(device, site: :organization) - {:noreply, socket} + if device.site.organization_id == organization.id do + _ = + if connected?(socket) do + Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}") + end + + range = Map.get(params, "range", "24h") + interface_id = Map.get(params, "interface_id") + sensor_id = Map.get(params, "sensor_id") + storage_id = Map.get(params, "storage_id") + + socket = + socket + |> assign(:device_id, device_id) + |> assign(:sensor_type, sensor_type) + |> assign(:range, range) + |> assign(:interface_id, interface_id) + |> assign(:sensor_id, sensor_id) + |> assign(:storage_id, storage_id) + |> load_graph_data() + + {:noreply, socket} + else + {:noreply, + socket + |> put_flash(:error, "You don't have access to this device") + |> push_navigate(to: ~p"/devices")} + end + end end @impl true