Добавил регистрацию/авторизацию

This commit is contained in:
maksim 2025-02-23 22:13:45 +04:00
parent 74d4ba0773
commit 149b0619e0
10 changed files with 279 additions and 76 deletions

2
go-auth/.gitignore vendored
View File

@ -1,2 +1,2 @@
.env
go.mod
.idea

View File

@ -1,3 +1,11 @@
# PIbd-42_Kashin_M.I_FinalQualifyingWork
Модуль "Авторизация"
Модуль "Авторизация"
go mod init main
go mod tidy
.env
JWT_SECRET_KEY=mysecretkey123
DB_URL=postgres://postgres:password_db@localhost:5432/name_db

View File

@ -8,7 +8,6 @@ import (
"main/database"
"main/models"
"os"
"strconv"
"time"
"github.com/joho/godotenv"
@ -60,44 +59,50 @@ func Register(c *fiber.Ctx) error {
return c.JSON(user)
}
// Логиним пользователя
// Генерация JWT-токена
func generateToken(user models.User) (string, error) {
claims := jwt.MapClaims{
"user_id": user.Id,
"exp": time.Now().Add(time.Hour * 24).Unix(), // Токен на 24 часа
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(SecretKey))
}
func Login(c *fiber.Ctx) error {
var data map[string]string
if err := c.BodyParser(&data); err != nil {
return err
}
// Найти пользователя по email
var user models.User
database.DB.Where("email = ?", data["email"]).First(&user)
if user.Id == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"message": "User not found"})
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid email or password"})
}
// Проверяем пароль
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(data["password"])); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Incorrect password"})
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid email or password"})
}
claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
Issuer: strconv.Itoa(int(user.Id)),
ExpiresAt: time.Now().Add(time.Hour * 12).Unix(),
})
token, err := claims.SignedString([]byte(SecretKey))
// Генерируем токен
token, err := generateToken(user)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Token invalid"})
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "Could not generate token"})
}
cookie := fiber.Cookie{
// Сохраняем токен в cookie
c.Cookie(&fiber.Cookie{
Name: "jwt",
Value: token,
Expires: time.Now().Add(time.Hour * 12),
Expires: time.Now().Add(time.Hour * 24),
HTTPOnly: true,
SameSite: "Strict",
}
c.Cookie(&cookie)
})
return c.JSON(fiber.Map{"message": "Login success"})
return c.JSON(fiber.Map{"message": "Login successful"})
}
// Получаем пользователя по токену

34
go-auth/go.mod Normal file
View File

@ -0,0 +1,34 @@
module main
go 1.24.0
require (
github.com/gofiber/fiber/v2 v2.52.6
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/joho/godotenv v1.5.1
golang.org/x/crypto v0.34.0
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.25.12
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
)

67
go-auth/go.sum Normal file
View File

@ -0,0 +1,67 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA=
golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

View File

@ -23,7 +23,7 @@ func main() {
app.Use(cors.New(cors.Config{
AllowCredentials: true,
AllowOrigins: "http://localhost:8000",
AllowOrigins: "http://localhost:5173/",
}))
routes.Setup(app)

View File

@ -2,6 +2,7 @@ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import Authorization from './component/page/Authorization';
import Registration from './component/page/Registration';
import Main from './component/page/Main';
import Profile from './component/profile/Profile';
function App() {
return (
@ -10,6 +11,7 @@ function App() {
<Route path="/" element={<Main />} />
<Route path="/login" element={<Authorization />} />
<Route path="/register" element={<Registration />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Router>
);

View File

@ -1,48 +1,63 @@
import { Link } from 'react-router-dom'; // Импортируем Link
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import React, { useState } from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input, Flex } from 'antd';
import { Button, Checkbox, Form, Input, Flex, message } from 'antd';
import axios from 'axios';
const Authorization = () => {
const onFinish = (values) => {
console.log('Received values of form: ', values);
const [loading, setLoading] = useState(false);
const navigate = useNavigate(); // Перенаправление после успешного входа
const onFinish = async (values) => {
setLoading(true);
try {
const response = await axios.post(
'http://localhost:8000/api/login',
{
email: values.email, // Обновляем поле, так как сервер ожидает email
password: values.password,
},
{ withCredentials: true } // Обязательно, чтобы браузер хранил cookie
);
message.success('Login successful!');
console.log(response.data);
// Перенаправление на главную страницу
navigate('/');
} catch (error) {
message.error(error.response?.data?.message || 'Login failed');
console.error(error);
} finally {
setLoading(false);
}
};
return (
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<Form
name="login"
initialValues={{
remember: true,
}}
style={{
maxWidth: 360,
}}
initialValues={{ remember: true }}
style={{ maxWidth: 360 }}
onFinish={onFinish}
>
<Form.Item
name="username"
name="email"
rules={[
{
required: true,
message: 'Please input your Username!',
},
{ required: true, message: 'Please input your Email!' },
{ type: 'email', message: 'Invalid email format!' },
]}
>
<Input prefix={<UserOutlined />} placeholder="Username" />
<Input prefix={<UserOutlined />} placeholder="Email" />
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: 'Please input your Password!',
},
]}
rules={[{ required: true, message: 'Please input your Password!' }]}
>
<Input prefix={<LockOutlined />} type="password" placeholder="Password" />
</Form.Item>
<Form.Item>
<Flex justify="space-between" align="center">
<Form.Item name="remember" valuePropName="checked" noStyle>
@ -53,10 +68,10 @@ const Authorization = () => {
</Form.Item>
<Form.Item>
<Button block type="primary" htmlType="submit">
<Button block type="primary" htmlType="submit" loading={loading}>
Log in
</Button>
or <Link to="/register">Register now!</Link> {/* Используем Link для перехода на страницу регистрации */}
or <Link to="/register">Register now!</Link>
</Form.Item>
</Form>
</div>

View File

@ -1,48 +1,64 @@
import { Link } from 'react-router-dom'; // Импортируем Link
import React from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input, Flex } from 'antd';
import { Link } from 'react-router-dom';
import React, { useState } from 'react';
import { LockOutlined, UserOutlined, MailOutlined } from '@ant-design/icons';
import { Button, Checkbox, Form, Input, Flex, message } from 'antd';
import axios from 'axios';
const Registration = () => {
const onFinish = (values) => {
console.log('Received values of form: ', values);
const [loading, setLoading] = useState(false); // Для отображения загрузки
const onFinish = async (values) => {
setLoading(true);
try {
const response = await axios.post('http://localhost:8000/api/register', {
name: values.name,
email: values.email,
password: values.password,
});
message.success('Registration successful!');
console.log(response.data); // Можно обработать ответ, например, переадресовать на login
} catch (error) {
message.error(error.response?.data?.message || 'Registration failed');
console.error(error);
} finally {
setLoading(false);
}
};
return (
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<Form
name="login"
initialValues={{
remember: true,
}}
style={{
maxWidth: 360,
}}
name="register"
initialValues={{ remember: true }}
style={{ maxWidth: 360 }}
onFinish={onFinish}
>
<Form.Item
name="username"
name="name"
rules={[{ required: true, message: 'Please input your Name!' }]}
>
<Input prefix={<UserOutlined />} placeholder="Name" />
</Form.Item>
<Form.Item
name="email"
rules={[
{
required: true,
message: 'Please input your Username!',
},
{ required: true, message: 'Please input your Email!' },
{ type: 'email', message: 'Invalid email format!' },
]}
>
<Input prefix={<UserOutlined />} placeholder="Username" />
<Input prefix={<MailOutlined />} placeholder="Email" />
</Form.Item>
<Form.Item
name="password"
rules={[
{
required: true,
message: 'Please input your Password!',
},
]}
rules={[{ required: true, message: 'Please input your Password!' }]}
>
<Input prefix={<LockOutlined />} type="password" placeholder="Password" />
</Form.Item>
<Form.Item>
<Flex justify="space-between" align="center">
<Form.Item name="remember" valuePropName="checked" noStyle>
@ -53,10 +69,10 @@ const Registration = () => {
</Form.Item>
<Form.Item>
<Button block type="primary" htmlType="submit">
Log in
<Button block type="primary" htmlType="submit" loading={loading}>
Register
</Button>
or <Link to="/login">Login now!</Link> {/* Используем Link для перехода на страницу авторизации */}
or <Link to="/login">Login now!</Link>
</Form.Item>
</Form>
</div>

View File

@ -0,0 +1,56 @@
import React, { useEffect, useState } from "react";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import { Card, Spin, Alert, Button } from "antd";
const Profile = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const fetchUserData = async () => {
try {
const response = await axios.get("http://localhost:8000/api/user", {
withCredentials: true, // Отправляем cookie с JWT
});
} catch (err) {
setError("Вы не авторизованы. Пожалуйста, войдите в систему.");
} finally {
setLoading(false);
}
};
fetchUserData();
}, []);
if (loading) return <Spin size="large" />;
if (error)
return (
<Alert
message="Ошибка"
description={error}
type="error"
showIcon
action={
<Button type="primary" onClick={() => navigate("/login")}>
Войти
</Button>
}
/>
);
return (
<div className="flex justify-center items-center min-h-screen bg-gray-100">
<Card title="Профиль пользователя" style={{ width: 400 }}>
<p><strong>Имя:</strong> имя </p>
<p><strong>Email:</strong> почта</p>
<Button type="primary" onClick={() => navigate("/logout")}>
Выйти
</Button>
</Card>
</div>
);
};
export default Profile;