208 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"log"
"os"
"sync"
"time"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/joho/godotenv"
)
// Модель пользователя
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
)
// Инициализация базы данных и загрузка переменных окружения
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{})
if err != nil {
log.Fatal("Ошибка подключения к базе данных:", err)
}
DB.AutoMigrate(&User{})
}
// Генерация 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))
}
// Проверка заблокированного токена
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
})
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()
app.Post("/register", Register)
app.Post("/login", Login)
app.Get("/user", UserInfo)
app.Post("/logout", Logout)
log.Println("Сервер запущен на порту 3000")
log.Fatal(app.Listen(":3000"))
}