| | const { encryptV3 } = require('@librechat/api'); |
| | const { logger } = require('@librechat/data-schemas'); |
| | const { |
| | verifyTOTP, |
| | getTOTPSecret, |
| | verifyBackupCode, |
| | generateTOTPSecret, |
| | generateBackupCodes, |
| | } = require('~/server/services/twoFactorService'); |
| | const { getUserById, updateUser } = require('~/models'); |
| |
|
| | const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, ''); |
| |
|
| | |
| | |
| | |
| | |
| | const enable2FA = async (req, res) => { |
| | try { |
| | const userId = req.user.id; |
| | const secret = generateTOTPSecret(); |
| | const { plainCodes, codeObjects } = await generateBackupCodes(); |
| |
|
| | |
| | const encryptedSecret = encryptV3(secret); |
| |
|
| | |
| | const user = await updateUser(userId, { |
| | totpSecret: encryptedSecret, |
| | backupCodes: codeObjects, |
| | twoFactorEnabled: false, |
| | }); |
| |
|
| | const otpauthUrl = `otpauth://totp/${safeAppTitle}:${user.email}?secret=${secret}&issuer=${safeAppTitle}`; |
| |
|
| | return res.status(200).json({ otpauthUrl, backupCodes: plainCodes }); |
| | } catch (err) { |
| | logger.error('[enable2FA]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | const verify2FA = async (req, res) => { |
| | try { |
| | const userId = req.user.id; |
| | const { token, backupCode } = req.body; |
| | const user = await getUserById(userId, '_id totpSecret backupCodes'); |
| |
|
| | if (!user || !user.totpSecret) { |
| | return res.status(400).json({ message: '2FA not initiated' }); |
| | } |
| |
|
| | const secret = await getTOTPSecret(user.totpSecret); |
| | let isVerified = false; |
| |
|
| | if (token) { |
| | isVerified = await verifyTOTP(secret, token); |
| | } else if (backupCode) { |
| | isVerified = await verifyBackupCode({ user, backupCode }); |
| | } |
| |
|
| | if (isVerified) { |
| | return res.status(200).json(); |
| | } |
| | return res.status(400).json({ message: 'Invalid token or backup code.' }); |
| | } catch (err) { |
| | logger.error('[verify2FA]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | const confirm2FA = async (req, res) => { |
| | try { |
| | const userId = req.user.id; |
| | const { token } = req.body; |
| | const user = await getUserById(userId, '_id totpSecret'); |
| |
|
| | if (!user || !user.totpSecret) { |
| | return res.status(400).json({ message: '2FA not initiated' }); |
| | } |
| |
|
| | const secret = await getTOTPSecret(user.totpSecret); |
| | if (await verifyTOTP(secret, token)) { |
| | await updateUser(userId, { twoFactorEnabled: true }); |
| | return res.status(200).json(); |
| | } |
| | return res.status(400).json({ message: 'Invalid token.' }); |
| | } catch (err) { |
| | logger.error('[confirm2FA]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | const disable2FA = async (req, res) => { |
| | try { |
| | const userId = req.user.id; |
| | const { token, backupCode } = req.body; |
| | const user = await getUserById(userId, '_id totpSecret backupCodes'); |
| |
|
| | if (!user || !user.totpSecret) { |
| | return res.status(400).json({ message: '2FA is not setup for this user' }); |
| | } |
| |
|
| | if (user.twoFactorEnabled) { |
| | const secret = await getTOTPSecret(user.totpSecret); |
| | let isVerified = false; |
| |
|
| | if (token) { |
| | isVerified = await verifyTOTP(secret, token); |
| | } else if (backupCode) { |
| | isVerified = await verifyBackupCode({ user, backupCode }); |
| | } else { |
| | return res |
| | .status(400) |
| | .json({ message: 'Either token or backup code is required to disable 2FA' }); |
| | } |
| |
|
| | if (!isVerified) { |
| | return res.status(401).json({ message: 'Invalid token or backup code' }); |
| | } |
| | } |
| | await updateUser(userId, { totpSecret: null, backupCodes: [], twoFactorEnabled: false }); |
| | return res.status(200).json(); |
| | } catch (err) { |
| | logger.error('[disable2FA]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | const regenerateBackupCodes = async (req, res) => { |
| | try { |
| | const userId = req.user.id; |
| | const { plainCodes, codeObjects } = await generateBackupCodes(); |
| | await updateUser(userId, { backupCodes: codeObjects }); |
| | return res.status(200).json({ |
| | backupCodes: plainCodes, |
| | backupCodesHash: codeObjects, |
| | }); |
| | } catch (err) { |
| | logger.error('[regenerateBackupCodes]', err); |
| | return res.status(500).json({ message: err.message }); |
| | } |
| | }; |
| |
|
| | module.exports = { |
| | enable2FA, |
| | verify2FA, |
| | confirm2FA, |
| | disable2FA, |
| | regenerateBackupCodes, |
| | }; |
| |
|