package main import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type Claims struct { UserID string `json:"uid"` jwt.RegisteredClaims } func HashPassword(pwd string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(pwd), 12) return string(bytes), err } func CheckPassword(hash, pwd string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pwd)) == nil } func GenerateToken(userID string) (string, error) { claims := Claims{ UserID: userID, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } func SetTokenCookie(c *gin.Context, token string) { c.SetSameSite(http.SameSiteLaxMode) c.SetCookie("token", token, 3600*24*7, "/", "", false, true) // HttpOnly } func ClearTokenCookie(c *gin.Context) { c.SetCookie("token", "", -1, "/", "", false, true) } func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { tokenStr, err := c.Cookie("token") if err != nil { c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}); return } claims := &Claims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { return jwtSecret, nil }) if err != nil || !token.Valid { c.AbortWithStatusJSON(401, gin.H{"error": "Invalid token"}); return } c.Set("userID", claims.UserID) c.Next() } } // Optional Auth (for public quiz view) func OptionalAuth() gin.HandlerFunc { return func(c *gin.Context) { tokenStr, _ := c.Cookie("token") if tokenStr != "" { claims := &Claims{} if token, _ := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) { return jwtSecret, nil }); token.Valid { c.Set("userID", claims.UserID) } } c.Next() } }