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 ( return (
<> <>
<Header /> <Header showToolbar />
{children} {children}
</> </>
); );

View File

@ -1,7 +1,15 @@
import Link from "next/link"; import Link from "next/link";
import { getAuth } from "@/lib/auth";
import Editor from "@/components/editor/Editor"; 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 ( return (
<div className="flex flex-col items-center gap-8"> <div className="flex flex-col items-center gap-8">
<Editor /> <Editor />

View File

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

View File

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

@ -14,6 +14,12 @@ export async function authenticate(_prevState: unknown, formData: FormData) {
const username = formData.get("username") as string; const username = formData.get("username") as string;
const password = formData.get("password") 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() const users = await db.select()
.from(usersTable) .from(usersTable)
.where(eq(usersTable.username, username)); .where(eq(usersTable.username, username));
@ -36,5 +42,11 @@ export async function authenticate(_prevState: unknown, formData: FormData) {
maxAge: 60 * 60 * 24 * 7, 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 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 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 ( return (
<header className="flex items-center gap-6 m-4"> <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" /> <HeaderButton title="rhyme" className="bg-sky-600 hover:bg-sky-500" />
</Link> </Link>
{showToolbar && ( <div className="flex gap-2">
<div className="flex gap-2"> {auth && (
<HeaderButton title="new" icon={<Plus size={20} />} /> <>
<HeaderButton title="list" icon={<List size={20} />} /> <HeaderButton title="new" icon={<Plus size={20} />} />
<HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} /> {showToolbar && <HeaderButton title="list" icon={<List size={20} />} />}
</div> </>
)} )}
{showToolbar && <HeaderButton title="export" icon={<ArrowRightFromLine size={20} />} />}
</div>
<div className="flex gap-2 ml-auto"> <div className="flex gap-2 ml-auto">
<Link href="/about"> <Link href="/about">
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} /> <HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
</Link> </Link>
<Link href="/auth"> {auth ? (
<HeaderButton title="login" icon={<UserRound size={20} />} /> <HeaderButton onClick={logOut} title="log out" icon={<UserRoundMinus size={20} />} />
</Link> ) : (
<Link href="/auth">
<HeaderButton title="login" icon={<UserRound size={20} />} />
</Link>
)}
</div> </div>
</header> </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 ( 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} {title}
{icon} {icon}
</button> </button>

View File

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