70 lines
1.6 KiB
Go
70 lines
1.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// CSRFMiddleware для Gin
|
|
func CSRFMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Пропускаем безопасные методы
|
|
if !isStateChangingMethod(c.Request.Method) {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// 1. Получаем токен из заголовка
|
|
headerToken := c.GetHeader("X-XSRF-TOKEN")
|
|
if headerToken == "" {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "missing X-XSRF-TOKEN header",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 2. Получаем токен из cookie
|
|
cookie, err := c.Cookie("xsrf_token")
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "missing xsrf_token cookie",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 3. Безопасное сравнение
|
|
if subtle.ConstantTimeCompare([]byte(headerToken), []byte(cookie)) != 1 {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
|
|
"error": "CSRF token mismatch",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 4. Опционально: сохраняем токен в контексте
|
|
c.Set("csrf_token", headerToken)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func isStateChangingMethod(method string) bool {
|
|
switch method {
|
|
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// CSRFTokenFromGin извлекает токен из Gin context
|
|
func CSRFTokenFromGin(c *gin.Context) (string, bool) {
|
|
token, exists := c.Get("xsrf_token")
|
|
if !exists {
|
|
return "", false
|
|
}
|
|
if s, ok := token.(string); ok {
|
|
return s, true
|
|
}
|
|
return "", false
|
|
}
|