mirror of
https://github.com/Direct-Dev-Ru/go-lcg.git
synced 2025-11-16 01:29:55 +00:00
264 lines
7.0 KiB
Go
264 lines
7.0 KiB
Go
package serve
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"net/http"
|
||
"os"
|
||
"time"
|
||
|
||
"github.com/direct-dev-ru/linux-command-gpt/config"
|
||
)
|
||
|
||
// CSRFManager управляет CSRF токенами
|
||
type CSRFManager struct {
|
||
secretKey []byte
|
||
}
|
||
|
||
// CSRFData содержит данные для CSRF токена
|
||
type CSRFData struct {
|
||
Token string
|
||
Timestamp int64
|
||
UserID string
|
||
}
|
||
|
||
// NewCSRFManager создает новый менеджер CSRF
|
||
func NewCSRFManager() (*CSRFManager, error) {
|
||
secret, err := getCSRFSecretKey()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &CSRFManager{secretKey: secret}, nil
|
||
}
|
||
|
||
// getCSRFSecretKey получает или генерирует секретный ключ для CSRF
|
||
func getCSRFSecretKey() ([]byte, error) {
|
||
// Пытаемся загрузить из переменной окружения
|
||
if secret := os.Getenv("LCG_CSRF_SECRET"); secret != "" {
|
||
return []byte(secret), nil
|
||
}
|
||
|
||
// Пытаемся загрузить из файла
|
||
secretFile := fmt.Sprintf("%s/server/csrf_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 CSRF 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 CSRF secret: %v", err)
|
||
}
|
||
|
||
return secret, nil
|
||
}
|
||
|
||
// GenerateToken генерирует CSRF токен для пользователя
|
||
func (c *CSRFManager) GenerateToken(userID string) (string, error) {
|
||
// Создаем данные токена
|
||
data := CSRFData{
|
||
Token: generateRandomString(32),
|
||
Timestamp: time.Now().Unix(),
|
||
UserID: userID,
|
||
}
|
||
|
||
// Создаем подпись
|
||
signature := c.createSignature(data)
|
||
|
||
// Кодируем данные в base64
|
||
encodedData := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%d:%s", data.Token, data.Timestamp, data.UserID)))
|
||
|
||
return fmt.Sprintf("%s.%s", encodedData, signature), nil
|
||
}
|
||
|
||
// ValidateToken проверяет CSRF токен
|
||
func (c *CSRFManager) ValidateToken(token, userID string) bool {
|
||
// Разделяем токен на данные и подпись
|
||
parts := splitToken(token)
|
||
if len(parts) != 2 {
|
||
return false
|
||
}
|
||
|
||
encodedData, signature := parts[0], parts[1]
|
||
|
||
// Декодируем данные
|
||
dataBytes, err := base64.StdEncoding.DecodeString(encodedData)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
// Парсим данные
|
||
dataParts := splitString(string(dataBytes), ":")
|
||
if len(dataParts) != 3 {
|
||
return false
|
||
}
|
||
|
||
tokenValue, timestampStr, tokenUserID := dataParts[0], dataParts[1], dataParts[2]
|
||
|
||
// Проверяем пользователя
|
||
if tokenUserID != userID {
|
||
return false
|
||
}
|
||
|
||
// Проверяем время жизни токена (24 часа)
|
||
timestamp, err := parseInt64(timestampStr)
|
||
if err != nil {
|
||
return false
|
||
}
|
||
|
||
if time.Now().Unix()-timestamp > 24*60*60 {
|
||
return false
|
||
}
|
||
|
||
// Создаем данные для проверки подписи
|
||
data := CSRFData{
|
||
Token: tokenValue,
|
||
Timestamp: timestamp,
|
||
UserID: tokenUserID,
|
||
}
|
||
|
||
// Проверяем подпись
|
||
expectedSignature := c.createSignature(data)
|
||
return signature == expectedSignature
|
||
}
|
||
|
||
// createSignature создает подпись для данных
|
||
func (c *CSRFManager) createSignature(data CSRFData) string {
|
||
message := fmt.Sprintf("%s:%d:%s", data.Token, data.Timestamp, data.UserID)
|
||
hash := sha256.Sum256(append(c.secretKey, []byte(message)...))
|
||
return hex.EncodeToString(hash[:])
|
||
}
|
||
|
||
// getTokenFromCookie извлекает CSRF токен из cookie
|
||
func GetCSRFTokenFromCookie(r *http.Request) string {
|
||
cookie, err := r.Cookie("csrf_token")
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
return cookie.Value
|
||
}
|
||
|
||
// setCSRFCookie устанавливает CSRF токен в cookie
|
||
func setCSRFCookie(w http.ResponseWriter, token string) {
|
||
cookie := &http.Cookie{
|
||
Name: "csrf_token",
|
||
Value: token,
|
||
Path: config.AppConfig.Server.CookiePath,
|
||
HttpOnly: true,
|
||
Secure: config.AppConfig.Server.CookieSecure,
|
||
SameSite: http.SameSiteLaxMode, // Более мягкий режим для reverse proxy
|
||
MaxAge: 1 * 60 * 60,
|
||
}
|
||
|
||
// Добавляем домен если указан
|
||
if config.AppConfig.Server.Domain != "" {
|
||
cookie.Domain = config.AppConfig.Server.Domain
|
||
}
|
||
|
||
http.SetCookie(w, cookie)
|
||
}
|
||
|
||
// clearCSRFCookie удаляет CSRF cookie
|
||
func СlearCSRFCookie(w http.ResponseWriter) {
|
||
cookie := &http.Cookie{
|
||
Name: "csrf_token",
|
||
Value: "",
|
||
Path: config.AppConfig.Server.CookiePath,
|
||
HttpOnly: true,
|
||
Secure: config.AppConfig.Server.CookieSecure,
|
||
SameSite: http.SameSiteLaxMode,
|
||
MaxAge: -1,
|
||
}
|
||
|
||
// Добавляем домен если указан
|
||
if config.AppConfig.Server.Domain != "" {
|
||
cookie.Domain = config.AppConfig.Server.Domain
|
||
}
|
||
|
||
http.SetCookie(w, cookie)
|
||
}
|
||
|
||
// generateRandomString генерирует случайную строку
|
||
func generateRandomString(length int) string {
|
||
bytes := make([]byte, length)
|
||
rand.Read(bytes)
|
||
return base64.URLEncoding.EncodeToString(bytes)[:length]
|
||
}
|
||
|
||
// splitToken разделяет токен на части
|
||
func splitToken(token string) []string {
|
||
// Ищем последнюю точку
|
||
lastDot := -1
|
||
for i := len(token) - 1; i >= 0; i-- {
|
||
if token[i] == '.' {
|
||
lastDot = i
|
||
break
|
||
}
|
||
}
|
||
|
||
if lastDot == -1 {
|
||
return []string{token}
|
||
}
|
||
|
||
return []string{token[:lastDot], token[lastDot+1:]}
|
||
}
|
||
|
||
// splitString разделяет строку по разделителю
|
||
func splitString(s, sep string) []string {
|
||
if s == "" {
|
||
return []string{}
|
||
}
|
||
|
||
var result []string
|
||
start := 0
|
||
for i := 0; i < len(s); i++ {
|
||
if i+len(sep) <= len(s) && s[i:i+len(sep)] == sep {
|
||
result = append(result, s[start:i])
|
||
start = i + len(sep)
|
||
i += len(sep) - 1
|
||
}
|
||
}
|
||
result = append(result, s[start:])
|
||
return result
|
||
}
|
||
|
||
// parseInt64 парсит строку в int64
|
||
func parseInt64(s string) (int64, error) {
|
||
var result int64
|
||
for _, char := range s {
|
||
if char < '0' || char > '9' {
|
||
return 0, fmt.Errorf("invalid number: %s", s)
|
||
}
|
||
result = result*10 + int64(char-'0')
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// Глобальный экземпляр CSRF менеджера
|
||
var csrfManager *CSRFManager
|
||
|
||
// InitCSRFManager инициализирует глобальный CSRF менеджер
|
||
func InitCSRFManager() error {
|
||
var err error
|
||
csrfManager, err = NewCSRFManager()
|
||
return err
|
||
}
|
||
|
||
// GetCSRFManager возвращает глобальный CSRF менеджер
|
||
func GetCSRFManager() *CSRFManager {
|
||
return csrfManager
|
||
}
|