diff --git a/src/app/(editor)/page.tsx b/src/app/(editor)/page.tsx index 231d4f6..13cda23 100644 --- a/src/app/(editor)/page.tsx +++ b/src/app/(editor)/page.tsx @@ -13,10 +13,12 @@ export default async function Home() { return (
- - Changes are not saved!
- Log in to save your notes. -
+ {!auth && ( + + Changes are not saved!
+ Log in to save your notes. +
+ )}
); } diff --git a/src/app/(noneditor)/auth/page.tsx b/src/app/(noneditor)/auth/page.tsx index 6d05536..417f29c 100644 --- a/src/app/(noneditor)/auth/page.tsx +++ b/src/app/(noneditor)/auth/page.tsx @@ -11,12 +11,14 @@ export default function About() { <>

Authenticate

-

Please, use this form to log in/register:

- +
+ + +
-

Welcome to rhyme!

-

Free service for writing and saving notes, lyrics, poetry, etc...

+

Welcome to Rhyme!

+

Free service for writing and saving notes, lyrics, poetry, etc

Made with ❤️ by Kirill Siukhin

diff --git a/src/app/(noneditor)/notes/page.tsx b/src/app/(noneditor)/notes/page.tsx index a6e6b8b..359cd30 100644 --- a/src/app/(noneditor)/notes/page.tsx +++ b/src/app/(noneditor)/notes/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from "next"; export const metadata: Metadata = { - title: "My notes - Rhyme", + title: "Notes - Rhyme", description: "View, create and edit your notes", }; diff --git a/src/app/actions.ts b/src/app/actions.ts index f4a1bc3..316a001 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -10,7 +10,7 @@ import bcrypt from "bcrypt"; const JWT_SECRET = process.env.JWT_SECRET!; -export async function authenticate(_prevState: unknown, formData: FormData) { +export async function login(_prevState: unknown, formData: FormData) { const username = formData.get("username") as string; const password = formData.get("password") as string; @@ -25,12 +25,9 @@ export async function authenticate(_prevState: unknown, formData: FormData) { .where(eq(usersTable.username, username)); if (users.length === 0) { - await db.insert(usersTable).values({ - username, - password: bcrypt.hashSync(password, bcrypt.genSaltSync()), - }); + return { error: "Invalid password or username!" }; } else if (!bcrypt.compareSync(password, users[0].password)) { - return { error: "Invalid password or username is already taken" }; + return { error: "Invalid password or username!" }; } const token = jwt.sign({ sub: username }, JWT_SECRET, { expiresIn: "7d" }); @@ -45,6 +42,46 @@ export async function authenticate(_prevState: unknown, formData: FormData) { redirect("/notes"); } +export async function register(_prevState: unknown, formData: FormData) { + const username = formData.get("username") as string; + const password = formData.get("password") as string; + const passwordConfirm = formData.get("password_confirm") as string; + + if (password !== passwordConfirm) { + return { error: "Passwords do not match!" }; + } + + 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) + .where(eq(usersTable.username, username)); + + if (users.length !== 0) { + return { error: "Username is already taken!" }; + } + + await db.insert(usersTable).values({ + username, + password: bcrypt.hashSync(password, bcrypt.genSaltSync()), + }); + + const token = jwt.sign({ sub: username }, JWT_SECRET, { expiresIn: "7d" }); + + const cookieStore = await cookies(); + cookieStore.set("session", token, { + httpOnly: true, + path: "/", + maxAge: 60 * 60 * 24 * 7, + }); + + redirect("/notes"); +} + export async function logOut() { const cookieStore = await cookies(); cookieStore.delete("session"); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 1cb83bc..d52903f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,8 +1,8 @@ import Link from "next/link"; import { ArrowRightFromLine, CircleQuestionMark, List, Plus, UserRound, UserRoundMinus } from "lucide-react"; +import { logOut } from "@/app/actions"; 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(); @@ -16,8 +16,12 @@ export default async function Header({ showToolbar = false }: { showToolbar?: bo
{auth && ( <> - } /> - {showToolbar && } />} + + } /> + + + } /> + )} {showToolbar && } />} diff --git a/src/components/forms/AuthForm.tsx b/src/components/forms/AuthForm.tsx index 8d26e68..fca0b99 100644 --- a/src/components/forms/AuthForm.tsx +++ b/src/components/forms/AuthForm.tsx @@ -1,17 +1,25 @@ "use client"; import { useActionState } from "react"; -import { authenticate } from "@/app/actions"; +import { login, register } from "@/app/actions"; -export default function AuthForm() { - const [state, formAction] = useActionState(authenticate, null); +export default function AuthForm({ isRegister = false }: { isRegister?: boolean }) { + const [state, formAction] = useActionState(isRegister ? register : login, null); return ( -
- - - - {state?.error &&

{state.error}

} -
+
+

{isRegister ? "Register" : "Login"}

+
+ + + {isRegister && ( + + )} + + {state?.error &&

{state.error}

} +
+
); } diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..6839fa2 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from "next/server"; +import { getAuth } from "./lib/auth"; + +export async function middleware(request: NextRequest) { + const auth = await getAuth(); + + if (auth) { + return NextResponse.next(); + } else { + return NextResponse.redirect(new URL("/auth", request.url)); + } +} + +export const config = { + matcher: "/notes/:path*", +};