Files
documenso/packages/lib/server-only/user/verify-email.ts
2025-08-25 08:23:12 +10:00

111 lines
2.4 KiB
TypeScript

import { DateTime } from 'luxon';
import { prisma } from '@documenso/prisma';
import { EMAIL_VERIFICATION_STATE } from '../../constants/email';
import { jobsClient } from '../../jobs/client';
export type VerifyEmailProps = {
token: string;
};
export const verifyEmail = async ({ token }: VerifyEmailProps) => {
const verificationToken = await prisma.verificationToken.findFirst({
include: {
user: {
select: {
id: true,
email: true,
name: true,
},
},
},
where: {
token,
},
});
if (!verificationToken) {
return {
state: EMAIL_VERIFICATION_STATE.NOT_FOUND,
userId: null,
};
}
// check if the token is valid or expired
const valid = verificationToken.expires > new Date();
if (!valid) {
const mostRecentToken = await prisma.verificationToken.findFirst({
where: {
userId: verificationToken.userId,
},
orderBy: {
createdAt: 'desc',
},
});
// If there isn't a recent token or it's older than 1 hour, send a new token
if (
!mostRecentToken ||
DateTime.now().minus({ hours: 1 }).toJSDate() > mostRecentToken.createdAt
) {
await jobsClient.triggerJob({
name: 'send.signup.confirmation.email',
payload: {
email: verificationToken.user.email,
},
});
}
return {
state: EMAIL_VERIFICATION_STATE.EXPIRED,
userId: null,
};
}
if (verificationToken.completed) {
return {
state: EMAIL_VERIFICATION_STATE.ALREADY_VERIFIED,
userId: null,
};
}
const [updatedUser] = await prisma.$transaction([
prisma.user.update({
where: {
id: verificationToken.userId,
},
data: {
emailVerified: new Date(),
},
}),
prisma.verificationToken.updateMany({
where: {
userId: verificationToken.userId,
},
data: {
completed: true,
},
}),
// Tidy up old expired tokens
prisma.verificationToken.deleteMany({
where: {
userId: verificationToken.userId,
expires: {
lt: new Date(),
},
},
}),
]);
if (!updatedUser) {
throw new Error('Something went wrong while verifying your email. Please try again.');
}
return {
state: EMAIL_VERIFICATION_STATE.VERIFIED,
userId: updatedUser.id,
};
};