feat: improve authentication
This commit is contained in:
parent
0b8aa37351
commit
2e7a86951d
@ -1,5 +1,5 @@
|
||||
import Link from "next/link";
|
||||
import Editor from "@/components/Editor";
|
||||
import Editor from "@/components/editor/Editor";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rhyme – About",
|
||||
title: "About rhyme",
|
||||
description: "Information about the Rhyme and its creators",
|
||||
};
|
||||
|
||||
|
24
src/app/(noneditor)/auth/page.tsx
Normal file
24
src/app/(noneditor)/auth/page.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Metadata } from "next";
|
||||
import AuthForm from "@/components/forms/AuthForm";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Authenticate - rhyme",
|
||||
description: "Register or log into Rhyme account to save, show and load notes",
|
||||
};
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<>
|
||||
<div className="m-4">
|
||||
<h1 className="font-bold text-xl mb-2">Authenticate</h1>
|
||||
<p className="mb-2">Please, use this form to log in/register:</p>
|
||||
<AuthForm />
|
||||
</div>
|
||||
<div className="text-center text-sm text-neutral-400">
|
||||
<p>Welcome to rhyme!</p>
|
||||
<p>Free service for writing and saving notes, lyrics, poetry, etc...</p>
|
||||
<p>Made with ❤️ by <a href="https://misterkirill.com" className="font-bold hover:underline">Kirill Siukhin</a></p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Metadata } from "next";
|
||||
import LoginForm from "@/components/forms/LoginForm";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Rhyme – Log In",
|
||||
description: "Log into Rhyme account to save, show and load notes",
|
||||
};
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="font-bold text-2xl mb-4">Login Page</h1>
|
||||
<LoginForm />
|
||||
</>
|
||||
);
|
||||
}
|
@ -10,7 +10,7 @@ import bcrypt from "bcrypt";
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET!;
|
||||
|
||||
export async function login(_prevState: unknown, formData: FormData) {
|
||||
export async function authenticate(_prevState: unknown, formData: FormData) {
|
||||
const username = formData.get("username") as string;
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
@ -19,43 +19,13 @@ export async function login(_prevState: unknown, formData: FormData) {
|
||||
.where(eq(usersTable.username, username));
|
||||
|
||||
if (users.length === 0) {
|
||||
return { error: "Invalid username or password" };
|
||||
await db.insert(usersTable).values({
|
||||
username,
|
||||
password: bcrypt.hashSync(password, bcrypt.genSaltSync()),
|
||||
});
|
||||
} else if (!bcrypt.compareSync(password, users[0].password)) {
|
||||
return { error: "Invalid password or username is already taken" };
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
if (!bcrypt.compareSync(password, user.password)) {
|
||||
return { error: "Invalid username or password" };
|
||||
}
|
||||
|
||||
const token = jwt.sign({ sub: user.username }, JWT_SECRET, { expiresIn: "7d" });
|
||||
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set("session", token, {
|
||||
httpOnly: true,
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
export async function register(_prevState: unknown, formData: FormData) {
|
||||
const username = formData.get("username") as string;
|
||||
const password = formData.get("password") as string;
|
||||
|
||||
const users = await db.select()
|
||||
.from(usersTable)
|
||||
.where(eq(usersTable.username, username));
|
||||
|
||||
if (users.length !== 0) {
|
||||
return { error: "Username already taken!" };
|
||||
}
|
||||
|
||||
await db.insert(usersTable).values({
|
||||
username,
|
||||
password: bcrypt.hashSync(password, bcrypt.genSaltSync()),
|
||||
});
|
||||
|
||||
const token = jwt.sign({ sub: username }, JWT_SECRET, { expiresIn: "7d" });
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.9 KiB |
@ -21,7 +21,7 @@ export default function Header({ showToolbar = true }: { showToolbar?: boolean }
|
||||
<Link href="/about">
|
||||
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
|
||||
</Link>
|
||||
<Link href="/login">
|
||||
<Link href="/auth">
|
||||
<HeaderButton title="login" icon={<UserRound size={20} />} />
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { ChangeEvent } from "react";
|
||||
import { Lock, LockOpen, Menu, Minus, Plus, X } from "lucide-react";
|
||||
import { Action, IBlock, ILine } from "@/lib/editorReducer";
|
||||
import IconOnlyButton from "./IconOnlyButton";
|
||||
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||
import LineInput from "./LineInput";
|
||||
|
||||
export default function Block({
|
@ -3,7 +3,7 @@
|
||||
import { useReducer } from "react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { editorReducer } from "@/lib/editorReducer";
|
||||
import IconOnlyButton from "./IconOnlyButton";
|
||||
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||
import Block from "./Block";
|
||||
|
||||
export default function Editor() {
|
17
src/components/forms/AuthForm.tsx
Normal file
17
src/components/forms/AuthForm.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { authenticate } from "@/app/actions";
|
||||
|
||||
export default function AuthForm() {
|
||||
const [state, formAction] = useActionState(authenticate, null);
|
||||
|
||||
return (
|
||||
<form className="flex flex-col gap-2" action={formAction}>
|
||||
<input name="username" className="p-2 bg-neutral-800 focus:outline-none focus:bg-neutral-700" placeholder="Username" required />
|
||||
<input name="password" className="p-2 bg-neutral-800 focus:outline-none focus:bg-neutral-700" placeholder="Password" type="password" required />
|
||||
<button type="submit" className="p-2 bg-neutral-800 hover:bg-neutral-700 cursor-pointer">Authenticate</button>
|
||||
{state?.error && <p>{state.error}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useActionState } from "react";
|
||||
import { login } from "@/app/actions";
|
||||
|
||||
export default function LoginForm() {
|
||||
const [state, formAction] = useActionState(login, null);
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<input name="username" placeholder="Username" required />
|
||||
<input name="password" placeholder="Password" type="password" required />
|
||||
<button type="submit">Login</button>
|
||||
{state?.error && <p>{state.error}</p>}
|
||||
</form>
|
||||
);
|
||||
}
|
11
src/lib/auth.ts
Normal file
11
src/lib/auth.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export async function getAuth() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get("session")?.value;
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user