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 bcryptjs from "bcryptjs";
import { validatePassword } from "@utils/validation";
import { SignJWT } from "jose"; import { SignJWT } from "jose";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { env } from "@utils/env"; import { env } from "@utils/env";
import { prisma } from "@utils/prisma"; import { prisma } from "@utils/prisma";
import { passwordStrengthCheck } from "@utils/validation";
// todo check email doesn't already exist
export async function POST(req: Request) { export async function POST(req: Request) {
try { try {
const { email, password, name } = await req.json(); 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 }); 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") { try {
return NextResponse.json({ message: "Your password is shorter than 8 characters" }, { status: 400 }); const newUser = await prisma.user.create({
} else if (passwordCheckResult === "long") { data: {
return NextResponse.json({ message: "Your password is longer than 16 characters" }, { status: 400 }); name,
} else if (passwordCheckResult === "no lower") { email,
return NextResponse.json({ message: "Your password must contain a lowercase letters" }, { status: 400 }); passwordHash: await bcryptjs.hash(password, 10),
} 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),
},
});
// Link orders with matching email to the new user // Link orders with matching email to the new user
await prisma.order.updateMany({ await prisma.order.updateMany({
where: { where: {
email: email, email: email,
userId: null, // Only update orders not already linked to a user userId: null, // Only update orders not already linked to a user
}, },
data: { data: {
userId: newUser.id, userId: newUser.id,
}, },
}); });
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: newUser.id }, where: { id: newUser.id },
include: { include: {
earthquakes: true, earthquakes: true,
observatories: true, observatories: true,
artefacts: true, artefacts: true,
purchasedOrders: true, purchasedOrders: true,
requests: true, requests: true,
scientist: { scientist: {
include: { include: {
superior: true, superior: true,
subordinates: true, subordinates: true,
},
}, },
}, },
}); },
const { passwordHash, ...userSansHash } = user!; });
const { passwordHash, ...userSansHash } = user!;
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const token = await new SignJWT({ userId: user!.id }) const token = await new SignJWT({ userId: user!.id })
.setProtectedHeader({ alg: "HS256" }) .setProtectedHeader({ alg: "HS256" })
.setExpirationTime("2w") .setExpirationTime("2w")
.sign(secret); .sign(secret);
const response = NextResponse.json({ message: "Account Created", user: userSansHash }, { status: 201 }); const response = NextResponse.json({ message: "Account Created", user: userSansHash }, { status: 201 });
response.cookies.set("jwt", token, { response.cookies.set("jwt", token, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === "production", secure: process.env.NODE_ENV === "production",
sameSite: "strict", sameSite: "strict",
maxAge: 3600 * 168 * 2, // 2 weeks maxAge: 3600 * 168 * 2, // 2 weeks
path: "/", path: "/",
}); });
return response; return response;
} catch (error) { } catch (error) {
console.error("Error creating user:", error); console.error("Error creating user:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
} }
} catch (error) { } catch (error) {
console.error("Error in signup endpoint:", 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 { prisma } from "@utils/prisma";
import { apiAuthMiddleware } from "@utils/apiAuthMiddleware"; import { apiAuthMiddleware } from "@utils/apiAuthMiddleware";
import { passwordStrengthCheck } from "@utils/validation"; import { validatePassword } from "@utils/validation";
export async function POST(req: Request) { export async function POST(req: Request) {
try { try {
@ -34,25 +34,12 @@ export async function POST(req: Request) {
} }
} }
// todo move to dedicated function
// Validate password strength if provided // Validate password strength if provided
let passwordHash = user.passwordHash; let passwordHash = user.passwordHash;
if (password) { if (password) {
const passwordCheckResult = await passwordStrengthCheck(password); const passwordCheckResult = validatePassword(password);
if (passwordCheckResult === "short") { if ("message" in passwordCheckResult) {
return NextResponse.json({ message: "Password is shorter than 8 characters" }, { status: 400 }); return NextResponse.json({ message: passwordCheckResult.message }, { status: passwordCheckResult.status });
} 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 });
} }
passwordHash = await bcryptjs.hash(password, 10); 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) { if (password.length < 8) {
return "short"; return "short";
} else if (password.length > 16) { } else if (password.length > 16) {
@ -21,3 +21,24 @@ export async function passwordStrengthCheck(password: string): Promise<string> {
} }
return "end of function"; 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 {};
}
}