From d46478a90d20166a0f7fda21247043be4fea5bc6 Mon Sep 17 00:00:00 2001 From: Baobeld Date: Fri, 20 Dec 2024 17:17:49 -0500 Subject: [PATCH] Improved form validation and feedback messages (#31) * form feedback * format --- messages/en.json | 9 ++++- src/routes/login/+page.server.ts | 67 ++++++++++++++++++++++---------- src/routes/login/+page.svelte | 16 +++++++- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/messages/en.json b/messages/en.json index 8651435..b3c3fd1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,4 +1,11 @@ { "$schema": "https://inlang.com/schema/inlang-message-format", - "greeting": "Hello {name}!" + "greeting": "Hello {name}!", + "email_required": "Email is required", + "password_required": "Password is required", + "name_required": "Name is required", + "email_incorrect": "Email is incorrect", + "password_incorrect": "Password is incorrect", + "email_inuse": "Email is already in use", + "user_not_found": "The user could not be found" } diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 0fea32f..e703154 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -1,31 +1,40 @@ +import { messages } from '$lib/i18n'; +import { email_inuse } from '$lib/paraglide/messages'; import { logger } from '$lib/server/logger'; -import { prisma } from '$lib/server/prisma'; -import { error, redirect, type Actions } from '@sveltejs/kit'; -import { Argon2id } from 'oslo/password'; import { auth } from '$lib/server/lucia.js'; +import { prisma } from '$lib/server/prisma'; +import { fail, redirect, type Actions } from '@sveltejs/kit'; +import { Argon2id } from 'oslo/password'; +import { string } from 'zod'; export const actions = { login: async (event) => { const form = await event.request.formData(); - if (!form.has('email')) { - return error(400, 'Email is a required form field!'); + const email = form.get('email'); + if (typeof email !== 'string') { + return fail(400, { email: { error: messages.email_required() } }); } + + const password = form.get('password') as string; + if (!password) { + return fail(400, { password: { error: messages.password_required() } }); + } + const user = await prisma.user.findUnique({ where: { - email: form.get('email') as string, + email: email, }, }); if (!user) { - logger.error('User not found! ${user}'); - return error(401); - } - const password = form.get('password') as string; - if (!password) { - return error(401, 'Password is required'); + logger.error(`User not found! ${email}`); + return fail(404, { email: { value: email, error: messages.user_not_found() } }); } + const validPassword = await new Argon2id().verify(user.password, password); if (!validPassword) { - return error(400, 'Password is incorrect!'); + return fail(400, { + password: { error: messages.password_incorrect() }, + }); } const session = await auth.createSession(user.id, []); const sessionCookie = auth.createSessionCookie(session.id); @@ -38,10 +47,29 @@ export const actions = { register: async (event) => { const form = await event.request.formData(); - if (!form.has('email') || !form.has('name') || !form.has('password')) { - return error(400); + if (!form.has('email')) { + return fail(400, { email: { error: messages.email_required() } }); } - const password = form.get('password') as string; + const { success, data: email, error } = string().email().safeParse(form.get('email')); + if (!success) { + logger.error(error); + return fail(400, { email: { value: email, error: messages.email_incorrect() } }); + } + + const password = form.get('password'); + if (typeof password !== 'string') { + return fail(400, { password: { error: messages.password_required() } }); + } + const name = form.get('name'); + if (typeof name !== 'string') { + return fail(400, { name: { error: messages.name_required() } }); + } + + const usersWithEmail = await prisma.user.count({ where: { email: email } }); + if (usersWithEmail !== 0) { + return fail(409, { email: { value: email, error: email_inuse() } }); + } + const hashedPassword = await new Argon2id().hash(password); const user = await prisma.user.create({ data: { @@ -50,15 +78,14 @@ export const actions = { password: hashedPassword, }, }); + const session = await auth.createSession(user.id.toString(), {}); const sessionCookie = auth.createSessionCookie(session.id); - if (!user) { - return error(500); - } event.cookies.set(sessionCookie.name, sessionCookie.value, { path: '/', maxAge: 120, }); - redirect(302, '/'); + + redirect(303, '/'); }, } satisfies Actions; diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index f32bb29..4978eaf 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -4,6 +4,8 @@ import Tabs from '$lib/components/Navigation/Tabs'; import { fade } from 'svelte/transition'; + let { form } = $props(); + let tab: 0 | 1 = $state(0); @@ -19,7 +21,14 @@ {/snippet} -{#snippet form(variant: 'login' | 'register')} +{#snippet alert(message: string)} + +{/snippet} + +{#snippet Form(variant: 'login' | 'register')}
@@ -42,9 +51,12 @@
+ {#if form} + {@render alert(Object.values(form)[0].error)} + {/if}
- {@render form(tab === 0 ? 'login' : 'register')} + {@render Form(tab === 0 ? 'login' : 'register')}