From 63166d4e45fc1966937dfdfa0c3b3bc0286ad1a2 Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Thu, 19 Sep 2024 07:24:10 -0400 Subject: [PATCH] impl tfa setup via user settings --- bun.lockb | Bin 201016 -> 201815 bytes package.json | 2 + .../(pages)/dashboard/user/profile/page.tsx | 8 +- .../(pages)/dashboard/user/settings/page.tsx | 3 +- .../user/response/user-setup-tfa-response.ts | 16 ++ src/app/types/user/user-flag.ts | 18 +- .../user/{ => profile}/avatar-setting.tsx | 0 .../user/{ => profile}/email-setting.tsx | 0 .../user/{ => profile}/tier-setting.tsx | 2 +- .../user/{ => profile}/username-setting.tsx | 0 .../user/settings/tfa/tfa-setting.tsx | 182 ++++++++++++++++++ .../user/settings/tfa/tfa-setup-form.tsx | 146 ++++++++++++++ src/components/ui/input-otp.tsx | 71 +++++++ tailwind.config.ts | 9 + 14 files changed, 450 insertions(+), 7 deletions(-) create mode 100644 src/app/types/user/response/user-setup-tfa-response.ts rename src/components/dashboard/user/{ => profile}/avatar-setting.tsx (100%) rename src/components/dashboard/user/{ => profile}/email-setting.tsx (100%) rename src/components/dashboard/user/{ => profile}/tier-setting.tsx (97%) rename src/components/dashboard/user/{ => profile}/username-setting.tsx (100%) create mode 100644 src/components/dashboard/user/settings/tfa/tfa-setting.tsx create mode 100644 src/components/dashboard/user/settings/tfa/tfa-setup-form.tsx create mode 100644 src/components/ui/input-otp.tsx diff --git a/bun.lockb b/bun.lockb index c3bb01f8660c805f238943a8fede956a215d49a6..434348d33ca792effe7b5b8ba47c0e942ee96bd4 100644 GIT binary patch delta 36508 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

#*nwxvrHv`NrP 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 ( {/* Content */}

); diff --git a/src/app/types/user/response/user-setup-tfa-response.ts b/src/app/types/user/response/user-setup-tfa-response.ts new file mode 100644 index 0000000..f81026a --- /dev/null +++ b/src/app/types/user/response/user-setup-tfa-response.ts @@ -0,0 +1,16 @@ +/** + * The response for when a {@link User} + * initializes the setup of two-factor + * authentication. + */ +export type UserSetupTfaResponse = { + /** + * The TFA secret. + */ + secret: string; + + /** + * The URL to the QR code. + */ + qrCodeUrl: string; +}; diff --git a/src/app/types/user/user-flag.ts b/src/app/types/user/user-flag.ts index 7efe8f7..b48aa38 100644 --- a/src/app/types/user/user-flag.ts +++ b/src/app/types/user/user-flag.ts @@ -2,7 +2,23 @@ * Flags for a {@link User}. */ export enum UserFlag { + /** + * The user is disabled. + */ DISABLED = 0, + + /** + * The user completed the onboarding process. + */ COMPLETED_ONBOARDING = 1, - ADMINISTRATOR = 2, + + /** + * The user has two-factor auth enabled. + */ + TFA_ENABLED = 2, + + /** + * The user is an administrator. + */ + ADMINISTRATOR = 3, } diff --git a/src/components/dashboard/user/avatar-setting.tsx b/src/components/dashboard/user/profile/avatar-setting.tsx similarity index 100% rename from src/components/dashboard/user/avatar-setting.tsx rename to src/components/dashboard/user/profile/avatar-setting.tsx diff --git a/src/components/dashboard/user/email-setting.tsx b/src/components/dashboard/user/profile/email-setting.tsx similarity index 100% rename from src/components/dashboard/user/email-setting.tsx rename to src/components/dashboard/user/profile/email-setting.tsx diff --git a/src/components/dashboard/user/tier-setting.tsx b/src/components/dashboard/user/profile/tier-setting.tsx similarity index 97% rename from src/components/dashboard/user/tier-setting.tsx rename to src/components/dashboard/user/profile/tier-setting.tsx index 3359293..ee041c1 100644 --- a/src/components/dashboard/user/tier-setting.tsx +++ b/src/components/dashboard/user/profile/tier-setting.tsx @@ -33,7 +33,7 @@ const TierSetting = (): ReactElement => { {capitalizeWords(user?.tier)} - + + ) : ( + { + if (open) { + setupTfa(); + } else if (enabledTfa) { + router.push("/auth"); + } + }} + > + + + + + + + {enabledTfa + ? "Two-Factor Auth Enabled" + : "Setup Two-Factor Auth"} + + {enabledTfa ? ( + + Your account now has two-factor + authentication enabled! Make sure to + save the backup codes below,{" "} + + this is the last time you'll see + them! + + + ) : ( + + Follow the steps below to setup two-factor + authentication on your account. This adds an + extra layer of security and will require not + only your password, but also your mobile + device during login. + + )} + + + {/* Content */} + {!tfaResponse ? ( +
+ Loading... +
+ ) : ( +
+ {!enabledTfa && ( + + )} + +
+ )} + + {/* Close */} + {enabledTfa && ( +
+ + + +
+ )} + + {/* Notice */} + {!enabledTfa && ( + + NOTE:Enabling two-factor auth will log + you out of all devices. + + )} +
+
+ )} + + ); +}; + +const QRCode = ({ + tfaResponse, +}: { + tfaResponse: UserSetupTfaResponse; +}): ReactElement => ( +
+ +
+

Or manually copy this code...

+ +
+
+); + +export default TFASetting; diff --git a/src/components/dashboard/user/settings/tfa/tfa-setup-form.tsx b/src/components/dashboard/user/settings/tfa/tfa-setup-form.tsx new file mode 100644 index 0000000..af6ca39 --- /dev/null +++ b/src/components/dashboard/user/settings/tfa/tfa-setup-form.tsx @@ -0,0 +1,146 @@ +import { ReactElement, useState } from "react"; +import { UserSetupTfaResponse } from "@/app/types/user/response/user-setup-tfa-response"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { + InputOTP, + InputOTPGroup, + InputOTPSeparator, + InputOTPSlot, +} from "@/components/ui/input-otp"; +import { Button } from "@/components/ui/button"; +import { z } from "zod"; +import { apiRequest } from "@/lib/api"; +import { Session } from "@/app/types/user/session"; +import { useUserContext } from "@/app/provider/user-provider"; +import { UserState } from "@/app/store/user-store"; +import { ArrowPathIcon } from "@heroicons/react/24/outline"; + +const FormSchema = z.object({ + pin: z.string(), +}); + +/** + * The form used to complete + * the setup of TFA for a user. + * + * @param tfaResponse the tfa setup response + * @return the form jsx + */ +const TfaSetupForm = ({ + tfaResponse, + setEnabledTfa, +}: { + tfaResponse: UserSetupTfaResponse; + setEnabledTfa: (hidden: boolean) => void; +}): ReactElement => { + const session: Session | undefined = useUserContext( + (state: UserState) => state.session + ); + + const [value, setValue] = useState(); + const [verifying, setVerifying] = useState(false); + const [backupCodes, setBackupCodes] = useState(); + const [error, setError] = useState(undefined); + + // Build the form + const { + register, + handleSubmit, + watch, + formState: { errors }, + } = useForm({ + resolver: zodResolver(FormSchema), + }); + + /** + * Handle submitting the form. + */ + const onSubmit = async ({ pin }: any) => { + if (pin.length !== 6) { + return; + } + setVerifying(true); + const { data, error } = await apiRequest({ + endpoint: "/user/enable-tfa", + method: "POST", + session, + body: { + secret: tfaResponse?.secret, + pin, + }, + }); + setBackupCodes(data); + if (data) { + setEnabledTfa(true); + } + setError(error?.message ?? undefined); + setVerifying(false); + }; + + return ( +
+ {backupCodes ? ( +
+ {backupCodes.map((code, index) => ( +
+ {code} +
+ ))} +
+ ) : ( + <> + {/* Notice */} +

+ Enter the 6-digit pin provided by your authenticator app + below to enable two-factor authentication! +

+ + {/* Input */} + { + await onSubmit({ pin }); + setValue(pin); + }} + > + + {[0, 1, 2].map((index: number) => ( + + ))} + + + + {[3, 4, 5].map((index: number) => ( + + ))} + + + + {/* Display the error if it exists */} + {error && ( +

{error}

+ )} + + {/* Verify Pin */} + + + )} +
+ ); +}; + +export default TfaSetupForm; diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx new file mode 100644 index 0000000..25be93f --- /dev/null +++ b/src/components/ui/input-otp.tsx @@ -0,0 +1,71 @@ +"use client"; + +import * as React from "react"; +import { DashIcon } from "@radix-ui/react-icons"; +import { OTPInput, OTPInputContext } from "input-otp"; + +import { cn } from "@/lib/utils"; + +const InputOTP = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)); +InputOTP.displayName = "InputOTP"; + +const InputOTPGroup = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => ( +
+)); +InputOTPGroup.displayName = "InputOTPGroup"; + +const InputOTPSlot = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> & { index: number } +>(({ index, className, ...props }, ref) => { + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + + return ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ); +}); +InputOTPSlot.displayName = "InputOTPSlot"; + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( +
+ +
+)); +InputOTPSeparator.displayName = "InputOTPSeparator"; + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; diff --git a/tailwind.config.ts b/tailwind.config.ts index ffff7ea..8ae2243 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -61,6 +61,15 @@ const config: Config = { md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, + keyframes: { + "caret-blink": { + "0%,70%,100%": { opacity: "1" }, + "20%,50%": { opacity: "0" }, + }, + }, + animation: { + "caret-blink": "caret-blink 1.25s ease-out infinite", + }, }, }, plugins: [require("tailwindcss-animate")],