Skip to content

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: false to avoid this issue entirely