/ server / api / auth / register.post.ts
register.post.ts
  1  import bcrypt from "bcrypt";
  2  import { encrypt } from "paseto-ts/v4";
  3  import { prisma } from "~~/prisma/prisma";
  4  import { z } from "zod";
  5  import { handleApiError} from "~~/server/utils/logging";
  6  
  7  const passwordSchema = z
  8    .string()
  9    .min(12, "Password must be at least 12 characters")
 10    .regex(/[A-Z]/, "Password must contain at least one uppercase letter")
 11    .regex(/[a-z]/, "Password must contain at least one lowercase letter")
 12    .regex(/[0-9]/, "Password must contain at least one number")
 13    .regex(
 14      /[^A-Za-z0-9]/,
 15      "Password must contain at least one special character"
 16    );
 17  
 18  export default defineEventHandler(async (event) => {
 19    const config = useRuntimeConfig();
 20  
 21    if (config.disableRegistering === "true") {
 22      throw createError({
 23        statusCode: 403,
 24        message: "Registration is currently disabled",
 25      });
 26    }
 27  
 28    const body = await readBody(event);
 29  
 30    if (
 31      !body.email ||
 32      !body.password ||
 33      typeof body.email !== "string" ||
 34      typeof body.password !== "string"
 35    ) {
 36      throw handleApiError(
 37        400,
 38        "Registration attempt with missing email or password.",
 39        "Email and password are required."
 40      );
 41    }
 42  
 43    try {
 44      const passwordValidation = passwordSchema.safeParse(body.password);
 45      if (!passwordValidation.success) {
 46        const errorDetail = `Password validation failed for email ${body.email}: ${passwordValidation.error.errors[0].message}`;
 47        throw handleApiError(
 48          400,
 49          errorDetail,
 50          passwordValidation.error.errors[0].message
 51        );
 52      }
 53  
 54      const existingUser = await prisma.user.findUnique({
 55        where: { email: body.email },
 56      });
 57  
 58      if (existingUser) {
 59        throw handleApiError(
 60          409,
 61          `Registration attempt with existing email: ${body.email}.`
 62        );
 63      }
 64  
 65      const saltRounds = 10;
 66      const passwordHash = await bcrypt.hash(body.password, saltRounds);
 67  
 68      const user = await prisma.user.create({
 69        data: {
 70          email: body.email,
 71          passwordHash,
 72        },
 73      });
 74  
 75      const token = encrypt(config.pasetoKey, {
 76        userId: user.id,
 77        email: user.email,
 78        exp: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
 79      });
 80  
 81      setCookie(event, "ziit_session", token, {
 82        expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
 83        path: "/",
 84        httpOnly: true,
 85        secure: true,
 86        sameSite: "strict",
 87      });
 88  
 89      return sendRedirect(event, "/");
 90    } catch (error: any) {
 91      if (error && typeof error === "object" && error.statusCode) {
 92        throw error;
 93      }
 94      const detailedMessage =
 95        error instanceof Error
 96          ? error.message
 97          : "An unknown error occurred during registration.";
 98      throw handleApiError(
 99        500,
100        `Registration failed: ${detailedMessage}`,
101        "An unexpected error occurred during registration. Please try again."
102      );
103    }
104  });