diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index eab1e7a..ce47e16 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -33,7 +33,7 @@ export async function validateSessionToken(token: string) { const [result] = await db .select({ // Adjust user table here to tweak returned data - user: { id: table.user.id, username: table.user.username }, + user: { id: table.user.id, email: table.user.email }, session: table.session }) .from(table.session) diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 653d167..cf493bd 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -1,45 +1,45 @@ -import { pgTable, serial, integer, text, timestamp } from 'drizzle-orm/pg-core'; +import {pgTable, serial, integer, text, timestamp} from 'drizzle-orm/pg-core'; export const user = pgTable('user', { - id: text('id').primaryKey(), - age: integer('age'), //나이 - username: text('username').notNull().unique(), // username 이 사용자가 회원가입 할때 id 임 - nickname: text('nickname'), // 별칭 - helloword: text('helloword'), // 인사말, 소개말 - avatar: text('avatar'), // 아바타 파일경로 - email: text('email'), // 이메일주소 - gender: text('gender'), // 성별 - passwordHash: text('password_hash').notNull(), - createdAt: timestamp(), - updatedAt: timestamp(), + id: text('id').primaryKey(), + age: integer('age'), //나이 + username: text('username'), + nickname: text('nickname'), // 별칭 + helloword: text('helloword'), // 인사말, 소개말 + avatar: text('avatar'), // 아바타 파일경로 + email: text('email').notNull().unique(), // 이메일주소 이것을 아이디로 사용하도록 변경하자 + gender: text('gender'), // 성별 + passwordHash: text('password_hash').notNull(), + createdAt: timestamp(), + updatedAt: timestamp(), }); export const session = pgTable('session', { - id: text('id').primaryKey(), - userId: text('user_id').notNull().references(() => user.id), - expiresAt: timestamp('expires_at', { withTimezone: true, mode: 'date' }).notNull(), - createdAt: timestamp(), - updatedAt: timestamp(), + id: text('id').primaryKey(), + userId: text('user_id').notNull().references(() => user.id), + expiresAt: timestamp('expires_at', {withTimezone: true, mode: 'date'}).notNull(), + createdAt: timestamp(), + updatedAt: timestamp(), }); export const post = pgTable('post', { - id: text('id').primaryKey(), - subject: text('subject'), - slug: text('subject'), - content: text('subject'), - author: text('author'), - status: text('status'), // draft, published 등등.. - category: text('category'), - createdAt: timestamp(), - updatedAt: timestamp(), + id: text('id').primaryKey(), + subject: text('subject'), + slug: text('subject'), + content: text('subject'), + author: text('author'), + status: text('status'), // draft, published 등등.. + category: text('category'), + createdAt: timestamp(), + updatedAt: timestamp(), }); export const hierarchy = pgTable('hierarchy', { - id: text('id').primaryKey(), - name: text('subject'), - parent_id: text('subject'), - createdAt: timestamp(), - updatedAt: timestamp(), + id: text('id').primaryKey(), + name: text('subject'), + parent_id: text('subject'), + createdAt: timestamp(), + updatedAt: timestamp(), }); export type Session = typeof session.$inferSelect; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3153e95..e98e847 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,7 @@ - + {@render children()} diff --git a/src/routes/login/+page.server.ts b/src/routes/login/+page.server.ts index 772429e..7ccfa69 100644 --- a/src/routes/login/+page.server.ts +++ b/src/routes/login/+page.server.ts @@ -30,17 +30,17 @@ export const actions: Actions = { }); } - const userId = form.data.userId; + const email = form.data.email; const password = form.data.password; const results = await db .select() .from(table.user) - .where(eq(table.user.username, userId)); + .where(eq(table.user.email, email)); const existingUser = results.at(0); if (!existingUser) { - return setError(form, 'userId', '존재하지 않는 아이디 입니다.'); + return setError(form, 'email', '등록된 이메일이 아닙니다.'); } const validPassword = await verify(existingUser.passwordHash, password, { diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index cf0bbd7..0afee2b 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -1,10 +1,11 @@ -
-
- -
+
+
+ +
diff --git a/src/routes/login/login-form.svelte b/src/routes/login/login-form.svelte index 1ddfdae..21afe12 100644 --- a/src/routes/login/login-form.svelte +++ b/src/routes/login/login-form.svelte @@ -26,7 +26,7 @@ -
+
LOGIN @@ -36,11 +36,11 @@
- + {#snippet children({props})} - UserId - + Email + {/snippet} @@ -50,7 +50,7 @@ {#snippet children({props})} - password + Password
이메일 인증을 통한 - 회원가입 + 회원가입
diff --git a/src/routes/login/schema.ts b/src/routes/login/schema.ts index 56432a5..846d2bb 100644 --- a/src/routes/login/schema.ts +++ b/src/routes/login/schema.ts @@ -2,10 +2,8 @@ import { z } from "zod"; export const formSchema = z.object({ // User ID (formerly 'user') - userId: z.string() - .min(3, "최소 3자 이상이어야 합니다.") // A common minimum for IDs - .max(50, "최대 50자 이하이어야 합니다.") // Reasonable maximum length - .regex(/^[a-zA-Z0-9_.-]+$/, "영문, 숫자, '_', '.', '-'만 포함할 수 있습니다.") // Restrict characters + email: z.string() + .email("이메일주소 형식이 아닙니다.") .trim(), // Remove leading/trailing whitespace // Password diff --git a/src/routes/register/+layout.svelte b/src/routes/register/+layout.svelte new file mode 100644 index 0000000..10d935d --- /dev/null +++ b/src/routes/register/+layout.svelte @@ -0,0 +1,5 @@ + + +{@render children()} \ No newline at end of file diff --git a/src/routes/register/+page.server.ts b/src/routes/register/+page.server.ts new file mode 100644 index 0000000..7ccfa69 --- /dev/null +++ b/src/routes/register/+page.server.ts @@ -0,0 +1,67 @@ +import {hash, verify} from '@node-rs/argon2'; +import {encodeBase32LowerCase} from '@oslojs/encoding'; +import {redirect} from '@sveltejs/kit'; +import {eq} from 'drizzle-orm'; +import * as auth from '$lib/server/auth'; +import {db} from '$lib/server/db'; +import * as table from '$lib/server/db/schema'; +import type {Actions, PageServerLoad} from './$types'; +import { setError, superValidate , fail} from 'sveltekit-superforms'; +import {zod} from "sveltekit-superforms/adapters"; +import {formSchema} from "./schema"; + + + +export const load: PageServerLoad = async (event) => { + if (event.locals.user) { + return redirect(302, '/demo/lucia'); + } + return { + form: await superValidate(zod(formSchema)), + }; +}; + +export const actions: Actions = { + login: async (event) => { + const form = await superValidate(event, zod(formSchema)); + if (!form.valid) { + return fail(400, { + form, + }); + } + + const email = form.data.email; + const password = form.data.password; + + const results = await db + .select() + .from(table.user) + .where(eq(table.user.email, email)); + + const existingUser = results.at(0); + if (!existingUser) { + return setError(form, 'email', '등록된 이메일이 아닙니다.'); + } + + const validPassword = await verify(existingUser.passwordHash, password, { + memoryCost: 19456, + timeCost: 2, + outputLen: 32, + parallelism: 1, + }); + if (!validPassword) { + return setError(form, 'password', '비밀번호가 일치하지 않습니다.'); + } + + const sessionToken = auth.generateSessionToken(); + const session = await auth.createSession(sessionToken, existingUser.id); + auth.setSessionTokenCookie(event, sessionToken, session.expiresAt); + + return redirect(302, '/demo/lucia'); + + }, +}; + + + + diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte new file mode 100644 index 0000000..e87a321 --- /dev/null +++ b/src/routes/register/+page.svelte @@ -0,0 +1,10 @@ + +
+
+ +
+
diff --git a/src/routes/register/register-form.svelte b/src/routes/register/register-form.svelte new file mode 100644 index 0000000..28336a9 --- /dev/null +++ b/src/routes/register/register-form.svelte @@ -0,0 +1,92 @@ + + +
+ + + 회원가입 + + Register a new account + + + +
+
+
+
+ + + {#snippet children({props})} + 이메일 + + {/snippet} + + + +
+
+ + + {#snippet children({props})} + 비밀번호 + + {/snippet} + + + + + + {#snippet children({props})} + 비밀번호 확인 + + {/snippet} + + + +
+ 회원가입 +
+ +
+
+
+
+ {#if dev} + + {/if} +
diff --git a/src/routes/register/schema.ts b/src/routes/register/schema.ts new file mode 100644 index 0000000..004a954 --- /dev/null +++ b/src/routes/register/schema.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; +import {integer, text, timestamp} from "drizzle-orm/pg-core"; + +export const formSchema = z.object({ + // User ID (formerly 'user') + email: z.string() + .email("이메일주소 형식이 아닙니다.") + .trim(), // Remove leading/trailing whitespace + + // Password + password: z.string() + .min(8, "최소 8자 이상이어야 합니다.") + .max(100, "최대 100자 이하이어야 합니다.") // Reasonable maximum length + .regex(/[a-z]/, "최소 하나의 소문자를 포함해야 합니다.") + .regex(/[A-Z]/, "최소 하나의 대문자를 포함해야 합니다.") + .regex(/[0-9]/, "최소 하나의 숫자를 포함해야 합니다.") + .regex(/[^a-zA-Z0-9]/, "최소 하나의 특수문자를 포함해야 합니다.") // For common special characters + .trim(), + passwordConfirm: z.string(), +}).refine((data) => data.password === data.passwordConfirm, { + message: "비밀번호가 일치하지 않습니다", + path: ["passwordConfirm"], // passwordConfirm 필드에 에러를 표시 +}); + +export type FormSchema = typeof formSchema; \ No newline at end of file