# 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. ```typescript 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: ```bash 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: ```bash 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 ```bash 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:** ```elixir # 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:** ```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 ```bash TEST_USER_EMAIL=your-test@example.com TEST_USER_PASSWORD=YourPassword123! TEST_USER_TOTP_SECRET= ``` ## Option 3: Create Test User Manually If you prefer to create the test user manually in IEx: ```elixir # 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 ```elixir 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: ```typescript // 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 ```typescript 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:** 1. Verify the secret matches: `SELECT totp_secret FROM user_credentials WHERE user_id = '...'` 2. Check system time is correct: `date` 3. Try generating a code manually to test: ```bash 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:** 1. Verify user exists: `SELECT * FROM users WHERE email = 'e2e-test@towerops.local'` 2. Check password in `.env` matches what you set 3. Try logging in manually via the web UI to confirm credentials ### "No organization found" **Cause:** Test user doesn't belong to any organization. **Solution:** ```elixir 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:** 1. Verify TOTP is enabled for the user 2. Check the selector in `auth.setup.ts` matches your login form 3. Add more specific selectors or waiting logic ## Security Notes - **Never commit `.env` file** - It's in `.gitignore` for 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: ```elixir # In IEx :crypto.strong_rand_bytes(20) |> Base.encode32(padding: false) ``` Or in bash: ```bash node -e "console.log(require('otplib').authenticator.generateSecret())" ```