towerops/search_totp_code.exs
2026-01-29 09:14:48 -06:00

61 lines
2 KiB
Elixir

alias Towerops.Accounts
# The code from the error
provided_code = "147273"
server_time = 1_769_699_437
IO.puts("=== Searching for when code #{provided_code} would be valid ===")
IO.puts("Server time: #{DateTime.from_unix!(server_time)}")
IO.puts("")
# Generate a fresh secret to test with
secret = Accounts.generate_totp_secret()
IO.puts("Testing with a fresh 20-byte secret...")
IO.puts("")
# Search ±1 hour (120 time steps of 30 seconds each)
found =
Enum.find_value(-120..120, fn offset ->
test_time = server_time + offset * 30
expected = NimbleTOTP.verification_code(secret, time: test_time)
if expected == provided_code do
%{
offset_seconds: offset * 30,
offset_minutes: Float.round(offset * 30 / 60, 1),
time: DateTime.from_unix!(test_time)
}
end
end)
case found do
nil ->
IO.puts("❌ Code #{provided_code} would NEVER be valid for a fresh 20-byte secret")
IO.puts("")
IO.puts("This means:")
IO.puts(" 1. Your authenticator app has a DIFFERENT secret than the server")
IO.puts(" 2. You need to:")
IO.puts(" - Delete the Towerops entry from your authenticator app")
IO.puts(" - Visit the enrollment page again (fresh)")
IO.puts(" - Scan the NEW QR code")
IO.puts(" - Enter the code immediately")
match ->
IO.puts("✓ Code #{provided_code} would be valid at:")
IO.puts(" Time: #{match.time}")
IO.puts(" Offset: #{match.offset_seconds} seconds (#{match.offset_minutes} minutes)")
IO.puts("")
cond do
abs(match.offset_seconds) <= 120 ->
IO.puts("⚠️ This is within the ±2 minute window - should have worked!")
abs(match.offset_seconds) <= 3600 ->
IO.puts("⏰ Clock drift: #{abs(match.offset_minutes)} minutes")
IO.puts(" Your device's clock is incorrect")
true ->
IO.puts("⏰ Extreme time difference: #{abs(match.offset_minutes)} minutes")
IO.puts(" Your device's clock is way off")
end
end