diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..cfac77a --- /dev/null +++ b/middleware.ts @@ -0,0 +1,16 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export function middleware(request: NextRequest) { + const { pathname } = request.nextUrl; + // Получаем access_token из куков (SSR) + const token = request.cookies.get('access_token'); + + if (pathname === '/auth' && token) { + return NextResponse.redirect(new URL('/', request.url)); + } + return NextResponse.next(); +} + +export const config = { + matcher: ['/auth'], +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6158c96..c353749 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@mui/material": "^7.1.0", "@mui/x-data-grid": "^8.5.0", "@types/react-datepicker": "^6.2.0", + "js-cookie": "^3.0.5", "material-react-table": "^3.2.1", "next": "15.3.3", "react": "^19.0.0", @@ -20,6 +21,7 @@ "recharts": "^2.15.3" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", @@ -1497,6 +1499,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.17.57", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", @@ -2067,6 +2076,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 96fa416..919bd5e 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@mui/material": "^7.1.0", "@mui/x-data-grid": "^8.5.0", "@types/react-datepicker": "^6.2.0", + "js-cookie": "^3.0.5", "material-react-table": "^3.2.1", "next": "15.3.3", "react": "^19.0.0", @@ -21,6 +22,7 @@ "recharts": "^2.15.3" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/src/app/auth/page.tsx b/src/app/auth/page.tsx new file mode 100644 index 0000000..3744a8c --- /dev/null +++ b/src/app/auth/page.tsx @@ -0,0 +1,88 @@ +"use client"; +import { useState, useEffect } from "react"; +import Cookies from "js-cookie"; +import styles from "../../styles/auth.module.css"; + +function hasToken() { + if (typeof document === "undefined") return false; + return Cookies.get('access_token') !== undefined; +} + +export default function AuthPage() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (hasToken()) { + window.location.href = "/"; + } + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(""); + setLoading(true); + if (!email || !password) { + setError("Пожалуйста, заполните все поля"); + setLoading(false); + return; + } + try { + const res = await fetch("/api/token", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + login: email, + password: password, + }), + }); + if (!res.ok) { + const data = await res.json(); + setError(data.detail || "Ошибка авторизации"); + setLoading(false); + return; + } + const data = await res.json(); + // Сохраняем токен в куки на 60 минут через js-cookie + Cookies.set('access_token', data.access_token, { expires: 1/24, path: '/', sameSite: 'strict'}); + setError(""); + window.location.href = "/"; + } catch (err) { + setError("Ошибка сети или сервера"); + } finally { + setLoading(false); + } + }; + + return ( +