mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-16 01:29:55 +00:00
Исправления в ветке auth-feature
This commit is contained in:
269
serve/auth.go
Normal file
269
serve/auth.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package serve
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// JWTClaims представляет claims для JWT токена
|
||||
type JWTClaims struct {
|
||||
Username string `json:"username"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// AuthRequest представляет запрос на аутентификацию
|
||||
type AuthRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// AuthResponse представляет ответ на аутентификацию
|
||||
type AuthResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// JWTSecretKey генерирует или загружает секретный ключ для JWT
|
||||
func getJWTSecretKey() ([]byte, error) {
|
||||
// Пытаемся загрузить из переменной окружения
|
||||
if secret := os.Getenv("LCG_JWT_SECRET"); secret != "" {
|
||||
return []byte(secret), nil
|
||||
}
|
||||
|
||||
// Пытаемся загрузить из файла
|
||||
secretFile := fmt.Sprintf("%s/server/jwt_secret", config.AppConfig.Server.ConfigFolder)
|
||||
if data, err := os.ReadFile(secretFile); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Генерируем новый секретный ключ
|
||||
secret := make([]byte, 32)
|
||||
if _, err := rand.Read(secret); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate JWT secret: %v", err)
|
||||
}
|
||||
|
||||
// Создаем директорию если не существует
|
||||
if err := os.MkdirAll(fmt.Sprintf("%s/server", config.AppConfig.Server.ConfigFolder), 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create config directory: %v", err)
|
||||
}
|
||||
|
||||
// Сохраняем секретный ключ в файл
|
||||
if err := os.WriteFile(secretFile, secret, 0600); err != nil {
|
||||
return nil, fmt.Errorf("failed to save JWT secret: %v", err)
|
||||
}
|
||||
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// generateJWTToken создает JWT токен для пользователя
|
||||
func generateJWTToken(username string) (string, error) {
|
||||
secret, err := getJWTSecretKey()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Создаем claims
|
||||
claims := JWTClaims{
|
||||
Username: username,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // Токен действителен 24 часа
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: "lcg-server",
|
||||
},
|
||||
}
|
||||
|
||||
// Создаем токен
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(secret)
|
||||
}
|
||||
|
||||
// validateJWTToken проверяет JWT токен
|
||||
func validateJWTToken(tokenString string) (*JWTClaims, error) {
|
||||
secret, err := getJWTSecretKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Парсим токен
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// Проверяем метод подписи
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return secret, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Проверяем валидность токена
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
// getTokenFromCookie извлекает JWT токен из cookies
|
||||
func getTokenFromCookie(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie("auth_token")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
// setAuthCookie устанавливает HTTP-only cookie с JWT токеном
|
||||
func setAuthCookie(w http.ResponseWriter, token string) {
|
||||
cookie := &http.Cookie{
|
||||
Name: "auth_token",
|
||||
Domain: config.AppConfig.Server.Domain,
|
||||
Value: token,
|
||||
Path: config.AppConfig.Server.CookiePath,
|
||||
HttpOnly: true,
|
||||
Secure: config.AppConfig.Server.CookieSecure,
|
||||
SameSite: http.SameSiteLaxMode, // Более мягкий режим для reverse proxy
|
||||
MaxAge: config.AppConfig.Server.CookieTTLHours * 60 * 60,
|
||||
}
|
||||
|
||||
// Добавляем домен если указан
|
||||
if config.AppConfig.Server.Domain != "" {
|
||||
cookie.Domain = config.AppConfig.Server.Domain
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
// clearAuthCookie удаляет cookie с токеном
|
||||
func clearAuthCookie(w http.ResponseWriter) {
|
||||
cookie := &http.Cookie{
|
||||
Name: "auth_token",
|
||||
Value: "",
|
||||
Path: config.AppConfig.Server.CookiePath,
|
||||
HttpOnly: true,
|
||||
Secure: config.AppConfig.Server.CookieSecure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
MaxAge: -1, // Удаляем cookie
|
||||
}
|
||||
|
||||
// Добавляем домен если указан
|
||||
if config.AppConfig.Server.Domain != "" {
|
||||
cookie.Domain = config.AppConfig.Server.Domain
|
||||
}
|
||||
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
// handleLogin обрабатывает запрос на вход
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var req AuthRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: false,
|
||||
Error: "Invalid request body",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем пароль
|
||||
if req.Password != config.AppConfig.Server.Password {
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: false,
|
||||
Error: "Неверный пароль",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Генерируем JWT токен
|
||||
token, err := generateJWTToken(req.Username)
|
||||
if err != nil {
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: false,
|
||||
Error: "Failed to generate token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Устанавливаем cookie
|
||||
setAuthCookie(w, token)
|
||||
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: true,
|
||||
Message: "Успешная авторизация",
|
||||
})
|
||||
}
|
||||
|
||||
// handleLogout обрабатывает запрос на выход
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
clearAuthCookie(w)
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handleValidateToken обрабатывает проверку валидности токена
|
||||
func handleValidateToken(w http.ResponseWriter, r *http.Request) {
|
||||
token, err := getTokenFromCookie(r)
|
||||
if err != nil {
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: false,
|
||||
Error: "Token not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = validateJWTToken(token)
|
||||
if err != nil {
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: false,
|
||||
Error: "Invalid token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiJsonResponse(w, AuthResponse{
|
||||
Success: true,
|
||||
Message: "Token is valid",
|
||||
})
|
||||
}
|
||||
|
||||
// requireAuth middleware проверяет аутентификацию
|
||||
func requireAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// Проверяем, требуется ли аутентификация
|
||||
if !config.AppConfig.Server.RequireAuth {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем токен из cookie
|
||||
token, err := getTokenFromCookie(r)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем валидность токена
|
||||
_, err = validateJWTToken(token)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
// Токен валиден, продолжаем
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user