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 });