diff --git a/apps/client/pages/api/auth/reset-password.ts b/apps/client/pages/api/auth/reset-password.ts deleted file mode 100644 index 44255910..00000000 --- a/apps/client/pages/api/auth/reset-password.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import crypto from 'crypto' -import type { NextApiRequest, NextApiResponse } from 'next' - -let prismaInstance: PrismaClient | null = null - -function getPrismaInstance() { - if (!prismaInstance) { - prismaInstance = new PrismaClient() - } - return prismaInstance -} - -const prisma = getPrismaInstance() - -type ResponseData = { - message: string -} - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (!req.body.email) { - res.status(400).json({ message: 'No email provided.' }) - return - } - - const user = await prisma.authUser.findUnique({ - where: { - email: req.body.email, - }, - }) - - if (!user) { - // No user found, we don't want to expose this information - return res.status(200).json({ message: 'OK' }) - } - - const token = crypto.randomBytes(32).toString('hex') - await prisma.authPasswordResets.create({ - data: { - token, - email: req.body.email, - expires: new Date(Date.now() + 1000 * 60 * 10), // 10 minutes - }, - }) - - // 2. Send a password reset email - - res.status(200).json({ message: 'Hello from Next.js!' }) -} diff --git a/apps/server/src/app/lib/endpoint.ts b/apps/server/src/app/lib/endpoint.ts index 7fba5f3c..2d487ff4 100644 --- a/apps/server/src/app/lib/endpoint.ts +++ b/apps/server/src/app/lib/endpoint.ts @@ -25,6 +25,7 @@ import { AccountService, AccountConnectionService, AuthUserService, + AuthPasswordResetService, UserService, EmailService, AccountQueryService, @@ -209,6 +210,14 @@ const accountService = new AccountService( const authUserService = new AuthUserService(logger.child({ service: 'AuthUserService' }), prisma) +// auth-password-reset + +const authPasswordResetService = new AuthPasswordResetService( + logger.child({ service: 'AuthPasswordResetService' }), + prisma, + emailService +) + // user const userService = new UserService( @@ -328,6 +337,7 @@ export async function createContext(req: Request) { holdingService, accountConnectionService, authUserService, + authPasswordResetService, userService, valuationService, institutionService, diff --git a/apps/server/src/app/routes/users.router.ts b/apps/server/src/app/routes/users.router.ts index 2e6c1f88..a8d2b565 100644 --- a/apps/server/src/app/routes/users.router.ts +++ b/apps/server/src/app/routes/users.router.ts @@ -388,6 +388,39 @@ router.delete( }) ) +router.post( + '/request-new-password', + endpoint.create({ + input: z.object({ + email: z.string().email(), + }), + resolve: async ({ ctx, input }) => { + if (ctx.user) return + await ctx.authPasswordResetService.create(input.email) + }, + }) +) + +router.post( + '/reset-password/:token/:email', + endpoint.create({ + input: z.object({ + // TODO: bring en par with required password schema + // (1 lowercase, 1 uppercase, 1 special char) + newPassword: z.string().min(8).max(64), + confirmPassword: z.string().min(8).max(64), + }), + resolve: async ({ ctx, input, req }) => { + if (ctx.user) return + await ctx.authPasswordResetService.resetPassword({ + token: req.params.token, + newPassword: input.newPassword, + email: req.params.email, + }) + }, + }) +) + router.delete( '/:id', endpoint.create({ diff --git a/libs/server/features/src/auth-password-reset/auth-password-reset.service.ts b/libs/server/features/src/auth-password-reset/auth-password-reset.service.ts index daa04d04..40119591 100644 --- a/libs/server/features/src/auth-password-reset/auth-password-reset.service.ts +++ b/libs/server/features/src/auth-password-reset/auth-password-reset.service.ts @@ -1,6 +1,6 @@ import type { PrismaClient } from '@prisma/client' import type { Logger } from 'winston' -import type { EmailService } from '../email' +import type { IEmailService } from '../email' import bcrypt from 'bcrypt' type ResetPasswordData = { @@ -13,11 +13,11 @@ export interface IAuthPasswordResetService { resetPassword(data: ResetPasswordData): Promise } -export class AuthPasswordResetsService implements IAuthPasswordResetService { +export class AuthPasswordResetService implements IAuthPasswordResetService { constructor( private readonly logger: Logger, - private readonly emailService: EmailService, - private readonly prisma: PrismaClient + private readonly prisma: PrismaClient, + private readonly emailService: IEmailService ) {} async create(email: string): Promise { @@ -50,7 +50,8 @@ export class AuthPasswordResetsService implements IAuthPasswordResetService { subject: 'Reset your password', to: email, // TODO: Use a template - textBody: `Click here to reset your password: ${process.env.NEXTAUTH_URL}/auth/reset-password?token=${token}&email=${email}`, + textBody: `Click here to reset your password: + ${process.env.NEXTAUTH_URL}/auth/reset-password/${token}/${email}`, }) return null