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.
-
- Resend email
-
+
);
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;