2FA Quick Reference - nself-org/nchat GitHub Wiki
Implementation Date: January 30, 2026 Version: v0.3.0
.backend/migrations/012_2fa_system.sql
src/lib/2fa/totp.ts # TOTP generation/verification
src/lib/2fa/backup-codes.ts # Backup code management
src/lib/2fa/device-fingerprint.ts # Device trust system
src/app/api/auth/2fa/setup/route.ts
src/app/api/auth/2fa/verify-setup/route.ts
src/app/api/auth/2fa/verify/route.ts
src/app/api/auth/2fa/disable/route.ts
src/app/api/auth/2fa/backup-codes/route.ts
src/app/api/auth/2fa/status/route.ts
src/app/api/auth/2fa/trusted-devices/route.ts
src/components/auth/TwoFactorVerify.tsx # Login verification modal
src/components/settings/TwoFactorSettings.tsx (existing, updated)
src/hooks/use-2fa.ts # React hook for 2FA operations
cd .backend
nself db migrate up# Start services
pnpm backend:start
pnpm dev
# Navigate to:
http://localhost:3000/settings/security-- Check if user has 2FA enabled
SELECT user_has_2fa_enabled('user-uuid');
-- Count active 2FA users
SELECT get_2fa_active_users_count();
-- Check backup codes remaining
SELECT count_remaining_backup_codes('user-uuid');
-- Check if device is trusted
SELECT is_device_trusted('user-uuid', 'device-hash');POST /api/auth/2fa/setup
Body: { userId: string, email: string }
Response: {
success: true,
data: {
secret: string,
qrCodeDataUrl: string,
backupCodes: string[],
manualEntryCode: string
}
}POST /api/auth/2fa/verify-setup
Body: {
userId: string,
secret: string,
code: string,
backupCodes: string[]
}
Response: { success: true }POST /api/auth/2fa/verify
Body: {
userId: string,
code: string,
rememberDevice: boolean
}
Response: {
success: true,
usedBackupCode: boolean
}GET /api/auth/2fa/status?userId={userId}
Response: {
success: true,
data: {
isEnabled: boolean,
backupCodes: { total, unused, used },
trustedDevices: Device[]
}
}import { use2FA } from '@/hooks/use-2fa'
function Component() {
const {
status, // Current 2FA status
setupData, // Setup data (QR, codes, etc.)
loading, // Loading state
error, // Error message
startSetup, // Start setup flow
verifyAndEnable, // Verify and enable 2FA
disable, // Disable 2FA
regenerateBackupCodes, // Get new backup codes
removeTrustedDevice, // Remove trusted device
refreshStatus, // Refresh status
} = use2FA()
// Start setup
const handleSetup = async () => {
const result = await startSetup()
if (result.success) {
// Show QR: setupData.qrCodeDataUrl
// Show codes: setupData.backupCodes
}
}
// Verify code
const handleVerify = async (code: string) => {
const result = await verifyAndEnable(code)
if (result.success) {
// 2FA enabled!
}
}
return (
<div>
{status?.isEnabled ? 'Enabled' : 'Disabled'}
<button onClick={handleSetup}>Enable 2FA</button>
</div>
)
}import { TwoFactorVerify } from '@/components/auth/TwoFactorVerify'
function LoginPage() {
const [showTwoFactor, setShowTwoFactor] = useState(false)
const [userId, setUserId] = useState('')
return (
<TwoFactorVerify
open={showTwoFactor}
userId={userId}
onVerified={(rememberDevice) => {
// Complete login
}}
onCancel={() => setShowTwoFactor(false)}
/>
)
}nchat_user_2fa_settings (
id, user_id, secret, is_enabled,
enabled_at, last_used_at, created_at, updated_at
)
nchat_user_backup_codes (
id, user_id, code_hash, used_at, created_at
)
nchat_user_trusted_devices (
id, user_id, device_id, device_name, device_info,
trusted_until, last_used_at, created_at
)
nchat_2fa_verification_attempts (
id, user_id, ip_address, user_agent,
success, attempt_type, created_at
)-
idx_2fa_settings_user_id- Fast user lookups -
idx_backup_codes_user_id- Backup code queries -
idx_trusted_devices_user_id- Device trust checks -
idx_2fa_attempts_user_time- Rate limiting
- TOTP secrets stored securely
- Backup codes hashed with bcrypt
- Device fingerprints hashed (SHA-256)
- Rate limiting (5 attempts / 15 min)
- Verification attempts logged
- RLS policies enabled
- Single-use backup codes
- 30-day device trust expiry
- TODO: Encrypt secrets at rest
- TODO: Password verification before disable
Solution: Use manual entry code: setupData.manualEntryCode
Solution:
- Check server time (NTP sync)
- Use backup code instead
- Wait for next 30-second window
Solution:
- Check localStorage enabled
- Verify device fingerprint consistent
- Check trusted_until not expired
-
Setup Flow
- Navigate to Settings → Security
- Click "Enable 2FA"
- Scan QR code with Google Authenticator
- Enter 6-digit code
- Save backup codes
-
Login Flow
- Log out
- Log in with email/password
- Enter TOTP code
- Check "Remember this device"
- Verify login successful
-
Backup Code Flow
- Log out
- Log in with email/password
- Click "Use backup code instead"
- Enter backup code (XXXX-XXXX)
- Verify login successful
- Check code marked as used
-
Device Trust
- Log in with "Remember device" checked
- Log out and log in again
- Verify 2FA skipped (device trusted)
-
Disable Flow
- Navigate to Settings → Security
- Click "Disable 2FA"
- Confirm action
- Verify 2FA disabled
SELECT
COUNT(*) FILTER (WHERE is_enabled) as enabled_users,
COUNT(*) as total_users,
ROUND(100.0 * COUNT(*) FILTER (WHERE is_enabled) / COUNT(*), 2) as adoption_rate
FROM nchat_user_2fa_settings;SELECT
created_at,
success,
attempt_type,
ip_address
FROM nchat_2fa_verification_attempts
ORDER BY created_at DESC
LIMIT 20;SELECT
u.email,
count_remaining_backup_codes(u.id) as remaining_codes
FROM auth.users u
JOIN nchat_user_2fa_settings s ON s.user_id = u.id
WHERE s.is_enabled = true
HAVING count_remaining_backup_codes(u.id) <= 3
ORDER BY remaining_codes;- Full Implementation:
docs/2FA-Implementation-Summary.md - This Quick Reference:
docs/2FA-Quick-Reference.md
Last Updated: January 30, 2026 Status: Production Ready ✓