Moved password checking to dedicated function

This commit is contained in:
Tim Howitz 2025-06-03 15:20:52 +01:00
parent 1c08f0364c
commit b0f519d058
3 changed files with 81 additions and 89 deletions

View File

@ -1,14 +1,11 @@
import bcryptjs from "bcryptjs";
import { validatePassword } from "@utils/validation";
import { SignJWT } from "jose";
import { NextResponse } from "next/server";
import { env } from "@utils/env";
import { prisma } from "@utils/prisma";
import { passwordStrengthCheck } from "@utils/validation";
// todo check email doesn't already exist
export async function POST(req: Request) {
try {
const { email, password, name } = await req.json();
@ -23,80 +20,67 @@ export async function POST(req: Request) {
return NextResponse.json({ message: "Sorry, this email is already in use" }, { status: 409 });
}
const passwordCheckResult = await passwordStrengthCheck(password);
const passwordCheckResult = validatePassword(password);
if ("message" in passwordCheckResult) {
return NextResponse.json({ message: passwordCheckResult.message }, { status: passwordCheckResult.status });
}
if (passwordCheckResult === "short") {
return NextResponse.json({ message: "Your password is shorter than 8 characters" }, { status: 400 });
} else if (passwordCheckResult === "long") {
return NextResponse.json({ message: "Your password is longer than 16 characters" }, { status: 400 });
} else if (passwordCheckResult === "no lower") {
return NextResponse.json({ message: "Your password must contain a lowercase letters" }, { status: 400 });
} else if (passwordCheckResult === "no upper") {
return NextResponse.json({ message: "Your password must contain a uppercase letters" }, { status: 400 });
} else if (passwordCheckResult === "no digit") {
return NextResponse.json({ message: "Your password must contain a number" }, { status: 400 });
} else if (passwordCheckResult === "no special") {
return NextResponse.json({ message: "Your password must contain a special character (!@#$%^&*)" }, { status: 400 });
} else if (passwordCheckResult === "end of function") {
return NextResponse.json({ message: "Password check script failure" }, { status: 500 });
} else {
try {
const newUser = await prisma.user.create({
data: {
name,
email,
passwordHash: await bcryptjs.hash(password, 10),
},
});
try {
const newUser = await prisma.user.create({
data: {
name,
email,
passwordHash: await bcryptjs.hash(password, 10),
},
});
// Link orders with matching email to the new user
await prisma.order.updateMany({
where: {
email: email,
userId: null, // Only update orders not already linked to a user
},
data: {
userId: newUser.id,
},
});
// Link orders with matching email to the new user
await prisma.order.updateMany({
where: {
email: email,
userId: null, // Only update orders not already linked to a user
},
data: {
userId: newUser.id,
},
});
const user = await prisma.user.findUnique({
where: { id: newUser.id },
include: {
earthquakes: true,
observatories: true,
artefacts: true,
purchasedOrders: true,
requests: true,
scientist: {
include: {
superior: true,
subordinates: true,
},
const user = await prisma.user.findUnique({
where: { id: newUser.id },
include: {
earthquakes: true,
observatories: true,
artefacts: true,
purchasedOrders: true,
requests: true,
scientist: {
include: {
superior: true,
subordinates: true,
},
},
});
const { passwordHash, ...userSansHash } = user!;
},
});
const { passwordHash, ...userSansHash } = user!;
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const token = await new SignJWT({ userId: user!.id })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("2w")
.sign(secret);
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const token = await new SignJWT({ userId: user!.id })
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("2w")
.sign(secret);
const response = NextResponse.json({ message: "Account Created", user: userSansHash }, { status: 201 });
response.cookies.set("jwt", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 3600 * 168 * 2, // 2 weeks
path: "/",
});
return response;
} catch (error) {
console.error("Error creating user:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
const response = NextResponse.json({ message: "Account Created", user: userSansHash }, { status: 201 });
response.cookies.set("jwt", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 3600 * 168 * 2, // 2 weeks
path: "/",
});
return response;
} catch (error) {
console.error("Error creating user:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
} catch (error) {
console.error("Error in signup endpoint:", error);

View File

@ -5,7 +5,7 @@ import { env } from "@utils/env";
import { prisma } from "@utils/prisma";
import { apiAuthMiddleware } from "@utils/apiAuthMiddleware";
import { passwordStrengthCheck } from "@utils/validation";
import { validatePassword } from "@utils/validation";
export async function POST(req: Request) {
try {
@ -34,25 +34,12 @@ export async function POST(req: Request) {
}
}
// todo move to dedicated function
// Validate password strength if provided
let passwordHash = user.passwordHash;
if (password) {
const passwordCheckResult = await passwordStrengthCheck(password);
if (passwordCheckResult === "short") {
return NextResponse.json({ message: "Password is shorter than 8 characters" }, { status: 400 });
} else if (passwordCheckResult === "long") {
return NextResponse.json({ message: "Password is longer than 16 characters" }, { status: 400 });
} else if (passwordCheckResult === "no lower") {
return NextResponse.json({ message: "Password must contain lowercase letters" }, { status: 400 });
} else if (passwordCheckResult === "no upper") {
return NextResponse.json({ message: "Password must contain uppercase letters" }, { status: 400 });
} else if (passwordCheckResult === "no digit") {
return NextResponse.json({ message: "Password must contain a number" }, { status: 400 });
} else if (passwordCheckResult === "no special") {
return NextResponse.json({ message: "Password must contain a special character (!@#$%^&*)" }, { status: 400 });
} else if (passwordCheckResult === "end of function") {
return NextResponse.json({ message: "Password check script failure" }, { status: 500 });
const passwordCheckResult = validatePassword(password);
if ("message" in passwordCheckResult) {
return NextResponse.json({ message: passwordCheckResult.message }, { status: passwordCheckResult.status });
}
passwordHash = await bcryptjs.hash(password, 10);
}

View File

@ -1,4 +1,4 @@
export async function passwordStrengthCheck(password: string): Promise<string> {
export function passwordStrengthCheck(password: string): string {
if (password.length < 8) {
return "short";
} else if (password.length > 16) {
@ -21,3 +21,24 @@ export async function passwordStrengthCheck(password: string): Promise<string> {
}
return "end of function";
}
export function validatePassword(password: string) {
const result = passwordStrengthCheck(password);
switch (result) {
case "short":
return { message: "Password is shorter than 8 characters", status: 400 };
case "long":
return { message: "Password is longer than 16 characters", status: 400 };
case "no lower":
return { message: "Password must contain lowercase letters", status: 400 };
case "no upper":
return { message: "Password must contain uppercase letters", status: 400 };
case "no digit":
return { message: "Password must contain a number", status: 400 };
case "no special":
return { message: "Password must contain a special character (!@#$%^&*)", status: 400 };
default:
return {};
}
}