From 6632675a3867773ee8222137f37bb8ac76ff575e Mon Sep 17 00:00:00 2001 From: Ivan Dalmet Date: Tue, 26 Sep 2023 13:09:23 +0200 Subject: [PATCH] feat: Improve verification token security --- src/server/api/routers/auth.tsx | 23 ++++++++++++++++------- src/server/auth.ts | 13 +++++++++++-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/server/api/routers/auth.tsx b/src/server/api/routers/auth.tsx index ef231da7a..c3c144274 100644 --- a/src/server/api/routers/auth.tsx +++ b/src/server/api/routers/auth.tsx @@ -1,6 +1,5 @@ import { TRPCError } from '@trpc/server'; import bcrypt from 'bcrypt'; -import { randomUUID } from 'crypto'; import dayjs from 'dayjs'; import jwt from 'jsonwebtoken'; import { cookies } from 'next/headers'; @@ -10,7 +9,7 @@ import EmailActivateAccount from '@/emails/templates/activate-account'; import EmailResetPassword from '@/emails/templates/reset-password'; import i18n from '@/lib/i18n/server'; import { createTRPCRouter, publicProcedure } from '@/server/api/trpc'; -import { AUTH_COOKIE_NAME } from '@/server/auth'; +import { AUTH_COOKIE_NAME, decodeJwt } from '@/server/auth'; import { prismaThrowFormatedTRPCError } from '@/server/db'; import { sendEmail } from '@/server/email'; @@ -122,7 +121,7 @@ export const authRouter = createTRPCRouter({ }); } - const token = randomUUID(); + const token = jwt.sign({ id: user.id }, process.env.AUTH_SECRET); await ctx.db.verificationToken.create({ data: { userId: user.id, @@ -170,8 +169,13 @@ export const authRouter = createTRPCRouter({ throw new TRPCError({ code: 'BAD_REQUEST' }); } + const jwtDecoded = decodeJwt(input.token); + if (!jwtDecoded?.id) { + throw new TRPCError({ code: 'BAD_REQUEST' }); + } + const verificationToken = await ctx.db.verificationToken.findUnique({ - where: { token: input.token }, + where: { token: input.token, userId: jwtDecoded.id }, }); if (!verificationToken) { @@ -206,8 +210,6 @@ export const authRouter = createTRPCRouter({ .input(z.object({ email: z.string().email() })) .output(z.void()) .mutation(async ({ ctx, input }) => { - const token = randomUUID(); - const user = await ctx.db.user.findFirst({ where: { email: input.email }, }); @@ -217,6 +219,8 @@ export const authRouter = createTRPCRouter({ return undefined; } + const token = jwt.sign({ id: user.id }, process.env.AUTH_SECRET); + await ctx.db.verificationToken.create({ data: { userId: user.id, @@ -257,8 +261,13 @@ export const authRouter = createTRPCRouter({ where: { expires: { lt: new Date() } }, }); + const jwtDecoded = decodeJwt(input.token); + if (!jwtDecoded?.id) { + throw new TRPCError({ code: 'BAD_REQUEST' }); + } + const verificationToken = await ctx.db.verificationToken.findUnique({ - where: { token: input.token }, + where: { token: input.token, userId: jwtDecoded.id }, }); if (!verificationToken) { diff --git a/src/server/auth.ts b/src/server/auth.ts index 7bf20aba8..c13b5cffd 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -1,3 +1,4 @@ +import { TRPCError } from '@trpc/server'; import jwt from 'jsonwebtoken'; import { cookies, headers } from 'next/headers'; @@ -19,9 +20,9 @@ export const getServerAuthSession = async () => { return null; } - const jwtDecoded = jwt.verify(token, process.env.AUTH_SECRET); + const jwtDecoded = decodeJwt(token); - if (!jwtDecoded || typeof jwtDecoded !== 'object' || !('id' in jwtDecoded)) { + if (!jwtDecoded?.id) { return null; } @@ -38,3 +39,11 @@ export const getServerAuthSession = async () => { }, }); }; + +export const decodeJwt = (token: string) => { + const jwtDecoded = jwt.verify(token, process.env.AUTH_SECRET); + if (!jwtDecoded || typeof jwtDecoded !== 'object' || !('id' in jwtDecoded)) { + return null; + } + return jwtDecoded; +};