Исправления в ветке auth-feature

This commit is contained in:
2025-10-27 18:48:49 +06:00
parent e1bd79db8c
commit 611bd17ac1
71 changed files with 3936 additions and 258 deletions

263
serve/csrf.go Normal file
View File

@@ -0,0 +1,263 @@
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
}