This commit is contained in:
Tomáš Dvořák
2025-10-16 13:32:05 +02:00
commit 12cba639b9
663 changed files with 168914 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
package utils
import (
"errors"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// JWTClaims represents the JWT claims structure
type JWTClaims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// GenerateJWT generates a new JWT token for the given user
func GenerateJWT(userID uint, email, role string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &JWTClaims{
UserID: userID,
Email: email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "fotbal-club",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(GetJWTSecret()))
if err != nil {
return "", err
}
return tokenString, nil
}
// ParseJWT parses and validates a JWT token
func ParseJWT(tokenString string) (*JWTClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(GetJWTSecret()), nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// GetJWTSecret returns the JWT secret from environment variable or a default value
func GetJWTSecret() string {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return "default-secret-key-change-in-production"
}
return secret
}
// GetUserFromContext gets the authenticated user from Gin context
func GetUserFromContext(c *gin.Context) (*JWTClaims, error) {
// Preferred: claims set by auth middleware
if v, ok := c.Get("claims"); ok {
if cl, ok2 := v.(*JWTClaims); ok2 {
return cl, nil
}
}
// Fallback: parse Authorization header (Bearer <token>)
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
return nil, errors.New("authorization header missing")
}
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, errors.New("invalid authorization header format")
}
token := parts[1]
cl, err := ParseJWT(token)
if err != nil {
return nil, err
}
return cl, nil
}
+18
View File
@@ -0,0 +1,18 @@
package utils
import "golang.org/x/crypto/bcrypt"
// HashPassword hashes a password using bcrypt
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedPassword), nil
}
// CheckPassword verifies a password against a hash
func CheckPassword(password, hashedPassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}
+82
View File
@@ -0,0 +1,82 @@
package utils
import (
"regexp"
"strings"
)
// SanitizeHTML removes potentially dangerous HTML tags and attributes
// This is a basic implementation. For production, consider using bluemonday library
func SanitizeHTML(html string) string {
// Remove script tags and content
reScript := regexp.MustCompile(`(?i)<script[^>]*>.*?</script>`)
html = reScript.ReplaceAllString(html, "")
// Remove inline event handlers (onclick, onerror, etc.)
reEvents := regexp.MustCompile(`(?i)\s*on\w+\s*=\s*["'][^"']*["']`)
html = reEvents.ReplaceAllString(html, "")
// Remove javascript: URLs
reJSURL := regexp.MustCompile(`(?i)javascript:`)
html = reJSURL.ReplaceAllString(html, "")
// Remove iframe tags (can be optionally allowed if needed)
reIframe := regexp.MustCompile(`(?i)<iframe[^>]*>.*?</iframe>`)
html = reIframe.ReplaceAllString(html, "")
// Remove object/embed tags
reObject := regexp.MustCompile(`(?i)<(object|embed)[^>]*>.*?</\1>`)
html = reObject.ReplaceAllString(html, "")
// Remove style tags (if CSS injection is a concern)
// Uncomment if you want to remove inline styles
// reStyle := regexp.MustCompile(`(?i)<style[^>]*>.*?</style>`)
// html = reStyle.ReplaceAllString(html, "")
return strings.TrimSpace(html)
}
// SanitizeString removes HTML tags entirely and returns plain text
func SanitizeString(input string) string {
// Remove all HTML tags
reHTML := regexp.MustCompile(`<[^>]*>`)
text := reHTML.ReplaceAllString(input, " ")
// Normalize whitespace
text = strings.Join(strings.Fields(text), " ")
return strings.TrimSpace(text)
}
// ValidateURL checks if a URL is safe (http/https only)
func ValidateURL(url string) bool {
if url == "" {
return true
}
lower := strings.ToLower(strings.TrimSpace(url))
return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") || strings.HasPrefix(lower, "/")
}
// SanitizeFilename removes dangerous characters from filenames
func SanitizeFilename(filename string) string {
// Remove path traversal attempts
filename = strings.ReplaceAll(filename, "..", "")
filename = strings.ReplaceAll(filename, "/", "")
filename = strings.ReplaceAll(filename, "\\", "")
// Allow only safe characters
re := regexp.MustCompile(`[^a-zA-Z0-9._-]`)
filename = re.ReplaceAllString(filename, "_")
// Limit length
if len(filename) > 200 {
filename = filename[:200]
}
return filename
}
// RemoveNullBytes removes null bytes that can cause issues
func RemoveNullBytes(s string) string {
return strings.ReplaceAll(s, "\x00", "")
}
+54
View File
@@ -0,0 +1,54 @@
package utils
import (
"os"
"time"
"github.com/golang-jwt/jwt/v5"
)
type SubscriberTokenClaims struct {
Email string `json:"email"`
jwt.RegisteredClaims
}
// GenerateSubscriberToken creates a signed token for a subscriber email, expires in `ttl` minutes
func GenerateSubscriberToken(email string, ttlMinutes int) (string, error) {
if ttlMinutes <= 0 {
ttlMinutes = 60
}
now := time.Now()
claims := &SubscriberTokenClaims{
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(ttlMinutes) * time.Minute)),
Issuer: "fotbal-club",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(getJWTSecret()))
}
// ParseSubscriberToken validates and returns the email embedded in the token
func ParseSubscriberToken(tokenString string) (string, error) {
token, err := jwt.ParseWithClaims(tokenString, &SubscriberTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(getJWTSecret()), nil
})
if err != nil {
return "", err
}
if claims, ok := token.Claims.(*SubscriberTokenClaims); ok && token.Valid {
return claims.Email, nil
}
return "", jwt.ErrTokenMalformed
}
func getJWTSecret() string {
s := os.Getenv("JWT_SECRET")
if s == "" {
return "default-secret-key-change-in-production"
}
return s
}