Add Optional 2FA Support (#61)

* Add TOTP MFA Support

* Add Passkey MFA Support

It's not impossible I missed some minor cleanup, but most things make sense and there isn't a lot of obvious duplication anymore.

---------

Co-authored-by: Bruno Bernardino <me@brunobernardino.com>
This commit is contained in:
0xGingi
2025-05-29 12:30:28 -04:00
committed by GitHub
parent 2a77915630
commit 455a7201e9
28 changed files with 2361 additions and 40 deletions

View File

@@ -1,6 +1,8 @@
import { convertObjectToFormData } from '/lib/utils/misc.ts';
import { FormField, generateFieldHtml, getFormDataField } from '/lib/form-utils.tsx';
import { currencyMap, SupportedCurrencySymbol } from '/lib/types.ts';
import { convertObjectToFormData } from '/lib/utils/misc.ts';
import { currencyMap, SupportedCurrencySymbol, User } from '/lib/types.ts';
import MultiFactorAuthSettings from '/islands/auth/MultiFactorAuthSettings.tsx';
import { getEnabledMultiFactorAuthMethodsFromUser } from '/lib/utils/multi-factor-auth.ts';
interface SettingsProps {
formData: Record<string, any>;
@@ -14,7 +16,11 @@ interface SettingsProps {
};
currency?: SupportedCurrencySymbol;
isExpensesAppEnabled: boolean;
isMultiFactorAuthEnabled: boolean;
helpEmail: string;
user: {
extra: Pick<User['extra'], 'multi_factor_auth_methods'>;
};
}
export type Action =
@@ -121,11 +127,20 @@ function formFields(action: Action, formData: FormData, currency?: SupportedCurr
}
export default function Settings(
{ formData: formDataObject, error, notice, currency, isExpensesAppEnabled, helpEmail }: SettingsProps,
{
formData: formDataObject,
error,
notice,
currency,
isExpensesAppEnabled,
isMultiFactorAuthEnabled,
helpEmail,
user,
}: SettingsProps,
) {
const formData = convertObjectToFormData(formDataObject);
const action = getFormDataField(formData, 'action') as Action;
const multiFactorAuthMethods = getEnabledMultiFactorAuthMethodsFromUser(user);
return (
<>
@@ -151,7 +166,7 @@ export default function Settings(
<form method='POST' class='mb-12'>
{formFields(
action === 'change-email' && notice?.message.includes('verify') ? 'verify-change-email' : 'change-email',
'change-email',
formData,
).map((field) => generateFieldHtml(field, formData))}
<section class='flex justify-end mt-8 mb-4'>
@@ -195,6 +210,20 @@ export default function Settings(
)
: null}
{isMultiFactorAuthEnabled
? (
<MultiFactorAuthSettings
methods={multiFactorAuthMethods.map((method) => ({
type: method.type,
id: method.id,
name: method.name,
enabled: method.enabled,
backupCodesCount: method.metadata.totp?.hashed_backup_codes?.length,
}))}
/>
)
: null}
<h2 class='text-2xl mb-4 text-left px-4 max-w-screen-md mx-auto lg:min-w-96'>Delete your account</h2>
<p class='text-left mt-2 mb-6 px-4 max-w-screen-md mx-auto lg:min-w-96'>
Deleting your account is instant and deletes all your data. {helpEmail !== ''