godcrm/backend/routes/v3/auth/twoFactor.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

130 lines
4 KiB
JavaScript

/**
* Two-Factor Authentication routes: /2fa/setup, /2fa/verify, /2fa (DELETE)
*/
import {
respondSuccess, respondError,
requireAuth,
dbGet, dbRun, authLogger
} from './authShared.js';
/**
* @param {import('express').Router} router
*/
export default function registerTwoFactorRoutes(router) {
// POST /api/v2/auth/2fa/setup - Setup 2FA (generate QR code)
router.post('/2fa/setup', requireAuth, async (req, res) => {
try {
const speakeasy = await import('speakeasy');
const QRCode = await import('qrcode');
const user = await dbGet('SELECT email FROM users WHERE id = ?', [req.user.id]);
const secret = speakeasy.default.generateSecret({
name: `CRM (${user.email})`,
issuer: 'Business CRM'
});
// Store secret temporarily (not enabled yet)
await dbRun(
'UPDATE users SET totp_secret = ? WHERE id = ?',
[secret.base32, req.user.id]
);
const qrCode = await QRCode.default.toDataURL(secret.otpauth_url);
return respondSuccess(res, {
secret: secret.base32,
qrCode
});
} catch (error) {
authLogger.error({ err: error }, '2FA setup error:', error);
return respondError(res, 500, '2FA_SETUP_FAILED', 'Failed to setup 2FA');
}
});
// POST /api/v2/auth/2fa/verify - Verify and enable 2FA
router.post('/2fa/verify', requireAuth, async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return respondError(res, 400, 'CODE_REQUIRED', 'Verification code is required');
}
const speakeasy = await import('speakeasy');
const user = await dbGet('SELECT totp_secret FROM users WHERE id = ?', [req.user.id]);
if (!user.totp_secret) {
return respondError(res, 400, 'SETUP_REQUIRED', 'Please setup 2FA first');
}
const verified = speakeasy.default.totp.verify({
secret: user.totp_secret,
encoding: 'base32',
token: code,
window: 1
});
if (!verified) {
return respondError(res, 400, 'INVALID_CODE', 'Invalid verification code');
}
await dbRun('UPDATE users SET totp_enabled = 1 WHERE id = ?', [req.user.id]);
return respondSuccess(res, { enabled: true, message: '2FA enabled successfully' });
} catch (error) {
authLogger.error({ err: error }, '2FA verify error:', error);
return respondError(res, 500, '2FA_VERIFY_FAILED', 'Failed to verify 2FA');
}
});
// DELETE /api/v2/auth/2fa - Disable 2FA
router.delete('/2fa', requireAuth, async (req, res) => {
try {
const { password, code } = req.body;
if (!password) {
return respondError(res, 400, 'PASSWORD_REQUIRED', 'Password is required to disable 2FA');
}
const bcrypt = await import('bcrypt');
const speakeasy = await import('speakeasy');
const user = await dbGet('SELECT password_hash, totp_secret, totp_enabled FROM users WHERE id = ?', [req.user.id]);
if (!user.totp_enabled) {
return respondError(res, 400, 'NOT_ENABLED', '2FA is not enabled');
}
// Verify password
const isValidPassword = await bcrypt.default.compare(password, user.password_hash);
if (!isValidPassword) {
return respondError(res, 401, 'INVALID_PASSWORD', 'Password is incorrect');
}
// Optionally verify TOTP code if provided
if (code) {
const verified = speakeasy.default.totp.verify({
secret: user.totp_secret,
encoding: 'base32',
token: code,
window: 1
});
if (!verified) {
return respondError(res, 400, 'INVALID_CODE', 'Invalid verification code');
}
}
await dbRun('UPDATE users SET totp_enabled = 0, totp_secret = NULL WHERE id = ?', [req.user.id]);
return respondSuccess(res, { enabled: false, message: '2FA disabled successfully' });
} catch (error) {
authLogger.error({ err: error }, '2FA disable error:', error);
return respondError(res, 500, '2FA_DISABLE_FAILED', 'Failed to disable 2FA');
}
});
}