totp debugging

This commit is contained in:
Graham McIntire 2026-01-28 16:50:12 -06:00
parent 27c8a2324a
commit fd4f8b2342
3 changed files with 84 additions and 33 deletions

View file

@ -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

View file

@ -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

View file

@ -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