Runbook: Admin account requires 2FA and I don't have the TOTP secret¶
Symptom¶
You're trying to sign into an admin account (via ebit-admin-fe at http://localhost:3001 or the Swagger UI at http://localhost:4000/swagger) but the account has MFA enabled and you don't have the original TOTP authenticator setup.
Likely cause¶
The admin user was created with mfaEnabled: true in the database (either by seed or manual setup) and the TOTP secret was registered in an authenticator app that's no longer accessible.
Diagnosis¶
# Check if the user has MFA enabled and retrieve the secret
sudo docker exec ebit-db psql -U ebit -d ebit -c \
"SELECT email, \"mfaEnabled\", length(\"mfaSecret\") as secret_len FROM \"User\" WHERE role = 'SuperAdmin' OR role = 'Admin';"
Fix¶
Option 1: Generate a TOTP code from the database secret¶
# Extract the MFA secret
sudo docker exec ebit-db psql -U ebit -d ebit -t -c \
"SELECT \"mfaSecret\" FROM \"User\" WHERE email = 'admin@admin.com';"
# Generate a TOTP code using oathtool (install: apt install oathtool)
oathtool --totp --base32 <mfaSecret>
# Use this 6-digit code within 30 seconds
If oathtool isn't available:
# Generate via Node.js
node -e "
const { authenticator } = require('otplib');
const secret = '<mfaSecret>';
console.log(authenticator.generate(secret));
"
Option 2: Disable MFA for the account¶
sudo docker exec ebit-db psql -U ebit -d ebit -c \
"UPDATE \"User\" SET \"mfaEnabled\" = false, \"mfaSecret\" = NULL WHERE email = 'admin@admin.com';"
Option 3: Use the E2E admin sign-in helper¶
The adminSignIn helper in the E2E test suite handles MFA automatically by reading the secret from the database. If you're writing tests, use this helper rather than manually managing TOTP codes.
Option 4: SuperAdmin short-circuit¶
SuperAdmin accounts may have a simplified auth path depending on the auth controller configuration. Check apps/api/src/auth/auth.controller.ts for any role-based MFA bypass logic.
Verification¶
# Sign in without MFA
curl -s -X POST http://localhost:4000/auth/login \
-H "Content-Type: application/json" \
-H "x-captcha-token: pass" \
-d '{"email":"admin@admin.com","password":"admin"}' \
| python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'accessToken' in d else f'MFA still required: {d}')"
Prevention¶
- Store the TOTP secret in a team password manager when setting up admin 2FA
- The seed script (
libs/_prisma/src/seed/admin.ts) creates the admin user — check whether it enables MFA by default - For local dev, consider seeding admin accounts with
mfaEnabled: falseto avoid this issue entirely