feat: improve authentication

This commit is contained in:
Kirill Siukhin 2025-07-07 22:13:10 +05:00
parent 0b8aa37351
commit 2e7a86951d
14 changed files with 64 additions and 75 deletions

View File

@ -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 (

View File

@ -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",
};

View 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>
</>
);
}

View File

@ -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 />
</>
);
}

View File

@ -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

View File

@ -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>

View File

@ -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({

View File

@ -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() {

View 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>
);
}

View File

@ -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
View 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
}