feat: more authentication

This commit is contained in:
Kirill Siukhin 2025-07-07 23:40:07 +05:00
parent 2e7a86951d
commit 9d0e447350
9 changed files with 73 additions and 22 deletions

View File

@ -7,7 +7,7 @@ export default function RootLayout({
}>) {
return (
<>
<Header />
<Header showToolbar />
{children}
</>
);

View File

@ -1,7 +1,15 @@
import Link from "next/link";
import { getAuth } from "@/lib/auth";
import Editor from "@/components/editor/Editor";
import { redirect } from "next/navigation";
export default async function Home() {
const auth = await getAuth();
if (auth) {
redirect("/notes");
}
export default function Home() {
return (
<div className="flex flex-col items-center gap-8">
<Editor />

View File

@ -1,7 +1,7 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "About rhyme",
title: "About Rhyme",
description: "Information about the Rhyme and its creators",
};

View File

@ -2,7 +2,7 @@ import { Metadata } from "next";
import AuthForm from "@/components/forms/AuthForm";
export const metadata: Metadata = {
title: "Authenticate - rhyme",
title: "Authenticate - Rhyme",
description: "Register or log into Rhyme account to save, show and load notes",
};

View File

@ -0,0 +1,14 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "My notes - Rhyme",
description: "View, create and edit your notes",
};
export default function Notes() {
return (
<div>
<h1>Notes</h1>
</div>
);
}

View File

@ -13,6 +13,12 @@ const JWT_SECRET = process.env.JWT_SECRET!;
export async function authenticate(_prevState: unknown, formData: FormData) {
const username = formData.get("username") as string;
const password = formData.get("password") as string;
if (username.length < 3) {
return { error: "Username is too short!" };
} else if (password.length < 8) {
return { error: "Password is too short!" };
}
const users = await db.select()
.from(usersTable)
@ -36,5 +42,11 @@ export async function authenticate(_prevState: unknown, formData: FormData) {
maxAge: 60 * 60 * 24 * 7,
});
redirect("/");
redirect("/notes");
}
export async function logOut() {
const cookieStore = await cookies();
cookieStore.delete("session");
redirect("/auth");
}

View File

@ -1,29 +1,39 @@
import Link from "next/link";
import { ArrowRightFromLine, CircleQuestionMark, List, Plus, UserRound } from "lucide-react";
import { ArrowRightFromLine, CircleQuestionMark, List, Plus, UserRound, UserRoundMinus } from "lucide-react";
import { getAuth } from "@/lib/auth";
import HeaderButton from "./HeaderButton";
import { logOut } from "@/app/actions";
export default async function Header({ showToolbar = false }: { showToolbar?: boolean }) {
const auth = await getAuth();
export default function Header({ showToolbar = true }: { showToolbar?: boolean }) {
return (
<header className="flex items-center gap-6 m-4">
<Link href="/">
<Link href={auth ? "/notes" : "/"}>
<HeaderButton title="rhyme" className="bg-sky-600 hover:bg-sky-500" />
</Link>
{showToolbar && (
<div className="flex gap-2">
<HeaderButton title="new" icon={<Plus size={20} />} />
<HeaderButton title="list" icon={<List size={20} />} />
<HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />
</div>
)}
<div className="flex gap-2">
{auth && (
<>
<HeaderButton title="new" icon={<Plus size={20} />} />
{showToolbar && <HeaderButton title="list" icon={<List size={20} />} />}
</>
)}
{showToolbar && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
</div>
<div className="flex gap-2 ml-auto">
<Link href="/about">
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
</Link>
<Link href="/auth">
<HeaderButton title="login" icon={<UserRound size={20} />} />
</Link>
{auth ? (
<HeaderButton onClick={logOut} title="log out" icon={<UserRoundMinus size={20} />} />
) : (
<Link href="/auth">
<HeaderButton title="login" icon={<UserRound size={20} />} />
</Link>
)}
</div>
</header>
);

View File

@ -1,8 +1,13 @@
import { ReactNode } from "react";
import { ButtonHTMLAttributes, ReactNode } from "react";
export default function HeaderButton({ title, icon, className }: { title: string, icon?: ReactNode, className?: string }) {
interface HeaderButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
title: string;
icon?: ReactNode;
}
export default function HeaderButton({ title, icon, className, ...props }: HeaderButtonProps) {
return (
<button className={`flex rounded bg-neutral-800 hover:bg-neutral-700 px-2 py-1 gap-2 items-center cursor-pointer ${className}`}>
<button className={`flex rounded bg-neutral-800 hover:bg-neutral-700 px-2 py-1 gap-2 items-center cursor-pointer ${className}`} {...props}>
{title}
{icon}
</button>

View File

@ -1,4 +1,5 @@
import { cookies } from "next/headers";
import jwt from "jsonwebtoken";
export async function getAuth() {
const cookieStore = await cookies();
@ -7,5 +8,6 @@ export async function getAuth() {
return null;
}
// TODO
const decodedToken = jwt.decode(token) as jwt.JwtPayload;
return { username: decodedToken.sub };
}