diff --git a/package-lock.json b/package-lock.json index 3dbf66d..11bbe0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "team-board-web", "version": "0.1.0", "dependencies": { + "jose": "^6.1.3", "next": "^15.3", "react": "^19.1", "react-dom": "^19.1" @@ -1079,6 +1080,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", diff --git a/package.json b/package.json index 56c7cbd..48a77e2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "jose": "^6.1.3", "next": "^15.3", "react": "^19.1", "react-dom": "^19.1" diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..2785c46 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,20 @@ +import { NextRequest, NextResponse } from "next/server"; +import { createToken } from "@/lib/auth"; + +const AUTH_USER = process.env.AUTH_USER || "admin"; +const AUTH_PASS = process.env.AUTH_PASS || "teamboard"; + +export async function POST(req: NextRequest) { + const { username, password } = await req.json(); + + if (username === AUTH_USER && password === AUTH_PASS) { + const token = await createToken({ + sub: username, + name: username, + provider: "local", + }); + return NextResponse.json({ token, user: { name: username, provider: "local" } }); + } + + return NextResponse.json({ error: "Invalid credentials" }, { status: 401 }); +} diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts new file mode 100644 index 0000000..27b5a8d --- /dev/null +++ b/src/app/api/auth/me/route.ts @@ -0,0 +1,18 @@ +import { NextRequest, NextResponse } from "next/server"; +import { verifyToken } from "@/lib/auth"; + +export async function GET(req: NextRequest) { + const authHeader = req.headers.get("authorization"); + const token = authHeader?.replace("Bearer ", ""); + + if (!token) { + return NextResponse.json({ error: "No token" }, { status: 401 }); + } + + const payload = await verifyToken(token); + if (!payload) { + return NextResponse.json({ error: "Invalid token" }, { status: 401 }); + } + + return NextResponse.json({ user: { name: payload.name, provider: payload.provider } }); +} diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts deleted file mode 100644 index 37ffac1..0000000 --- a/src/app/api/auth/route.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { cookies } from "next/headers"; -import { NextRequest, NextResponse } from "next/server"; - -const AUTH_USER = process.env.AUTH_USER || "admin"; -const AUTH_PASS = process.env.AUTH_PASS || "teamboard"; -const COOKIE_NAME = "tb_session"; -const SESSION_TOKEN = "authenticated"; - -export async function POST(req: NextRequest) { - const { username, password } = await req.json(); - if (username === AUTH_USER && password === AUTH_PASS) { - const jar = await cookies(); - jar.set(COOKIE_NAME, SESSION_TOKEN, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - maxAge: 60 * 60 * 24 * 7, // 7 days - path: "/", - }); - return NextResponse.json({ ok: true }); - } - return NextResponse.json({ ok: false, error: "Invalid credentials" }, { status: 401 }); -} - -export async function DELETE() { - const jar = await cookies(); - jar.delete(COOKIE_NAME); - return NextResponse.json({ ok: true }); -} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index d051c0a..4265a4c 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -15,16 +15,21 @@ export default function LoginPage() { setError(""); setLoading(true); try { - const res = await fetch("/api/auth", { + const res = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), }); - if (res.ok) { + const data = await res.json(); + if (res.ok && data.token) { + // Store token + localStorage.setItem("tb_token", data.token); + // Also set cookie for SSR middleware + document.cookie = `tb_token=${data.token}; path=/; max-age=${7 * 24 * 3600}; samesite=lax`; router.push("/"); router.refresh(); } else { - setError("Неверный логин или пароль"); + setError(data.error || "Ошибка авторизации"); } } catch { setError("Ошибка соединения"); @@ -70,6 +75,18 @@ export default function LoginPage() { > {loading ? "..." : "Войти"} + +