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:
263
serve/csrf.go
Normal file
263
serve/csrf.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user