All files / apps/web/src/handlers/auth login.ts

100% Statements 17/17
100% Branches 8/8
100% Functions 2/2
100% Lines 17/17

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 73 74 75 76 77 78 79 80 81 82 83 84 85                      6x                         5x 5x       5x                               5x 1x 1x 1x           4x   4x 1x 1x 1x         3x       3x     3x 3x                        
import { TRPCError } from "@trpc/server";
import { z } from "zod";
 
import { AUTH_COOKIE } from "~app/utils/auth";
import { passwordSchema } from "~app/utils/validation";
import { getHash } from "~utils/server/crypto";
import { createAuthorizationSession } from "~web/handlers/auth/utils";
import { unauthProcedure } from "~web/handlers/trpc";
import { emailSchema } from "~web/handlers/validation";
import { setCookie } from "~web/utils/cookies";
 
export const procedure = unauthProcedure
	.meta({
		title: "Log in",
		description:
			"Authenticates an account by email and password and starts a new session.",
	})
	.input(
		z.strictObject({
			email: emailSchema,
			password: passwordSchema,
		}),
	)
	.mutation(async ({ input, ctx }) => {
		const { database } = ctx;
		const result = await database
			.selectFrom("users")
			.where("email", "=", input.email.lowercase)
			.innerJoin("peers", (qb) =>
				qb.onRef("peers.connectedUserId", "=", "users.id"),
			)
			.whereRef("peers.id", "=", "peers.connectedUserId")
			.select([
				"users.id as userId",
				"users.email",
				"users.passwordSalt",
				"users.passwordHash",
				"users.role",
				"peers.name",
				"users.confirmationToken",
				"users.avatarUrl",
			])
			.limit(1)
			.executeTakeFirst();
 
		if (!result) {
			const errorMessage = `Authentication of account "${input.email.original}" failed: account not found.`;
			ctx.logger.debug(errorMessage);
			throw new TRPCError({
				code: "UNAUTHORIZED",
				message: errorMessage,
			});
		}
		const isPasswordValid =
			(await getHash(input.password, result.passwordSalt)) ===
			result.passwordHash;
		if (!isPasswordValid) {
			const errorMessage = `Authentication of account "${input.email.original}" failed: password is wrong.`;
			ctx.logger.debug(errorMessage);
			throw new TRPCError({
				code: "UNAUTHORIZED",
				message: errorMessage,
			});
		}
		const { authToken, expirationDate } = await createAuthorizationSession(
			ctx,
			result.userId,
		);
		ctx.logger.debug(
			`Authentication of account "${input.email.original}" succeed.`,
		);
		setCookie(ctx, AUTH_COOKIE, authToken, { expires: expirationDate });
		return {
			user: {
				id: result.userId,
				verified: !result.confirmationToken,
				avatarUrl: result.avatarUrl || undefined,
				role: result.role ?? undefined,
			},
			peer: {
				name: result.name,
			},
		};
	});