feat: enhance user signup process with password hashing and session management

This commit is contained in:
2025-12-01 21:42:47 +01:00
parent 76041eb7f8
commit c61129c828
3 changed files with 89 additions and 15 deletions

View File

@@ -5,6 +5,7 @@ export const userTable = pgTable('user', {
name: text('name').notNull(),
email: text('email').notNull().unique(),
password: text('password').notNull(),
salt: text('salt').notNull(),
created_at: timestamp('created_at').defaultNow()
});

View File

@@ -1,22 +1,86 @@
import { db } from '$lib/server/db';
import { userTable } from '$lib/server/db/schema';
import { userTable, sessionTable } from '$lib/server/db/schema';
import { z } from 'zod';
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
import { randomBytes, scrypt } from 'crypto';
const userSchema = z.object({
name: z.string().min(3, "Username must be at least 3 characters long").max(32, "Username must be at most 32 characters long"),
email: z.email("Invalid email address"),
password: z.string().min(8, "Password must be at least 8 characters long")
});
export const _hash = async (password: string, keyLength = 32): Promise<{ hash: string; salt: string }> => {
return new Promise((resolve, reject) => {
const salt = randomBytes(16).toString("hex");
scrypt(password, salt, keyLength, (err, derivedKey) => {
if (err) reject(err);
resolve({ hash: derivedKey.toString("hex"), salt });
});
});
};
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const name = formData.get('name');
const email = formData.get('email');
const password = formData.get('password');
default: async ({ request, cookies }) => {
const formData = await request.formData();
const name = formData.get('name')?.toString();
const email = formData.get('email')?.toString();
const password = formData.get('password')?.toString();
// TODO: Implement data validation.
const parseResult = userSchema.safeParse({ name, email, password });
const userRecord = await db.insert(userTable).values({
name: name as string,
email: email as string,
password: password as string
}).returning();
if (!parseResult.success) {
return fail(400, {
data: { name, email },
errors: z.flattenError(parseResult.error).fieldErrors
});
}
// TODO: Handle post-signup logic (e.g., redirect, session creation).
}
try {
const hashedpw = await _hash(parseResult.data.password);
const newUser = await db.insert(userTable).values({
name: parseResult.data.name,
email: parseResult.data.email,
password: hashedpw.hash,
salt: hashedpw.salt
}).returning();
const sessionToken = randomBytes(32).toString("hex");
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
await db.insert(sessionTable).values({
user_id: newUser[0].id,
token: sessionToken,
expires_at: expiresAt
});
cookies.set('session', sessionToken, {
path: '/',
httpOnly: true,
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production',
expires: expiresAt
});
} catch (error) {
if (error instanceof Error && 'code' in error && error.code === '23505') {
return fail(400, {
data: { name, email },
errors: {
email: ["Email is already in use"]
}
});
}
console.error("Unexpected error during user registration:", error);
return fail(500, {
message: "An unexpected error occurred."
});
}
throw redirect(303, '/dashboard');
}
} satisfies Actions;

View File

@@ -1,6 +1,15 @@
<script lang="ts">
import { enhance } from '$app/forms';
import type { PageProps } from './$types';
let { form }: PageProps = $props();
/* ... */
</script>
<form method="post">
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit">Sign Up</button>
</form>
</form>