- 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
232 lines
5.8 KiB
Markdown
232 lines
5.8 KiB
Markdown
# 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=<secret-from-above>
|
|
```
|
|
|
|
## 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())"
|
|
```
|