Files
go-lcg/serve/csrf.go

264 lines
7.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}