diff --git a/apps/web/src/app/(unauthenticated)/unverified-account/page.tsx b/apps/web/src/app/(unauthenticated)/unverified-account/page.tsx index dc98044ae..9b636f7cf 100644 --- a/apps/web/src/app/(unauthenticated)/unverified-account/page.tsx +++ b/apps/web/src/app/(unauthenticated)/unverified-account/page.tsx @@ -1,51 +1,8 @@ -'use client'; - -import { useState } from 'react'; - -import { useSearchParams } from 'next/navigation'; - import { Mails } from 'lucide-react'; -import { ONE_SECOND } from '@documenso/lib/constants/time'; -import { trpc } from '@documenso/trpc/react'; -import { Button } from '@documenso/ui/primitives/button'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND; +import { SendConfirmationEmailForm } from '~/components/forms/send-confirmation-email'; export default function UnverifiedAccount() { - const [isButtonDisabled, setIsButtonDisabled] = useState(false); - const searchParams = useSearchParams(); - const { toast } = useToast(); - - const encryptedEmail = searchParams?.get('token') ?? ''; - - const { mutateAsync: sendConfirmationEmail } = trpc.profile.sendConfirmationEmail.useMutation(); - - const onResendConfirmationEmail = async () => { - try { - setIsButtonDisabled(true); - - await sendConfirmationEmail({ encryptedEmail }); - - toast({ - title: 'Success', - description: 'Verification email sent successfully.', - duration: 5000, - }); - - setTimeout(() => setIsButtonDisabled(false), RESEND_CONFIRMATION_EMAIL_TIMEOUT); - } catch (err) { - setIsButtonDisabled(false); - - toast({ - title: 'Error', - description: 'Something went wrong while sending the confirmation email.', - variant: 'destructive', - }); - } - }; - return (
@@ -55,13 +12,11 @@ export default function UnverifiedAccount() {

Confirm email

- To gain full access to your account and unlock all its features, please confirm your email - address by clicking on the link sent to your email address. + To gain access to your account, please confirm your email address by clicking on the + confirmation link from your inbox.

- +
); diff --git a/apps/web/src/components/forms/signin.tsx b/apps/web/src/components/forms/signin.tsx index 0353333cf..d0b5e1b60 100644 --- a/apps/web/src/components/forms/signin.tsx +++ b/apps/web/src/components/forms/signin.tsx @@ -11,7 +11,6 @@ import { FcGoogle } from 'react-icons/fc'; import { z } from 'zod'; import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes'; -import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog'; @@ -62,8 +61,6 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) = useState(false); const router = useRouter(); - const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation(); - const [twoFactorAuthenticationMethod, setTwoFactorAuthenticationMethod] = useState< 'totp' | 'backup' >('totp'); @@ -132,9 +129,12 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) = const errorMessage = ERROR_MESSAGES[result.error]; if (result.error === ErrorCode.UNVERIFIED_EMAIL) { - const encryptedEmail = await encryptSecondaryData({ data: email }); + router.push(`/unverified-account`); - router.push(`/unverified-account?token=${encryptedEmail}`); + toast({ + title: 'Unable to sign in', + description: errorMessage ?? 'An unknown error occurred', + }); return; } diff --git a/apps/web/src/components/forms/signup.tsx b/apps/web/src/components/forms/signup.tsx index bc7ee0ce5..4520e00ca 100644 --- a/apps/web/src/components/forms/signup.tsx +++ b/apps/web/src/components/forms/signup.tsx @@ -62,19 +62,17 @@ export const SignUpForm = ({ className, isGoogleSSOEnabled }: SignUpFormProps) = const isSubmitting = form.formState.isSubmitting; const { mutateAsync: signup } = trpc.auth.signup.useMutation(); - const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation(); const onFormSubmit = async ({ name, email, password, signature }: TSignUpFormSchema) => { try { await signup({ name, email, password, signature }); - const encryptedEmail = await encryptSecondaryData({ data: email }); - - router.push(`/unverified-account?token=${encryptedEmail}`); + router.push(`/unverified-account}`); toast({ title: 'Registration Successful', - description: 'You have successfully registered. Please sign in to continue.', + description: + 'You have successfully registered. Please verify your account by clicking on the link you received in the email.', duration: 5000, }); diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 37f1ed864..1dedfe12b 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -14,6 +14,7 @@ import { IdentityProvider } from '@documenso/prisma/client'; import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble'; import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa'; import { getUserByEmail } from '../server-only/user/get-user-by-email'; +import { sendConfirmationToken } from '../server-only/user/send-confirmation-token'; import { ErrorCode } from './error-codes'; export const NEXT_AUTH_OPTIONS: AuthOptions = { @@ -71,6 +72,15 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { } if (!user.emailVerified) { + const totalUserVerificationTokens = user.VerificationToken.length; + const lastUserVerificationToken = user.VerificationToken[totalUserVerificationTokens - 1]; + const expiredToken = + DateTime.fromJSDate(lastUserVerificationToken.expires) <= DateTime.now(); + + if (totalUserVerificationTokens < 1 || expiredToken) { + await sendConfirmationToken({ email }); + } + throw new Error(ErrorCode.UNVERIFIED_EMAIL); } diff --git a/packages/lib/server-only/user/get-user-by-email.ts b/packages/lib/server-only/user/get-user-by-email.ts index 0a2ef8d16..8c61202a2 100644 --- a/packages/lib/server-only/user/get-user-by-email.ts +++ b/packages/lib/server-only/user/get-user-by-email.ts @@ -9,5 +9,8 @@ export const getUserByEmail = async ({ email }: GetUserByEmailOptions) => { where: { email: email.toLowerCase(), }, + include: { + VerificationToken: true, + }, }); }; diff --git a/packages/lib/server-only/user/send-confirmation-token.ts b/packages/lib/server-only/user/send-confirmation-token.ts index af4a97a48..a399dd9fc 100644 --- a/packages/lib/server-only/user/send-confirmation-token.ts +++ b/packages/lib/server-only/user/send-confirmation-token.ts @@ -20,6 +20,10 @@ export const sendConfirmationToken = async ({ email }: { email: string }) => { throw new Error('User not found'); } + if (user.emailVerified) { + throw new Error('Email verified'); + } + const createdToken = await prisma.verificationToken.create({ data: { identifier: IDENTIFIER, diff --git a/packages/trpc/server/profile-router/router.ts b/packages/trpc/server/profile-router/router.ts index 1faa3c8e6..3d765372b 100644 --- a/packages/trpc/server/profile-router/router.ts +++ b/packages/trpc/server/profile-router/router.ts @@ -1,6 +1,5 @@ import { TRPCError } from '@trpc/server'; -import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'; import { forgotPassword } from '@documenso/lib/server-only/user/forgot-password'; import { getUserById } from '@documenso/lib/server-only/user/get-user-by-id'; import { resetPassword } from '@documenso/lib/server-only/user/reset-password'; @@ -118,15 +117,9 @@ export const profileRouter = router({ .input(ZConfirmEmailMutationSchema) .mutation(async ({ input }) => { try { - const { encryptedEmail } = input; + const { email } = input; - const decryptedEmail = decryptSecondaryData(encryptedEmail); - - if (!decryptedEmail) { - throw new Error('Email is required'); - } - - return await sendConfirmationToken({ email: decryptedEmail }); + return await sendConfirmationToken({ email }); } catch (err) { let message = 'We were unable to send a confirmation email. Please try again.'; diff --git a/packages/trpc/server/profile-router/schema.ts b/packages/trpc/server/profile-router/schema.ts index 135d0d1e8..ef9ca2a14 100644 --- a/packages/trpc/server/profile-router/schema.ts +++ b/packages/trpc/server/profile-router/schema.ts @@ -24,7 +24,7 @@ export const ZResetPasswordFormSchema = z.object({ }); export const ZConfirmEmailMutationSchema = z.object({ - encryptedEmail: z.string().min(1), + email: z.string().email().min(1), }); export type TRetrieveUserByIdQuerySchema = z.infer;