security fixes

This commit is contained in:
Graham McIntire 2026-01-28 17:18:23 -06:00
parent 6ab1333b41
commit 4364d81416
3 changed files with 108 additions and 12 deletions

21
check_prod_time.sh Executable file
View file

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

52
diagnose_totp_secret.sh Executable file
View file

@ -0,0 +1,52 @@
#!/bin/bash
# Diagnose TOTP secret for a specific user
if [ -z "$1" ]; then
echo "Usage: ./diagnose_totp_secret.sh <user_email>"
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
"

View file

@ -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
</div>
</div>
<div class="mt-8 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div class="flex">
<.icon
name="hero-information-circle"
class="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5"
/>
<div class="ml-3">
<p class="text-sm text-blue-900 dark:text-blue-100">
<strong>Keep your device safe:</strong>
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.
</p>
<div class="mt-8 space-y-4">
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
<div class="flex">
<.icon
name="hero-exclamation-triangle"
class="h-5 w-5 text-yellow-600 dark:text-yellow-400 mt-0.5 flex-shrink-0"
/>
<div class="ml-3">
<p class="text-sm text-yellow-900 dark:text-yellow-100 font-semibold">
Important: Do not refresh this page!
</p>
<p class="text-sm text-yellow-900 dark:text-yellow-100 mt-1">
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.
</p>
</div>
</div>
</div>
<div class="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
<div class="flex">
<.icon
name="hero-information-circle"
class="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0"
/>
<div class="ml-3">
<p class="text-sm text-blue-900 dark:text-blue-100">
<strong>Keep your device safe:</strong>
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.
</p>
</div>
</div>
</div>
</div>