/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import {Plural, Trans, useLingui} from '@lingui/react/macro'; import {observer} from 'mobx-react-lite'; import type React from 'react'; import * as ModalActionCreators from '~/actions/ModalActionCreators'; import {modal} from '~/actions/ModalActionCreators'; import * as UserActionCreators from '~/actions/UserActionCreators'; import {BackupCodesViewModal} from '~/components/modals/BackupCodesViewModal'; import {openClaimAccountModal} from '~/components/modals/ClaimAccountModal'; import {ConfirmModal} from '~/components/modals/ConfirmModal'; import {MfaTotpDisableModal} from '~/components/modals/MfaTotpDisableModal'; import {MfaTotpEnableModal} from '~/components/modals/MfaTotpEnableModal'; import {PasskeyNameModal} from '~/components/modals/PasskeyNameModal'; import {PhoneAddModal} from '~/components/modals/PhoneAddModal'; import {SettingsTabSection} from '~/components/modals/shared/SettingsTabLayout'; import {Button} from '~/components/uikit/Button/Button'; import {Tooltip} from '~/components/uikit/Tooltip/Tooltip'; import type {UserRecord} from '~/records/UserRecord'; import * as DateUtils from '~/utils/DateUtils'; import * as WebAuthnUtils from '~/utils/WebAuthnUtils'; import styles from './SecurityTab.module.css'; interface SecurityTabProps { user: UserRecord; isClaimed: boolean; hasSmsMfa: boolean; hasTotpMfa: boolean; isSmsMfaDisabledForUser: boolean; passkeys: Array; loadingPasskeys: boolean; enablingSmsMfa: boolean; disablingSmsMfa: boolean; loadPasskeys: () => Promise; setEnablingSmsMfa: React.Dispatch>; setDisablingSmsMfa: React.Dispatch>; } export const SecurityTabContent: React.FC = observer( ({ user, isClaimed, hasSmsMfa, hasTotpMfa, isSmsMfaDisabledForUser, passkeys, loadingPasskeys, enablingSmsMfa, disablingSmsMfa, loadPasskeys, setEnablingSmsMfa, setDisablingSmsMfa, }) => { const {t, i18n} = useLingui(); const handleAddPasskey = async () => { try { const options = await UserActionCreators.getWebAuthnRegistrationOptions(); const credential = await WebAuthnUtils.performRegistration(options); ModalActionCreators.push( modal(() => ( { await UserActionCreators.registerWebAuthnCredential(credential, options.challenge, name); await loadPasskeys(); }} /> )), ); } catch (error) { console.error('Failed to add passkey', error); } }; const handleRenamePasskey = async (credentialId: string) => { ModalActionCreators.push( modal(() => ( { try { await UserActionCreators.renameWebAuthnCredential(credentialId, name); await loadPasskeys(); } catch (error) { console.error('Failed to rename passkey', error); } }} /> )), ); }; const handleDeletePasskey = (credentialId: string) => { const passkey = passkeys.find((p) => p.id === credentialId); ModalActionCreators.push( modal(() => ( Are you sure you want to delete the passkey {passkey.name}? ) : ( Are you sure you want to delete this passkey? ) } primaryText={t`Delete Passkey`} primaryVariant="danger-primary" onPrimary={async () => { try { await UserActionCreators.deleteWebAuthnCredential(credentialId); await loadPasskeys(); } catch (error) { console.error('Failed to delete passkey', error); } }} /> )), ); }; const handleEnableSmsMfa = () => { ModalActionCreators.push( modal(() => ( SMS two-factor authentication adds an additional layer of security to your account by requiring a verification code sent to your phone number when signing in. } primaryText={t`Enable SMS 2FA`} primaryVariant="primary" onPrimary={async () => { setEnablingSmsMfa(true); try { await UserActionCreators.enableSmsMfa(); } catch (error) { console.error('Failed to enable SMS MFA', error); } finally { setEnablingSmsMfa(false); } }} /> )), ); }; const handleDisableSmsMfa = () => { ModalActionCreators.push( modal(() => ( Are you sure you want to disable SMS two-factor authentication? This will make your account less secure. } primaryText={t`Disable SMS 2FA`} primaryVariant="danger-primary" onPrimary={async () => { setDisablingSmsMfa(true); try { await UserActionCreators.disableSmsMfa(); } catch (error) { console.error('Failed to disable SMS MFA', error); } finally { setDisablingSmsMfa(false); } }} /> )), ); }; if (!isClaimed) { return ( Security Features} description={ Claim your account to access security features like two-factor authentication and passkeys. } > ); } return ( <> Two-Factor Authentication} description={Add an extra layer of security to your account} >
Authenticator App
{hasTotpMfa ? ( Two-factor authentication is enabled ) : ( Use an authenticator app to generate codes for two-factor authentication )}
{hasTotpMfa ? ( ) : ( )}
{hasTotpMfa && (
Backup Codes
View and manage your backup codes for account recovery
)}
Passkeys} description={Use passkeys for passwordless sign-in and two-factor authentication} >
Registered Passkeys
{passkeys.length > 0 && (
{passkeys.map((passkey) => { const createdDate = DateUtils.getRelativeDateString(new Date(passkey.created_at), i18n); const lastUsedDate = passkey.last_used_at ? DateUtils.getRelativeDateString(new Date(passkey.last_used_at), i18n) : null; return (
{passkey.name}
{lastUsedDate ? ( Added: {createdDate} • Last used: {lastUsedDate} ) : ( Added: {createdDate} )}
); })}
)}
{user.mfaEnabled && ( <> Phone Number} description={Manage your phone number for SMS two-factor authentication} >
Phone Number
{user.phone ? ( Phone number added: {user.phone} ) : ( Add a phone number to enable SMS two-factor authentication )}
{user.phone ? ( ) : ( )}
{user.phone && ( SMS Two-Factor Authentication} description={Receive verification codes via SMS as a backup authentication method} >
SMS Backup
{hasSmsMfa ? ( SMS two-factor authentication is enabled ) : ( Enable SMS codes as a backup for your authenticator app )}
{hasSmsMfa ? ( ) : isSmsMfaDisabledForUser ? (
) : ( )}
)} )} ); }, );