diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 0aa248c80..41c289497 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -26,7 +26,7 @@ export default async function RootLayout({ {children} - + ); @@ -52,7 +52,7 @@ function getClassName(theme?: string) { } function getTheme() { - return cookies().get('theme')?.value; + return cookies().get('theme')?.value as 'light' | 'dark' | 'system'; } export const generateMetadata = generateRootMetadata; diff --git a/apps/web/config/auth.config.ts b/apps/web/config/auth.config.ts index 44d206965..5654bebe1 100644 --- a/apps/web/config/auth.config.ts +++ b/apps/web/config/auth.config.ts @@ -39,7 +39,7 @@ const authConfig = AuthConfigSchema.parse({ // in your production project providers: { password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true', - magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true', + magicLink: true, oAuth: ['google'], }, } satisfies z.infer); diff --git a/apps/web/public/locales/en/auth.json b/apps/web/public/locales/en/auth.json index 4cd72d596..3c1a15e6d 100644 --- a/apps/web/public/locales/en/auth.json +++ b/apps/web/public/locales/en/auth.json @@ -76,6 +76,7 @@ "passwordsDoNotMatch": "The passwords do not match", "minPasswordNumbers": "Password must contain at least one number", "minPasswordSpecialChars": "Password must contain at least one special character", - "uppercasePassword": "Password must contain at least one uppercase letter" + "uppercasePassword": "Password must contain at least one uppercase letter", + "insufficient_aal": "Please sign-in with your current multi-factor authentication to perform this action" } } diff --git a/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-list.tsx b/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-list.tsx index 6ff42a043..598cbcf57 100644 --- a/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-list.tsx +++ b/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-list.tsx @@ -150,14 +150,25 @@ function ConfirmUnenrollFactorModal( (factorId: string) => { if (unEnroll.isPending) return; - const promise = unEnroll.mutateAsync(factorId).then(() => { + const promise = unEnroll.mutateAsync(factorId).then((response) => { props.setIsModalOpen(false); + + if (!response.success) { + const errorCode = response.data; + + throw t(`auth:errors.${errorCode}`, { + defaultValue: t(`account:unenrollFactorError`) + }); + } }); toast.promise(promise, { loading: t(`account:unenrollingFactor`), success: t(`account:unenrollFactorSuccess`), - error: t(`account:unenrollFactorError`), + error: (error: string) => { + return error; + }, + duration: Infinity }); }, [props, t, unEnroll], @@ -279,16 +290,22 @@ function useUnenrollFactor(userId: string) { }); if (error) { - throw error; + return { + success: false as const, + data: error.code as string, + } } - return data; + return { + success: true as const, + data, + } }; return useMutation({ mutationFn, mutationKey, - onSuccess: async () => { + onSuccess: () => { return queryClient.refetchQueries({ queryKey: mutationKey, }); diff --git a/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-setup-dialog.tsx b/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-setup-dialog.tsx index 93b8d950c..6b32f98c7 100644 --- a/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-setup-dialog.tsx +++ b/packages/features/accounts/src/components/personal-account-settings/mfa/multi-factor-auth-setup-dialog.tsx @@ -6,7 +6,7 @@ import Image from 'next/image'; import { zodResolver } from '@hookform/resolvers/zod'; import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; @@ -43,6 +43,7 @@ import { import { Trans } from '@kit/ui/trans'; import { refreshAuthSession } from '../../../server/personal-accounts-server-actions'; +import {ArrowLeftIcon} from "lucide-react"; export function MultiFactorAuthSetupDialog(props: { userId: string }) { const { t } = useTranslation(); @@ -252,7 +253,8 @@ function FactorQrCode({ onSetFactorId: (factorId: string) => void; }>) { const enrollFactorMutation = useEnrollFactor(userId); - const [error, setError] = useState(false); + const { t } = useTranslation(); + const [error, setError] = useState(''); const form = useForm({ resolver: zodResolver( @@ -280,9 +282,16 @@ function FactorQrCode({ - + + +
+ +
); } @@ -292,18 +301,14 @@ function FactorQrCode({ { - const data = await enrollFactorMutation - .mutateAsync(name) - .catch((error) => { - console.error(error); + const response = await enrollFactorMutation.mutateAsync(name); - return; - }); - - if (data === undefined) { - return setError(true); + if (!response.success) { + return setError(response.data as string); } + const data = response.data; + if (data.type === 'totp') { form.setValue('factorName', name); form.setValue('qrCode', data.totp.qr_code); @@ -401,24 +406,36 @@ function QrImage({ src }: { src: string }) { function useEnrollFactor(userId: string) { const client = useSupabase(); + const queryClient = useQueryClient(); const mutationKey = useFactorsMutationKey(userId); const mutationFn = async (factorName: string) => { - const { data, error } = await client.auth.mfa.enroll({ + const response = await client.auth.mfa.enroll({ friendlyName: factorName, factorType: 'totp', }); - if (error) { - throw error; + if (response.error) { + return { + success: false as const, + data: response.error.code, + } } - return data; + return { + success: true as const, + data: response.data, + } }; return useMutation({ mutationFn, mutationKey, + onSuccess() { + return queryClient.refetchQueries({ + queryKey: mutationKey, + }); + }, }); }