- Fix create_test_user.exs to use create_totp_device instead of create_user_credential - Update TOTP_SETUP.md to reference UserTotpDevice schema and user_totp_devices table - Fix function signatures to match Accounts.create_totp_device(user_id, device_name, secret) - Add terms_of_service_consent and privacy_policy_consent to user registration
5.8 KiB
TOTP Setup for E2E Tests
The e2e tests need to authenticate using TOTP (Time-based One-Time Password). This guide explains how to set up a test user with TOTP enabled.
How It Works
The tests use the otplib library to generate TOTP codes programmatically from a shared secret. This is the same secret that Google Authenticator or similar apps use.
import { authenticator } from 'otplib';
// Generate a valid 6-digit code from the secret
const token = authenticator.generate(totpSecret);
Option 1: Create Test User with Known Secret (Recommended)
This is the easiest approach - you create a test user with a known TOTP secret from the start.
Step 1: Run the Helper Script
From the Phoenix app directory:
cd .. # Go to towerops-web root
mix run e2e/scripts/create_test_user.exs
This will:
- Create a test user:
e2e-test@towerops.local - Set up TOTP with a known secret
- Create an organization for testing
- Output the credentials you need
Step 2: Copy Credentials to .env
The script outputs something like:
TEST_USER_EMAIL=e2e-test@towerops.local
TEST_USER_PASSWORD=TestPassword123!
TEST_USER_TOTP_SECRET=JBSWY3DPEHPK3PXP
Copy these to your e2e/.env file.
Step 3: Run Tests
npm test
Option 2: Use Existing User and Extract TOTP Secret
If you already have a test user with TOTP enabled, you can extract the secret.
Get Secret from Database
Via IEx:
# Start console
iex -S mix
# Get the TOTP secret
user = Towerops.Accounts.get_user_by_email("your-test@example.com")
device = Towerops.Repo.get_by(Towerops.Accounts.UserTotpDevice, user_id: user.id)
IO.puts("TOTP Secret: #{device.totp_secret}")
Via SQL:
SELECT u.email, utd.totp_secret
FROM users u
JOIN user_totp_devices utd ON utd.user_id = u.id
WHERE u.email = 'your-test@example.com';
Add to .env
TEST_USER_EMAIL=your-test@example.com
TEST_USER_PASSWORD=YourPassword123!
TEST_USER_TOTP_SECRET=<secret-from-above>
Option 3: Create Test User Manually
If you prefer to create the test user manually in IEx:
# Start console
cd .. # Go to towerops-web root
iex -S mix
# Create test user
alias Towerops.{Accounts, Organizations, Repo}
{:ok, user} = Accounts.register_user(%{
email: "e2e-test@towerops.local",
password: "TestPassword123!",
password_confirmation: "TestPassword123!"
})
# Set a known TOTP secret
totp_secret = "JBSWY3DPEHPK3PXP"
{:ok, _device, _secret} = Accounts.create_totp_device(user.id, "E2E Test Device", totp_secret)
# Create an organization
{:ok, _org} = Organizations.create_organization(%{
name: "E2E Test Org"
}, user.id)
# Print credentials
IO.puts("\n=== E2E Test User Created ===")
IO.puts("Email: e2e-test@towerops.local")
IO.puts("Password: TestPassword123!")
IO.puts("TOTP Secret: #{totp_secret}")
IO.puts("\nAdd these to e2e/.env:")
IO.puts("TEST_USER_EMAIL=e2e-test@towerops.local")
IO.puts("TEST_USER_PASSWORD=TestPassword123!")
IO.puts("TEST_USER_TOTP_SECRET=#{totp_secret}")
Option 4: Disable TOTP (Not Recommended)
If you want to skip TOTP entirely for testing (not recommended for production-like testing):
1. Disable TOTP for Test User
user = Accounts.get_user_by_email("test@example.com")
device = Repo.get_by(Accounts.UserTotpDevice, user_id: user.id)
if device, do: Repo.delete(device)
2. Update Auth Setup
Edit tests/auth.setup.ts to skip TOTP:
// Remove or comment out TOTP section
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
// Skip TOTP - go straight to dashboard
await page.waitForURL('**/orgs');
3. Remove TOTP_SECRET requirement
const totpSecret = process.env.TEST_USER_TOTP_SECRET;
// Remove the error check
Troubleshooting
"Invalid authentication code" errors
Cause: The TOTP secret doesn't match what's in the database, or there's clock skew.
Solution:
- Verify the secret matches:
SELECT totp_secret FROM user_credentials WHERE user_id = '...' - Check system time is correct:
date - Try generating a code manually to test:
node -e "console.log(require('otplib').authenticator.generate('YOUR_SECRET'))"
"User not found" or login fails
Cause: Test user doesn't exist or credentials are wrong.
Solution:
- Verify user exists:
SELECT * FROM users WHERE email = 'e2e-test@towerops.local' - Check password in
.envmatches what you set - Try logging in manually via the web UI to confirm credentials
"No organization found"
Cause: Test user doesn't belong to any organization.
Solution:
user = Accounts.get_user_by_email("e2e-test@towerops.local")
{:ok, _org} = Organizations.create_organization(%{name: "E2E Test Org"}, user.id)
Tests fail at TOTP step
Cause: TOTP element not found or timing issue.
Solution:
- Verify TOTP is enabled for the user
- Check the selector in
auth.setup.tsmatches your login form - Add more specific selectors or waiting logic
Security Notes
- Never commit
.envfile - It's in.gitignorefor a reason - Use dedicated test accounts - Don't reuse production credentials
- Rotate secrets regularly - Especially for staging environments
- Limit test user permissions - Only give access to test organizations
Common TOTP Secrets for Testing
These are publicly known test secrets, safe for local development:
JBSWY3DPEHPK3PXP # "Hello!" in Base32
GEZDGNBVGY3TQOJQ # "12345678" in Base32
Never use these in production!
Advanced: Custom TOTP Secret
To generate your own TOTP secret:
# In IEx
:crypto.strong_rand_bytes(20) |> Base.encode32(padding: false)
Or in bash:
node -e "console.log(require('otplib').authenticator.generateSecret())"