diff --git a/check_prod_time.sh b/check_prod_time.sh new file mode 100755 index 00000000..54ad908a --- /dev/null +++ b/check_prod_time.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Quick script to check production server time via Elixir remote console + +echo "=== Checking Production Server Time ===" +echo "" +echo "Running remote Elixir command..." +echo "" + +kubectl exec -it -n towerops deployment/towerops -- bin/towerops rpc " +IO.puts(\"System.system_time(:second): #{System.system_time(:second)}\") +IO.puts(\"DateTime.utc_now(): #{DateTime.utc_now()}\") +IO.puts(\"\") +IO.puts(\"Expected UTC now: #{DateTime.from_unix!(System.system_time(:second))}\") +" + +echo "" +echo "=== Current UTC Time (from local machine) ===" +date -u +echo "" +echo "Compare the times above. If they differ by more than a few seconds," +echo "the production server's clock is out of sync." diff --git a/diagnose_totp_secret.sh b/diagnose_totp_secret.sh new file mode 100755 index 00000000..91a932a1 --- /dev/null +++ b/diagnose_totp_secret.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Diagnose TOTP secret for a specific user + +if [ -z "$1" ]; then + echo "Usage: ./diagnose_totp_secret.sh " + exit 1 +fi + +USER_EMAIL="$1" + +echo "=== TOTP Secret Diagnostic ===" +echo "User: $USER_EMAIL" +echo "" + +kubectl exec -n towerops deployment/towerops -- bin/towerops rpc " +alias Towerops.Accounts +require Logger + +user = Accounts.get_user_by_email(\"$USER_EMAIL\") + +if is_nil(user) do + IO.puts(\"❌ User not found\") +else + IO.puts(\"✓ User found: #{user.email}\") + + if is_nil(user.totp_secret) do + IO.puts(\"❌ TOTP not enabled for this user\") + else + IO.puts(\"✓ TOTP enabled\") + IO.puts(\"Secret length: #{byte_size(user.totp_secret)} bytes\") + + # Check if secret is valid Base32 + try do + # NimbleTOTP expects the secret to be base32-encoded + # Let's generate a code to verify the secret is valid + current_time = System.system_time(:second) + code = NimbleTOTP.verification_code(user.totp_secret, time: current_time) + + IO.puts(\"✓ Secret is valid (can generate codes)\") + IO.puts(\"\") + IO.puts(\"Current expected codes:\") + IO.puts(\" Current: #{code}\") + IO.puts(\" Prev (-30s): #{NimbleTOTP.verification_code(user.totp_secret, time: current_time - 30)}\") + IO.puts(\" Next (+30s): #{NimbleTOTP.verification_code(user.totp_secret, time: current_time + 30)}\") + IO.puts(\"\") + IO.puts(\"Server time: #{DateTime.from_unix!(current_time)}\") + rescue + e -> IO.puts(\"❌ Secret is INVALID: #{inspect(e)}\") + end + end +end +" diff --git a/lib/towerops_web/live/account_live/totp_enrollment.ex b/lib/towerops_web/live/account_live/totp_enrollment.ex index 183cfd13..86764110 100644 --- a/lib/towerops_web/live/account_live/totp_enrollment.ex +++ b/lib/towerops_web/live/account_live/totp_enrollment.ex @@ -16,6 +16,8 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do if Accounts.totp_enabled?(user) do {:ok, redirect(socket, to: ~p"/devices")} else + # Generate secret once and store in process state + # If user refreshes, they'll get a NEW secret - they must scan the LATEST QR code secret = Accounts.generate_totp_secret() qr_code = Accounts.generate_totp_qr_code(user, secret) @@ -196,18 +198,39 @@ defmodule ToweropsWeb.AccountLive.TotpEnrollment do -
-
- <.icon - name="hero-information-circle" - class="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5" - /> -
-

- Keep your device safe: - You'll need to enter a code from your authenticator app each time you log in. - Make sure to keep your phone secure and backed up. -

+
+
+
+ <.icon + name="hero-exclamation-triangle" + class="h-5 w-5 text-yellow-600 dark:text-yellow-400 mt-0.5 flex-shrink-0" + /> +
+

+ Important: Do not refresh this page! +

+

+ Each time you refresh, a new QR code is generated. You must scan the current QR code + and enter the verification code immediately without refreshing. + If you refresh by accident, you must scan the NEW QR code again. +

+
+
+
+ +
+
+ <.icon + name="hero-information-circle" + class="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" + /> +
+

+ Keep your device safe: + You'll need to enter a code from your authenticator app each time you log in. + Make sure to keep your phone secure and backed up. +

+