2025-02-21 19:43:34 +04:00
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
2025-03-09 17:19:40 +04:00
|
|
|
|
"log"
|
|
|
|
|
"os"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-02-21 19:43:34 +04:00
|
|
|
|
"github.com/gofiber/fiber/v2"
|
2025-03-09 17:19:40 +04:00
|
|
|
|
"github.com/golang-jwt/jwt/v4"
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
|
"gorm.io/driver/sqlite"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
2025-02-21 19:43:34 +04:00
|
|
|
|
"github.com/joho/godotenv"
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
// Модель пользователя
|
|
|
|
|
type User struct {
|
|
|
|
|
Id uint `json:"id" gorm:"primaryKey"`
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Email string `json:"email" gorm:"unique"`
|
|
|
|
|
Password []byte `json:"-"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Глобальные переменные
|
|
|
|
|
var (
|
|
|
|
|
DB *gorm.DB
|
|
|
|
|
SecretKey string
|
|
|
|
|
blockedTokens = make(map[string]struct{})
|
|
|
|
|
blockedTokensMu sync.Mutex
|
|
|
|
|
)
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
// Инициализация базы данных и загрузка переменных окружения
|
|
|
|
|
func init() {
|
|
|
|
|
if err := godotenv.Load(); err != nil {
|
|
|
|
|
log.Fatal("Ошибка загрузки .env файла")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SecretKey = os.Getenv("JWT_SECRET_KEY")
|
|
|
|
|
if SecretKey == "" {
|
|
|
|
|
log.Fatal("JWT_SECRET_KEY не задан в .env")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var err error
|
|
|
|
|
DB, err = gorm.Open(sqlite.Open("users.db"), &gorm.Config{})
|
2025-02-21 19:43:34 +04:00
|
|
|
|
if err != nil {
|
2025-03-09 17:19:40 +04:00
|
|
|
|
log.Fatal("Ошибка подключения к базе данных:", err)
|
2025-02-21 19:43:34 +04:00
|
|
|
|
}
|
2025-03-09 17:19:40 +04:00
|
|
|
|
DB.AutoMigrate(&User{})
|
|
|
|
|
}
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
// Генерация JWT-токена
|
|
|
|
|
func generateToken(user User) (string, error) {
|
|
|
|
|
claims := jwt.MapClaims{
|
|
|
|
|
"user_id": user.Id,
|
|
|
|
|
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
|
|
|
|
}
|
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
|
|
|
return token.SignedString([]byte(SecretKey))
|
|
|
|
|
}
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
// Проверка заблокированного токена
|
|
|
|
|
func isTokenBlocked(token string) bool {
|
|
|
|
|
blockedTokensMu.Lock()
|
|
|
|
|
defer blockedTokensMu.Unlock()
|
|
|
|
|
_, exists := blockedTokens[token]
|
|
|
|
|
return exists
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Блокировка токена при выходе
|
|
|
|
|
func blockToken(token string) {
|
|
|
|
|
blockedTokensMu.Lock()
|
|
|
|
|
defer blockedTokensMu.Unlock()
|
|
|
|
|
blockedTokens[token] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Регистрация пользователя
|
|
|
|
|
func Register(c *fiber.Ctx) error {
|
|
|
|
|
var data map[string]string
|
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
|
|
|
log.Println("Ошибка парсинга запроса:", err)
|
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Invalid request body"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if data["password"] == "" {
|
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Password is required"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var existingUser User
|
|
|
|
|
if err := DB.Where("email = ?", data["email"]).First(&existingUser).Error; err == nil {
|
|
|
|
|
return c.Status(fiber.StatusConflict).JSON(fiber.Map{"message": "Email already taken"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
passwordHash, err := bcrypt.GenerateFromPassword([]byte(data["password"]), bcrypt.DefaultCost)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("Ошибка хеширования пароля:", err)
|
|
|
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "Could not process password"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user := User{
|
|
|
|
|
Name: data["name"],
|
|
|
|
|
Email: data["email"],
|
|
|
|
|
Password: passwordHash,
|
|
|
|
|
}
|
|
|
|
|
DB.Create(&user)
|
|
|
|
|
|
|
|
|
|
log.Println("Пользователь зарегистрирован:", user.Email)
|
|
|
|
|
return c.JSON(user)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Логин пользователя
|
|
|
|
|
func Login(c *fiber.Ctx) error {
|
|
|
|
|
var data map[string]string
|
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
|
|
|
log.Println("Ошибка парсинга запроса:", err)
|
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Invalid request body"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var user User
|
|
|
|
|
DB.Where("email = ?", data["email"]).First(&user)
|
|
|
|
|
if user.Id == 0 {
|
|
|
|
|
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": "Invalid email or password"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token, err := generateToken(user)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Println("Ошибка генерации JWT токена:", err)
|
|
|
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "Could not generate token"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Cookie(&fiber.Cookie{
|
|
|
|
|
Name: "jwt",
|
|
|
|
|
Value: token,
|
|
|
|
|
Expires: time.Now().Add(time.Hour * 24),
|
|
|
|
|
HTTPOnly: true,
|
|
|
|
|
Secure: true,
|
|
|
|
|
SameSite: "Strict",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
log.Println("Пользователь вошел в систему:", user.Email)
|
|
|
|
|
return c.JSON(fiber.Map{"message": "Login successful"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Получение информации о пользователе
|
|
|
|
|
func UserInfo(c *fiber.Ctx) error {
|
|
|
|
|
cookie := c.Cookies("jwt")
|
|
|
|
|
if isTokenBlocked(cookie) {
|
|
|
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Token is blocked"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
token, err := jwt.ParseWithClaims(cookie, &jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
|
|
|
|
|
return []byte(SecretKey), nil
|
|
|
|
|
})
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
if err != nil {
|
|
|
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Unauthorized"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
claims, ok := token.Claims.(*jwt.MapClaims)
|
|
|
|
|
if !ok || !token.Valid {
|
|
|
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Invalid token"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var user User
|
|
|
|
|
DB.Where("id = ?", (*claims)["user_id"]).First(&user)
|
|
|
|
|
if user.Id == 0 {
|
|
|
|
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "User not found"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return c.JSON(user)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Выход пользователя
|
|
|
|
|
func Logout(c *fiber.Ctx) error {
|
|
|
|
|
cookie := c.Cookies("jwt")
|
|
|
|
|
blockToken(cookie)
|
|
|
|
|
|
|
|
|
|
c.Cookie(&fiber.Cookie{
|
|
|
|
|
Name: "jwt",
|
|
|
|
|
Value: "",
|
|
|
|
|
Expires: time.Now().Add(-time.Hour),
|
|
|
|
|
HTTPOnly: true,
|
|
|
|
|
Secure: true,
|
|
|
|
|
SameSite: "Strict",
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
log.Println("Пользователь вышел из системы")
|
|
|
|
|
return c.JSON(fiber.Map{"message": "Logout successful"})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Запуск приложения и маршрутизация
|
|
|
|
|
func main() {
|
|
|
|
|
app := fiber.New()
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
app.Post("/register", Register)
|
|
|
|
|
app.Post("/login", Login)
|
|
|
|
|
app.Get("/user", UserInfo)
|
|
|
|
|
app.Post("/logout", Logout)
|
2025-02-21 19:43:34 +04:00
|
|
|
|
|
2025-03-09 17:19:40 +04:00
|
|
|
|
log.Println("Сервер запущен на порту 3000")
|
|
|
|
|
log.Fatal(app.Listen(":3000"))
|
2025-02-21 19:43:34 +04:00
|
|
|
|
}
|