feat: enhance user signup process with password hashing and session management
This commit is contained in:
@@ -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()
|
||||
});
|
||||
|
||||
|
||||
@@ -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 }) => {
|
||||
default: async ({ request, cookies }) => {
|
||||
const formData = await request.formData();
|
||||
const name = formData.get('name');
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
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
|
||||
if (!parseResult.success) {
|
||||
return fail(400, {
|
||||
data: { name, email },
|
||||
errors: z.flattenError(parseResult.error).fieldErrors
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// TODO: Handle post-signup logic (e.g., redirect, session creation).
|
||||
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;
|
||||
@@ -1,3 +1,12 @@
|
||||
<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 />
|
||||
|
||||
Reference in New Issue
Block a user