이것저것 수정
Some checks failed
main-branch-frovide/pipeline/head There was a failure building this commit
Some checks failed
main-branch-frovide/pipeline/head There was a failure building this commit
This commit is contained in:
parent
26b5707800
commit
82d4981c10
@ -1,138 +0,0 @@
|
|||||||
import type { PageServerLoad, Actions } from "./$types.js";
|
|
||||||
import { fail } from "@sveltejs/kit";
|
|
||||||
import { superValidate } from "sveltekit-superforms";
|
|
||||||
import { zod } from "sveltekit-superforms/adapters";
|
|
||||||
import { formSchema } from "./schema";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
|
||||||
return {
|
|
||||||
form: await superValidate(zod(formSchema)),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const actions: Actions = {
|
|
||||||
default: async (event) => {
|
|
||||||
const form = await superValidate(event, zod(formSchema));
|
|
||||||
if (!form.valid) {
|
|
||||||
return fail(400, {
|
|
||||||
form,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
form,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// import { hash, verify } from '@node-rs/argon2';
|
|
||||||
// import { encodeBase32LowerCase } from '@oslojs/encoding';
|
|
||||||
// import { fail, 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';
|
|
||||||
|
|
||||||
// export const load: PageServerLoad = async (event) => {
|
|
||||||
// if (event.locals.user) {
|
|
||||||
// return redirect(302, '/demo/lucia');
|
|
||||||
// }
|
|
||||||
// return {};
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const actions: Actions = {
|
|
||||||
// login: async (event) => {
|
|
||||||
// const formData = await event.request.formData();
|
|
||||||
// const username = formData.get('username');
|
|
||||||
// const password = formData.get('password');
|
|
||||||
|
|
||||||
// if (!validateUsername(username)) {
|
|
||||||
// return fail(400, { message: 'Invalid username (min 3, max 31 characters, alphanumeric only)' });
|
|
||||||
// }
|
|
||||||
// if (!validatePassword(password)) {
|
|
||||||
// return fail(400, { message: 'Invalid password (min 6, max 255 characters)' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const results = await db
|
|
||||||
// .select()
|
|
||||||
// .from(table.user)
|
|
||||||
// .where(eq(table.user.username, username));
|
|
||||||
|
|
||||||
// const existingUser = results.at(0);
|
|
||||||
// if (!existingUser) {
|
|
||||||
// return fail(400, { message: 'Incorrect username or password' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const validPassword = await verify(existingUser.passwordHash, password, {
|
|
||||||
// memoryCost: 19456,
|
|
||||||
// timeCost: 2,
|
|
||||||
// outputLen: 32,
|
|
||||||
// parallelism: 1,
|
|
||||||
// });
|
|
||||||
// if (!validPassword) {
|
|
||||||
// return fail(400, { message: 'Incorrect username or password' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const sessionToken = auth.generateSessionToken();
|
|
||||||
// const session = await auth.createSession(sessionToken, existingUser.id);
|
|
||||||
// auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
|
||||||
|
|
||||||
// return redirect(302, '/demo/lucia');
|
|
||||||
// },
|
|
||||||
// register: async (event) => {
|
|
||||||
// const formData = await event.request.formData();
|
|
||||||
// const username = formData.get('username');
|
|
||||||
// const password = formData.get('password');
|
|
||||||
|
|
||||||
// if (!validateUsername(username)) {
|
|
||||||
// return fail(400, { message: 'Invalid username' });
|
|
||||||
// }
|
|
||||||
// if (!validatePassword(password)) {
|
|
||||||
// return fail(400, { message: 'Invalid password' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const userId = generateUserId();
|
|
||||||
// const passwordHash = await hash(password, {
|
|
||||||
// // recommended minimum parameters
|
|
||||||
// memoryCost: 19456,
|
|
||||||
// timeCost: 2,
|
|
||||||
// outputLen: 32,
|
|
||||||
// parallelism: 1,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// await db.insert(table.user).values({ id: userId, username, passwordHash });
|
|
||||||
|
|
||||||
// const sessionToken = auth.generateSessionToken();
|
|
||||||
// const session = await auth.createSession(sessionToken, userId);
|
|
||||||
// auth.setSessionTokenCookie(event, sessionToken, session.expiresAt);
|
|
||||||
// } catch (e) {
|
|
||||||
// return fail(500, { message: 'An error has occurred' });
|
|
||||||
// }
|
|
||||||
// return redirect(302, '/demo/lucia');
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// function generateUserId() {
|
|
||||||
// // ID with 120 bits of entropy, or about the same as UUID v4.
|
|
||||||
// const bytes = crypto.getRandomValues(new Uint8Array(15));
|
|
||||||
// const id = encodeBase32LowerCase(bytes);
|
|
||||||
// return id;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function validateUsername(username: unknown): username is string {
|
|
||||||
// return (
|
|
||||||
// typeof username === 'string' &&
|
|
||||||
// username.length >= 3 &&
|
|
||||||
// username.length <= 31 &&
|
|
||||||
// /^[a-z0-9_-]+$/.test(username)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function validatePassword(password: unknown): password is string {
|
|
||||||
// return (
|
|
||||||
// typeof password === 'string' &&
|
|
||||||
// password.length >= 6 &&
|
|
||||||
// password.length <= 255
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import * as Form from "$lib/components/ui/form/index.js";
|
|
||||||
import { Input } from "$lib/components/ui/input/index.js";
|
|
||||||
import { formSchema, type FormSchema } from "./schema";
|
|
||||||
import {
|
|
||||||
type SuperValidated,
|
|
||||||
type Infer,
|
|
||||||
superForm,
|
|
||||||
} from "sveltekit-superforms";
|
|
||||||
import { zodClient } from "sveltekit-superforms/adapters";
|
|
||||||
import { Button } from "$lib/components/ui/button/index.js";
|
|
||||||
import * as Card from "$lib/components/ui/card/index.js";
|
|
||||||
import { Label } from "$lib/components/ui/label/index.js";
|
|
||||||
import { Switch } from "$lib/components/ui/switch/index.js";
|
|
||||||
import { cn } from "$lib/utils.js";
|
|
||||||
import type { HTMLAttributes } from "svelte/elements";
|
|
||||||
|
|
||||||
let { data }: { data: { form: SuperValidated<Infer<FormSchema>> } } =
|
|
||||||
$props();
|
|
||||||
|
|
||||||
const form = superForm(data.form, {
|
|
||||||
validators: zodClient(formSchema),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { form: formData, enhance } = form;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class={cn("flex flex-col gap-6")}>
|
|
||||||
<Card.Root>
|
|
||||||
<Card.Header class="text-center">
|
|
||||||
<Card.Title class="text-xl font-semibold">LOGIN</Card.Title>
|
|
||||||
</Card.Header>
|
|
||||||
<Card.Content>
|
|
||||||
<form method="POST" use:enhance>
|
|
||||||
<div class="grid gap-6">
|
|
||||||
<div class="grid gap-6">
|
|
||||||
<div class="grid gap-3">
|
|
||||||
<Form.Field {form} name="email">
|
|
||||||
<Form.Control>
|
|
||||||
{#snippet children({ props })}
|
|
||||||
<Form.Label>Email</Form.Label>
|
|
||||||
<Input {...props} bind:value={$formData.email} />
|
|
||||||
{/snippet}
|
|
||||||
</Form.Control>
|
|
||||||
<Form.FieldErrors />
|
|
||||||
</Form.Field>
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-3">
|
|
||||||
<Form.Field {form} name="password">
|
|
||||||
<Form.Control>
|
|
||||||
{#snippet children({ props })}
|
|
||||||
<Form.Label>password</Form.Label>
|
|
||||||
<Input
|
|
||||||
{...props}
|
|
||||||
bind:value={$formData.password}
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
{/snippet}
|
|
||||||
</Form.Control>
|
|
||||||
<Form.FieldErrors />
|
|
||||||
</Form.Field>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<Switch id="airplane-mode" />
|
|
||||||
<Label for="airplane-mode">이메일 저장</Label>
|
|
||||||
</div>
|
|
||||||
<Form.Button class="w-full">로그인</Form.Button>
|
|
||||||
</div>
|
|
||||||
<div class="text-center text-sm">
|
|
||||||
이메일 인증을 통한
|
|
||||||
<a href="##" class="underline underline-offset-4"> 회원가입</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</Card.Content>
|
|
||||||
</Card.Root>
|
|
||||||
</div>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const formSchema = z.object({
|
|
||||||
email: z.string().email(),
|
|
||||||
password: z.string().min(8),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type FormSchema = typeof formSchema;
|
|
||||||
67
src/routes/login/+page.server.ts
Normal file
67
src/routes/login/+page.server.ts
Normal file
@ -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 userId = form.data.userId;
|
||||||
|
const password = form.data.password;
|
||||||
|
|
||||||
|
const results = await db
|
||||||
|
.select()
|
||||||
|
.from(table.user)
|
||||||
|
.where(eq(table.user.username, userId));
|
||||||
|
|
||||||
|
const existingUser = results.at(0);
|
||||||
|
if (!existingUser) {
|
||||||
|
return setError(form, 'userId', '존재하지 않는 아이디 입니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from "./$types.js";
|
import type { PageData } from "../../../.svelte-kit/types/src/routes";
|
||||||
import LoginForm from "./login-form.svelte";
|
import LoginForm from "./login-form.svelte";
|
||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
</script>
|
</script>
|
||||||
81
src/routes/login/login-form.svelte
Normal file
81
src/routes/login/login-form.svelte
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import * as Form from "@/components/ui/form";
|
||||||
|
import {Input} from "@/components/ui/input";
|
||||||
|
import * as Card from "@/components/ui/card";
|
||||||
|
import {formSchema, type FormSchema} from "./schema";
|
||||||
|
import {
|
||||||
|
type SuperValidated,
|
||||||
|
type Infer,
|
||||||
|
superForm,
|
||||||
|
} from "sveltekit-superforms";
|
||||||
|
import {zodClient} from "sveltekit-superforms/adapters";
|
||||||
|
import { dev } from '$app/environment';
|
||||||
|
import {Label} from "@/components/ui/label";
|
||||||
|
import {Switch} from "@/components/ui/switch";
|
||||||
|
import {cn} from "@/utils.js";
|
||||||
|
import SuperDebug from 'sveltekit-superforms';
|
||||||
|
|
||||||
|
let {data}: { data: { form: SuperValidated<Infer<FormSchema>> } } =
|
||||||
|
$props();
|
||||||
|
|
||||||
|
const form = superForm(data.form, {
|
||||||
|
validators: zodClient(formSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {form: formData, enhance} = form;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={cn("flex flex-col gap-6")}>
|
||||||
|
<Card.Root>
|
||||||
|
<Card.Header class="text-center">
|
||||||
|
<Card.Title class="text-xl font-semibold">LOGIN</Card.Title>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Content>
|
||||||
|
<form method="POST" action="?/login" use:enhance>
|
||||||
|
<div class="grid gap-6">
|
||||||
|
<div class="grid gap-6">
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<Form.Field {form} name="userId">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({props})}
|
||||||
|
<Form.Label>UserId</Form.Label>
|
||||||
|
<Input {...props} bind:value={$formData.userId}/>
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.FieldErrors/>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<Form.Field {form} name="password">
|
||||||
|
<Form.Control>
|
||||||
|
{#snippet children({props})}
|
||||||
|
<Form.Label>password</Form.Label>
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
bind:value={$formData.password}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
{/snippet}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.FieldErrors/>
|
||||||
|
</Form.Field>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<Switch id="airplane-mode"/>
|
||||||
|
<Label for="airplane-mode">이메일 저장</Label>
|
||||||
|
</div>
|
||||||
|
<Form.Button class="w-full">로그인</Form.Button>
|
||||||
|
</div>
|
||||||
|
<div class="text-center text-sm">
|
||||||
|
이메일 인증을 통한
|
||||||
|
<a href="##" class="underline underline-offset-4"> 회원가입</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card.Content>
|
||||||
|
</Card.Root>
|
||||||
|
{#if dev}
|
||||||
|
<SuperDebug data={formData}/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
22
src/routes/login/schema.ts
Normal file
22
src/routes/login/schema.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
.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(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FormSchema = typeof formSchema;
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { fail } from '@sveltejs/kit';
|
import { fail } from '@sveltejs/kit';
|
||||||
import nodemailer from 'nodemailer';
|
const nodemailer = require("nodemailer");
|
||||||
import {
|
import {
|
||||||
BREVO_SMTP_HOST,
|
BREVO_SMTP_HOST,
|
||||||
BREVO_SMTP_PORT,
|
BREVO_SMTP_PORT,
|
||||||
Loading…
x
Reference in New Issue
Block a user