/*
* 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}
>