diff --git a/package.json b/package.json index e7e4c4a..fe2f208 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "react-dom": "^18", "sonner": "^1.4.41", "tailwind-merge": "^2.2.2", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.6" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/auth/signIn/page.tsx b/src/app/auth/signIn/page.tsx index 2e7b28a..69e5e0f 100644 --- a/src/app/auth/signIn/page.tsx +++ b/src/app/auth/signIn/page.tsx @@ -5,9 +5,16 @@ import Link from "next/link"; import { useFormState } from "react-dom"; import { login } from "@/lib/auth/actions"; import SubmitButton from "@/components/app/SubmitButton/SubmitButton"; +import { useEffect } from "react"; +import { toast } from "sonner"; export default function Page() { - const [, formAction] = useFormState(login, null); + const [formData, formAction] = useFormState(login, null); + useEffect(() => { + if (formData?.error) { + toast.error(formData.error) + } + }, [formData]) return (
diff --git a/src/lib/auth/actions.ts b/src/lib/auth/actions.ts index 9e88d8b..82c9e78 100644 --- a/src/lib/auth/actions.ts +++ b/src/lib/auth/actions.ts @@ -6,6 +6,7 @@ import { redirect } from "next/navigation"; import prisma from "../db"; import { Argon2id } from "oslo/password"; import { generateId } from "lucia"; +import { accountSchema } from "./zod"; export async function logout() { const { session } = await validateRequest(); @@ -16,25 +17,13 @@ export async function logout() { } export async function login(prev: any, data: FormData) { - const username = data.get("username"); - if ( - typeof username !== "string" || - username.length < 3 || - username.length > 31 || - !/^[a-z0-9_-]+$/.test(username) - ) { + const checkSchema = await accountSchema.safeParseAsync(Object.fromEntries(data.entries())) + if (!checkSchema.success) return { - error: "Invalid username", + error: `From ${checkSchema.error.errors[0].path[0]}: ${checkSchema.error.errors[0].message}`, success: false, }; - } - const password = data.get("password"); - if (typeof password !== "string" || password.length < 6 || password.length > 255) { - return { - error: "Invalid password", - success: false, - }; - } + const { username, password } = checkSchema.data; const existingUser = await prisma.user.findUnique({ where: { @@ -72,35 +61,13 @@ export async function login(prev: any, data: FormData) { } export async function signup(prev: any, formData: FormData): Promise { - "use server"; - const username = formData.get("username"); - console.log(username) - // username must be between 4 ~ 31 characters, and only consists of lowercase letters, 0-9, -, and _ - // keep in mind some database (e.g. mysql) are case insensitive - if ( - typeof username !== "string" || - username.length < 3 || - username.length > 31 || - !/^[a-z0-9_-]+$/.test(username) - ) { + const checkSchema = await accountSchema.safeParseAsync(Object.fromEntries(formData.entries())) + if (!checkSchema.success) return { - error: "Invalid username", + error: `From ${checkSchema.error.errors[0].path[0]}: ${checkSchema.error.errors[0].message}`, success: false, }; - } - if (await prisma.user.findUnique({ where: { username: username } })) { - return { - error: "Username is already taken", - success: false, - }; - } - const password = formData.get("password"); - if (typeof password !== "string" || password.length < 6 || password.length > 255) { - return { - error: "Invalid password", - success: false, - }; - } + const { username, password } = checkSchema.data; const hashedPassword = await new Argon2id().hash(password); const userId = generateId(15); diff --git a/src/lib/auth/zod.ts b/src/lib/auth/zod.ts new file mode 100644 index 0000000..9d3af86 --- /dev/null +++ b/src/lib/auth/zod.ts @@ -0,0 +1,8 @@ +'use server' + +import { z } from 'zod' + +export const accountSchema = z.object({ + username: z.string().min(3, { message: 'Mininum 3 characters' }).max(31, { message: 'Maximum 31 characters' }).regex(/^[a-z0-9_-]+$/, { message: 'Only characters from a-z, 0-9, underscores and dashes' }), + password: z.string().min(6, { message: 'Minimum 6 characters' }).max(255, { message: 'Maximum 255 characters' }), +}) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6b7bd78..aef9af9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3392,3 +3392,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.23.6: + version "3.23.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.6.tgz#c08a977e2255dab1fdba933651584a05fcbf19e1" + integrity sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==