feat: improve authentication
This commit is contained in:
parent
0b8aa37351
commit
2e7a86951d
@ -1,5 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Editor from "@/components/Editor";
|
import Editor from "@/components/editor/Editor";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Rhyme – About",
|
title: "About rhyme",
|
||||||
description: "Information about the Rhyme and its creators",
|
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!;
|
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 username = formData.get("username") as string;
|
||||||
const password = formData.get("password") as string;
|
const password = formData.get("password") as string;
|
||||||
|
|
||||||
@ -19,44 +19,14 @@ export async function login(_prevState: unknown, formData: FormData) {
|
|||||||
.where(eq(usersTable.username, username));
|
.where(eq(usersTable.username, username));
|
||||||
|
|
||||||
if (users.length === 0) {
|
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" });
|
const token = jwt.sign({ sub: username }, JWT_SECRET, { expiresIn: "7d" });
|
||||||
|
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
|
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">
|
<Link href="/about">
|
||||||
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
|
<HeaderButton title="about" icon={<CircleQuestionMark size={20} />} />
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/login">
|
<Link href="/auth">
|
||||||
<HeaderButton title="login" icon={<UserRound size={20} />} />
|
<HeaderButton title="login" icon={<UserRound size={20} />} />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { ChangeEvent } from "react";
|
import { ChangeEvent } from "react";
|
||||||
import { Lock, LockOpen, Menu, Minus, Plus, X } from "lucide-react";
|
import { Lock, LockOpen, Menu, Minus, Plus, X } from "lucide-react";
|
||||||
import { Action, IBlock, ILine } from "@/lib/editorReducer";
|
import { Action, IBlock, ILine } from "@/lib/editorReducer";
|
||||||
import IconOnlyButton from "./IconOnlyButton";
|
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||||
import LineInput from "./LineInput";
|
import LineInput from "./LineInput";
|
||||||
|
|
||||||
export default function Block({
|
export default function Block({
|
@ -3,7 +3,7 @@
|
|||||||
import { useReducer } from "react";
|
import { useReducer } from "react";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { editorReducer } from "@/lib/editorReducer";
|
import { editorReducer } from "@/lib/editorReducer";
|
||||||
import IconOnlyButton from "./IconOnlyButton";
|
import IconOnlyButton from "../ui/IconOnlyButton";
|
||||||
import Block from "./Block";
|
import Block from "./Block";
|
||||||
|
|
||||||
export default function Editor() {
|
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