All files / apps/web/src/handlers/reset-password-intentions add.ts

100% Statements 16/16
100% Branches 6/6
100% Functions 2/2
100% Lines 16/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72                6x                       5x 5x           5x 1x         4x 4x     4x 1x         3x     3x               3x 1x         2x               2x 2x    
import { TRPCError } from "@trpc/server";
import { z } from "zod";
 
import { generateResetPasswordEmail } from "~web/email/utils";
import { unauthProcedure } from "~web/handlers/trpc";
import { MAX_INTENTIONS_AMOUNT, emailSchema } from "~web/handlers/validation";
import { getEmailClient } from "~web/providers/email";
 
export const procedure = unauthProcedure
	.meta({
		title: "Add reset password intention",
		description:
			"Sends a password reset email for a given email address, up to a daily limit of pending intentions.",
	})
	.input(
		z.strictObject({
			email: emailSchema,
		}),
	)
	.mutation(async ({ input, ctx }) => {
		const { database } = ctx;
		const user = await database
			.selectFrom("users")
			.select("id")
			.where("email", "=", input.email.lowercase)
			.limit(1)
			.executeTakeFirst();
		if (!user) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: `User "${input.email.original}" does not exist.`,
			});
		}
		const uuid: string = ctx.getUuid();
		const expirationDate = Temporal.Now.zonedDateTimeISO().add({
			days: 1,
		});
		if (!ctx.emailOptions.getActive()) {
			throw new TRPCError({
				code: "FORBIDDEN",
				message: `Currently password reset is not supported.`,
			});
		}
		const currentIntentions = await database
			.selectFrom("resetPasswordIntentions")
			.where((eb) =>
				eb("resetPasswordIntentions.userId", "=", user.id).and(
					"expiresTimestamp",
					">",
					Temporal.Now.zonedDateTimeISO(),
				),
			)
			.select("expiresTimestamp")
			.execute();
		if (currentIntentions.length >= MAX_INTENTIONS_AMOUNT) {
			throw new TRPCError({
				code: "FORBIDDEN",
				message: `Maximum amount of intentions per day is ${MAX_INTENTIONS_AMOUNT}, please try later.`,
			});
		}
		await database
			.insertInto("resetPasswordIntentions")
			.values({
				userId: user.id,
				expiresTimestamp: expirationDate,
				token: uuid,
			})
			.executeTakeFirst();
		const data = await generateResetPasswordEmail(uuid, ctx);
		await getEmailClient(ctx).send({ address: input.email.lowercase, ...data });
	});