mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 01:40:15 +01:00
🔏 fix: Enhance Two-Factor Authentication (#6247)
* 🌟 feat: Implement Two-Factor Authentication (2FA) functionality * fix: Two-Factor Authentication Logic and State Management * 🌟 feat: Add LICENSE file and update package version to 0.0.2 with MIT license
This commit is contained in:
parent
cc661c95ee
commit
3e3dfe5bad
15 changed files with 179 additions and 29 deletions
|
|
@ -22,7 +22,7 @@ function Account() {
|
|||
<div className="pb-3">
|
||||
<EnableTwoFactorItem />
|
||||
</div>
|
||||
{Array.isArray(user.user?.backupCodes) && user.user?.backupCodes.length > 0 && (
|
||||
{user?.user?.twoFactorEnabled && (
|
||||
<div className="pb-3">
|
||||
<BackupCodesItem />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { LockIcon, UnlockIcon } from 'lucide-react';
|
||||
// import { motion } from 'framer-motion';
|
||||
// import { LockIcon, UnlockIcon } from 'lucide-react';
|
||||
import { Label, Button } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
const [backupCodes, setBackupCodes] = useState<string[]>([]);
|
||||
const [isDialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [verificationToken, setVerificationToken] = useState<string>('');
|
||||
const [phase, setPhase] = useState<Phase>(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup');
|
||||
const [phase, setPhase] = useState<Phase>(user?.twoFactorEnabled ? 'disable' : 'setup');
|
||||
|
||||
const { mutate: confirm2FAMutate } = useConfirmTwoFactorMutation();
|
||||
const { mutate: enable2FAMutate, isLoading: isGenerating } = useEnableTwoFactorMutation();
|
||||
|
|
@ -56,7 +56,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
const currentStep = steps.indexOf(phasesLabel[phase]);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
if (Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && otpauthUrl) {
|
||||
if (user?.twoFactorEnabled && otpauthUrl) {
|
||||
disable2FAMutate(undefined, {
|
||||
onError: () =>
|
||||
showToast({ message: localize('com_ui_2fa_disable_error'), status: 'error' }),
|
||||
|
|
@ -68,7 +68,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
setBackupCodes([]);
|
||||
setVerificationToken('');
|
||||
setDisableToken('');
|
||||
setPhase(Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? 'disable' : 'setup');
|
||||
setPhase(user?.twoFactorEnabled ? 'disable' : 'setup');
|
||||
setDownloaded(false);
|
||||
}, [user, otpauthUrl, disable2FAMutate, localize, showToast]);
|
||||
|
||||
|
|
@ -136,6 +136,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
used: false,
|
||||
usedAt: null,
|
||||
})),
|
||||
twoFactorEnabled: true,
|
||||
}) as TUser,
|
||||
);
|
||||
}, [setUser, localize, showToast, backupCodes]);
|
||||
|
|
@ -171,6 +172,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
...prev,
|
||||
totpSecret: '',
|
||||
backupCodes: [],
|
||||
twoFactorEnabled: false,
|
||||
}) as TUser,
|
||||
);
|
||||
setPhase('setup');
|
||||
|
|
@ -183,7 +185,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
onError: () => showToast({ message: localize('com_ui_2fa_invalid'), status: 'error' }),
|
||||
});
|
||||
},
|
||||
[disableToken, verify2FAMutate, disable2FAMutate, showToast, localize, setUser],
|
||||
[verify2FAMutate, disable2FAMutate, showToast, localize, setUser],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -197,7 +199,7 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
}}
|
||||
>
|
||||
<DisableTwoFactorToggle
|
||||
enabled={Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0}
|
||||
enabled={!!user?.twoFactorEnabled}
|
||||
onChange={() => setDialogOpen(true)}
|
||||
disabled={isVerifying || isDisabling || isGenerating}
|
||||
/>
|
||||
|
|
@ -215,9 +217,11 @@ const TwoFactorAuthentication: React.FC = () => {
|
|||
<OGDialogHeader>
|
||||
<OGDialogTitle className="mb-2 flex items-center gap-3 text-2xl font-bold">
|
||||
<SmartphoneIcon className="h-6 w-6 text-primary" />
|
||||
{Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 ? localize('com_ui_2fa_disable') : localize('com_ui_2fa_setup')}
|
||||
{user?.twoFactorEnabled
|
||||
? localize('com_ui_2fa_disable')
|
||||
: localize('com_ui_2fa_setup')}
|
||||
</OGDialogTitle>
|
||||
{Array.isArray(user?.backupCodes) && user?.backupCodes.length > 0 && phase !== 'disable' && (
|
||||
{user?.twoFactorEnabled && phase !== 'disable' && (
|
||||
<div className="mt-4 space-y-3">
|
||||
<Progress
|
||||
value={(steps.indexOf(phasesLabel[phase]) / (steps.length - 1)) * 100}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export const DisablePhase: React.FC<DisablePhaseProps> = ({ onDisable, isDisabli
|
|||
disabled={isDisabling || token.length !== (useBackup ? 8 : 6)}
|
||||
className="w-full rounded-xl px-6 py-3 transition-all disabled:opacity-50"
|
||||
>
|
||||
{isDisabling === true && <Spinner className="mr-2" />}
|
||||
{isDisabling && <Spinner className="mr-2" />}
|
||||
{isDisabling ? localize('com_ui_disabling') : localize('com_ui_2fa_disable')}
|
||||
</Button>
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface SetupPhaseProps {
|
|||
onGenerate: () => void;
|
||||
}
|
||||
|
||||
export const SetupPhase: React.FC<SetupPhaseProps> = ({ isGenerating, onGenerate, onNext }) => {
|
||||
export const SetupPhase: React.FC<SetupPhaseProps> = ({ isGenerating, onGenerate }) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue