Add user id parameter to multi-factor authentication functions

The multi-factor authentication functions have been modified to accept a user id as a parameter. This provides more flexibility as it allows a more specific targeting of users. The `useFetchAuthFactors` function has been updated to export the function rather than default, and the `useFactorsMutationKey` function has been updated to take a user id.
This commit is contained in:
giancarlo
2024-05-28 21:13:36 +07:00
parent ae162f7471
commit cbf116c688
7 changed files with 46 additions and 28 deletions

View File

@@ -2,6 +2,7 @@ import { redirect } from 'next/navigation';
import { MultiFactorChallengeContainer } from '@kit/auth/mfa'; import { MultiFactorChallengeContainer } from '@kit/auth/mfa';
import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa'; import { checkRequiresMultiFactorAuthentication } from '@kit/supabase/check-requires-mfa';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client'; import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import pathsConfig from '~/config/paths.config'; import pathsConfig from '~/config/paths.config';
@@ -31,9 +32,15 @@ async function VerifyPage(props: Props) {
} }
const redirectPath = props.searchParams.next ?? pathsConfig.app.home; const redirectPath = props.searchParams.next ?? pathsConfig.app.home;
const auth = await requireUser(client);
if (auth.error) {
redirect(auth.redirectTo);
}
return ( return (
<MultiFactorChallengeContainer <MultiFactorChallengeContainer
userId={auth.data.id}
paths={{ paths={{
redirectPath, redirectPath,
}} }}

View File

@@ -143,7 +143,7 @@ export function PersonalAccountSettingsContainer(
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<MultiFactorAuthFactorsList /> <MultiFactorAuthFactorsList userId={props.userId} />
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -10,7 +10,7 @@ import { ShieldCheck, X } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { toast } from 'sonner'; import { toast } from 'sonner';
import useFetchAuthFactors from '@kit/supabase/hooks/use-fetch-mfa-factors'; import { useFetchAuthFactors } from '@kit/supabase/hooks/use-fetch-mfa-factors';
import { useSupabase } from '@kit/supabase/hooks/use-supabase'; import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { useFactorsMutationKey } from '@kit/supabase/hooks/use-user-factors-mutation-key'; import { useFactorsMutationKey } from '@kit/supabase/hooks/use-user-factors-mutation-key';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
@@ -48,8 +48,13 @@ import { MultiFactorAuthSetupDialog } from './multi-factor-auth-setup-dialog';
const MAX_FACTOR_COUNT = 10; const MAX_FACTOR_COUNT = 10;
export function MultiFactorAuthFactorsList() { export function MultiFactorAuthFactorsList(props: { userId: string }) {
const { data: factors, isLoading, isError } = useFetchAuthFactors(); const {
data: factors,
isLoading,
isError,
} = useFetchAuthFactors(props.userId);
const [unEnrolling, setUnenrolling] = useState<string>(); const [unEnrolling, setUnenrolling] = useState<string>();
if (isLoading) { if (isLoading) {
@@ -100,7 +105,7 @@ export function MultiFactorAuthFactorsList() {
</Alert> </Alert>
<div> <div>
<MultiFactorAuthSetupDialog /> <MultiFactorAuthSetupDialog userId={props.userId} />
</div> </div>
</div> </div>
); );
@@ -114,13 +119,14 @@ export function MultiFactorAuthFactorsList() {
<If condition={canAddNewFactors}> <If condition={canAddNewFactors}>
<div> <div>
<MultiFactorAuthSetupDialog /> <MultiFactorAuthSetupDialog userId={props.userId} />
</div> </div>
</If> </If>
<If condition={unEnrolling}> <If condition={unEnrolling}>
{(factorId) => ( {(factorId) => (
<ConfirmUnenrollFactorModal <ConfirmUnenrollFactorModal
userId={props.userId}
factorId={factorId} factorId={factorId}
setIsModalOpen={() => setUnenrolling(undefined)} setIsModalOpen={() => setUnenrolling(undefined)}
/> />
@@ -133,11 +139,12 @@ export function MultiFactorAuthFactorsList() {
function ConfirmUnenrollFactorModal( function ConfirmUnenrollFactorModal(
props: React.PropsWithChildren<{ props: React.PropsWithChildren<{
factorId: string; factorId: string;
userId: string;
setIsModalOpen: (isOpen: boolean) => void; setIsModalOpen: (isOpen: boolean) => void;
}>, }>,
) { ) {
const { t } = useTranslation(); const { t } = useTranslation();
const unEnroll = useUnenrollFactor(); const unEnroll = useUnenrollFactor(props.userId);
const onUnenrollRequested = useCallback( const onUnenrollRequested = useCallback(
(factorId: string) => { (factorId: string) => {
@@ -261,10 +268,10 @@ function FactorsTable({
); );
} }
function useUnenrollFactor() { function useUnenrollFactor(userId: string) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const client = useSupabase(); const client = useSupabase();
const mutationKey = useFactorsMutationKey(); const mutationKey = useFactorsMutationKey(userId);
const mutationFn = async (factorId: string) => { const mutationFn = async (factorId: string) => {
const { data, error } = await client.auth.mfa.unenroll({ const { data, error } = await client.auth.mfa.unenroll({

View File

@@ -44,7 +44,7 @@ import { Trans } from '@kit/ui/trans';
import { refreshAuthSession } from '../../../server/personal-accounts-server-actions'; import { refreshAuthSession } from '../../../server/personal-accounts-server-actions';
export function MultiFactorAuthSetupDialog() { export function MultiFactorAuthSetupDialog(props: { userId: string }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -77,6 +77,7 @@ export function MultiFactorAuthSetupDialog() {
<div> <div>
<MultiFactorAuthSetupForm <MultiFactorAuthSetupForm
userId={props.userId}
onCancel={() => setIsOpen(false)} onCancel={() => setIsOpen(false)}
onEnrolled={onEnrollSuccess} onEnrolled={onEnrollSuccess}
/> />
@@ -90,11 +91,13 @@ export function MultiFactorAuthSetupDialog() {
function MultiFactorAuthSetupForm({ function MultiFactorAuthSetupForm({
onEnrolled, onEnrolled,
onCancel, onCancel,
userId,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
userId: string;
onCancel: () => void; onCancel: () => void;
onEnrolled: () => void; onEnrolled: () => void;
}>) { }>) {
const verifyCodeMutation = useVerifyCodeMutation(); const verifyCodeMutation = useVerifyCodeMutation(userId);
const verificationCodeForm = useForm({ const verificationCodeForm = useForm({
resolver: zodResolver( resolver: zodResolver(
@@ -161,6 +164,7 @@ function MultiFactorAuthSetupForm({
<div className={'flex flex-col space-y-4'}> <div className={'flex flex-col space-y-4'}>
<div className={'flex justify-center'}> <div className={'flex justify-center'}>
<FactorQrCode <FactorQrCode
userId={userId}
onCancel={onCancel} onCancel={onCancel}
onSetFactorId={(factorId) => onSetFactorId={(factorId) =>
verificationCodeForm.setValue('factorId', factorId) verificationCodeForm.setValue('factorId', factorId)
@@ -241,11 +245,13 @@ function MultiFactorAuthSetupForm({
function FactorQrCode({ function FactorQrCode({
onSetFactorId, onSetFactorId,
onCancel, onCancel,
userId,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
userId: string;
onCancel: () => void; onCancel: () => void;
onSetFactorId: (factorId: string) => void; onSetFactorId: (factorId: string) => void;
}>) { }>) {
const enrollFactorMutation = useEnrollFactor(); const enrollFactorMutation = useEnrollFactor(userId);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const form = useForm({ const form = useForm({
@@ -385,9 +391,9 @@ function QrImage({ src }: { src: string }) {
return <Image alt={'QR Code'} src={src} width={160} height={160} />; return <Image alt={'QR Code'} src={src} width={160} height={160} />;
} }
function useEnrollFactor() { function useEnrollFactor(userId: string) {
const client = useSupabase(); const client = useSupabase();
const mutationKey = useFactorsMutationKey(); const mutationKey = useFactorsMutationKey(userId);
const mutationFn = async (factorName: string) => { const mutationFn = async (factorName: string) => {
const { data, error } = await client.auth.mfa.enroll({ const { data, error } = await client.auth.mfa.enroll({
@@ -408,8 +414,8 @@ function useEnrollFactor() {
}); });
} }
function useVerifyCodeMutation() { function useVerifyCodeMutation(userId: string) {
const mutationKey = useFactorsMutationKey(); const mutationKey = useFactorsMutationKey(userId);
const client = useSupabase(); const client = useSupabase();
const mutationFn = async (params: { factorId: string; code: string }) => { const mutationFn = async (params: { factorId: string; code: string }) => {

View File

@@ -10,7 +10,7 @@ import { useMutation } from '@tanstack/react-query';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import useFetchAuthFactors from '@kit/supabase/hooks/use-fetch-mfa-factors'; import { useFetchAuthFactors } from '@kit/supabase/hooks/use-fetch-mfa-factors';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out'; import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import { useSupabase } from '@kit/supabase/hooks/use-supabase'; import { useSupabase } from '@kit/supabase/hooks/use-supabase';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
@@ -35,7 +35,9 @@ import { Trans } from '@kit/ui/trans';
export function MultiFactorChallengeContainer({ export function MultiFactorChallengeContainer({
paths, paths,
userId,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
userId: string;
paths: { paths: {
redirectPath: string; redirectPath: string;
}; };
@@ -65,6 +67,7 @@ export function MultiFactorChallengeContainer({
if (!factorId) { if (!factorId) {
return ( return (
<FactorsListContainer <FactorsListContainer
userId={userId}
onSelect={(factorId) => { onSelect={(factorId) => {
verificationCodeForm.setValue('factorId', factorId); verificationCodeForm.setValue('factorId', factorId);
}} }}
@@ -195,12 +198,14 @@ function useVerifyMFAChallenge() {
function FactorsListContainer({ function FactorsListContainer({
onSuccess, onSuccess,
onSelect, onSelect,
userId,
}: React.PropsWithChildren<{ }: React.PropsWithChildren<{
userId: string;
onSuccess: () => void; onSuccess: () => void;
onSelect: (factor: string) => void; onSelect: (factor: string) => void;
}>) { }>) {
const signOut = useSignOut(); const signOut = useSignOut();
const { data: factors, isLoading, error } = useFetchAuthFactors(); const { data: factors, isLoading, error } = useFetchAuthFactors(userId);
const isSuccess = factors && !isLoading && !error; const isSuccess = factors && !isLoading && !error;

View File

@@ -3,9 +3,9 @@ import { useQuery } from '@tanstack/react-query';
import { useSupabase } from './use-supabase'; import { useSupabase } from './use-supabase';
import { useFactorsMutationKey } from './use-user-factors-mutation-key'; import { useFactorsMutationKey } from './use-user-factors-mutation-key';
function useFetchAuthFactors() { export function useFetchAuthFactors(userId: string) {
const client = useSupabase(); const client = useSupabase();
const queryKey = useFactorsMutationKey(); const queryKey = useFactorsMutationKey(userId);
const queryFn = async () => { const queryFn = async () => {
const { data, error } = await client.auth.mfa.listFactors(); const { data, error } = await client.auth.mfa.listFactors();
@@ -22,5 +22,3 @@ function useFetchAuthFactors() {
queryFn, queryFn,
}); });
} }
export default useFetchAuthFactors;

View File

@@ -1,8 +1,3 @@
import { useUserSession } from './use-user-session'; export function useFactorsMutationKey(userId: string) {
export function useFactorsMutationKey() {
const user = useUserSession();
const userId = user?.data?.user.id;
return ['mfa-factors', userId]; return ['mfa-factors', userId];
} }