From d052f0201325d590d5066efafeefbc8f2e6a1f69 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Tue, 13 Feb 2024 06:01:25 +0000 Subject: [PATCH] chore: refactor code --- packages/lib/next-auth/auth-options.ts | 22 +++++++-------- .../user/get-last-verification-token.ts | 27 ------------------- ...st-recent-verification-token-by-user-id.ts | 18 +++++++++++++ .../user/send-confirmation-token.ts | 20 +++++++++++++- 4 files changed, 46 insertions(+), 41 deletions(-) delete mode 100644 packages/lib/server-only/user/get-last-verification-token.ts create mode 100644 packages/lib/server-only/user/get-most-recent-verification-token-by-user-id.ts diff --git a/packages/lib/next-auth/auth-options.ts b/packages/lib/next-auth/auth-options.ts index 723b9cd7b..b944b6e7b 100644 --- a/packages/lib/next-auth/auth-options.ts +++ b/packages/lib/next-auth/auth-options.ts @@ -13,7 +13,7 @@ import { IdentityProvider, UserSecurityAuditLogType } from '@documenso/prisma/cl import { isTwoFactorAuthenticationEnabled } from '../server-only/2fa/is-2fa-availble'; import { validateTwoFactorAuthentication } from '../server-only/2fa/validate-2fa'; -import { getLastVerificationToken } from '../server-only/user/get-last-verification-token'; +import { getMostRecentVerificationTokenByUserId } from '../server-only/user/get-most-recent-verification-token-by-user-id'; import { getUserByEmail } from '../server-only/user/get-user-by-email'; import { sendConfirmationToken } from '../server-only/user/send-confirmation-token'; import { extractNextAuthRequestMetadata } from '../universal/extract-request-metadata'; @@ -93,19 +93,15 @@ export const NEXT_AUTH_OPTIONS: AuthOptions = { } if (!user.emailVerified) { - const [lastUserVerificationToken] = await getLastVerificationToken({ userId: user.id }); + const mostRecentToken = await getMostRecentVerificationTokenByUserId({ + userId: user.id, + }); - if (!lastUserVerificationToken) { - await sendConfirmationToken({ email }); - throw new Error(ErrorCode.UNVERIFIED_EMAIL); - } - - const expiredToken = - DateTime.fromJSDate(lastUserVerificationToken.expires) <= DateTime.now(); - const lastSentToken = DateTime.fromJSDate(lastUserVerificationToken.createdAt); - const sentWithinLastHour = DateTime.now().minus({ hours: 1 }) <= lastSentToken; - - if (expiredToken || !sentWithinLastHour) { + if ( + !mostRecentToken || + mostRecentToken.expires.valueOf() <= Date.now() || + DateTime.fromJSDate(mostRecentToken.createdAt).diffNow('minutes').minutes > -5 + ) { await sendConfirmationToken({ email }); } diff --git a/packages/lib/server-only/user/get-last-verification-token.ts b/packages/lib/server-only/user/get-last-verification-token.ts deleted file mode 100644 index 279a1fcfd..000000000 --- a/packages/lib/server-only/user/get-last-verification-token.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { prisma } from '@documenso/prisma'; - -export interface GetLastVerificationTokenOptions { - userId: number; -} - -export const getLastVerificationToken = async ({ userId }: GetLastVerificationTokenOptions) => { - const user = await prisma.user.findFirstOrThrow({ - where: { - id: userId, - }, - include: { - VerificationToken: { - select: { - expires: true, - createdAt: true, - }, - orderBy: { - createdAt: 'desc', - }, - take: 1, - }, - }, - }); - - return user.VerificationToken; -}; diff --git a/packages/lib/server-only/user/get-most-recent-verification-token-by-user-id.ts b/packages/lib/server-only/user/get-most-recent-verification-token-by-user-id.ts new file mode 100644 index 000000000..d9adc4498 --- /dev/null +++ b/packages/lib/server-only/user/get-most-recent-verification-token-by-user-id.ts @@ -0,0 +1,18 @@ +import { prisma } from '@documenso/prisma'; + +export type GetMostRecentVerificationTokenByUserIdOptions = { + userId: number; +}; + +export const getMostRecentVerificationTokenByUserId = async ({ + userId, +}: GetMostRecentVerificationTokenByUserIdOptions) => { + return await prisma.verificationToken.findFirst({ + where: { + userId, + }, + orderBy: { + createdAt: 'desc', + }, + }); +}; diff --git a/packages/lib/server-only/user/send-confirmation-token.ts b/packages/lib/server-only/user/send-confirmation-token.ts index a399dd9fc..ef7c4b104 100644 --- a/packages/lib/server-only/user/send-confirmation-token.ts +++ b/packages/lib/server-only/user/send-confirmation-token.ts @@ -1,13 +1,20 @@ import crypto from 'crypto'; +import { DateTime } from 'luxon'; import { prisma } from '@documenso/prisma'; import { ONE_HOUR } from '../../constants/time'; import { sendConfirmationEmail } from '../auth/send-confirmation-email'; +import { getMostRecentVerificationTokenByUserId } from './get-most-recent-verification-token-by-user-id'; const IDENTIFIER = 'confirmation-email'; -export const sendConfirmationToken = async ({ email }: { email: string }) => { +type SendConfirmationTokenOptions = { email: string; force?: boolean }; + +export const sendConfirmationToken = async ({ + email, + force = false, +}: SendConfirmationTokenOptions) => { const token = crypto.randomBytes(20).toString('hex'); const user = await prisma.user.findFirst({ @@ -24,6 +31,17 @@ export const sendConfirmationToken = async ({ email }: { email: string }) => { throw new Error('Email verified'); } + const mostRecentToken = await getMostRecentVerificationTokenByUserId({ userId: user.id }); + + // If we've sent a token in the last 5 minutes, don't send another one + if ( + !force && + mostRecentToken?.createdAt && + DateTime.fromJSDate(mostRecentToken.createdAt).diffNow('minutes').minutes > -5 + ) { + return; + } + const createdToken = await prisma.verificationToken.create({ data: { identifier: IDENTIFIER,