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(),
|
name: text('name').notNull(),
|
||||||
email: text('email').notNull().unique(),
|
email: text('email').notNull().unique(),
|
||||||
password: text('password').notNull(),
|
password: text('password').notNull(),
|
||||||
|
salt: text('salt').notNull(),
|
||||||
created_at: timestamp('created_at').defaultNow()
|
created_at: timestamp('created_at').defaultNow()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,86 @@
|
|||||||
import { db } from '$lib/server/db';
|
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 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 = {
|
export const actions = {
|
||||||
default: async ({ request }) => {
|
default: async ({ request, cookies }) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const name = formData.get('name');
|
const name = formData.get('name')?.toString();
|
||||||
const email = formData.get('email');
|
const email = formData.get('email')?.toString();
|
||||||
const password = formData.get('password');
|
const password = formData.get('password')?.toString();
|
||||||
|
|
||||||
// TODO: Implement data validation.
|
const parseResult = userSchema.safeParse({ name, email, password });
|
||||||
|
|
||||||
const userRecord = await db.insert(userTable).values({
|
if (!parseResult.success) {
|
||||||
name: name as string,
|
return fail(400, {
|
||||||
email: email as string,
|
data: { name, email },
|
||||||
password: password as string
|
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();
|
}).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;
|
} 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">
|
<form method="post">
|
||||||
<input type="text" name="name" placeholder="Name" required />
|
<input type="text" name="name" placeholder="Name" required />
|
||||||
<input type="email" name="email" placeholder="Email" required />
|
<input type="email" name="email" placeholder="Email" required />
|
||||||
|
|||||||
Reference in New Issue
Block a user