208 lines
5.8 KiB
Go
208 lines
5.8 KiB
Go
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"))
|
||
}
|