package auth import ( "errors" "time" "github.com/golang-jwt/jwt/v5" "gogs.dmsc.dev/arp/models" "golang.org/x/crypto/bcrypt" ) // Token expiration: 10 years const TokenExpiration = 10 * 365 * 24 * time.Hour // JWT secret key - in production this should be loaded from environment var jwtSecret = []byte("your-secret-key-change-in-production") // Claims represents the JWT claims type Claims struct { UserID uint `json:"user_id"` Email string `json:"email"` Roles []RoleClaim `json:"roles"` Permissions []string `json:"permissions"` jwt.RegisteredClaims } // RoleClaim represents a role in the JWT type RoleClaim struct { ID uint `json:"id"` Name string `json:"name"` } // HashPassword hashes a password using bcrypt func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(bytes), err } // CheckPassword verifies a password against a hash func CheckPassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // GenerateToken creates a JWT token for a user func GenerateToken(user models.User) (string, error) { // Build role claims roles := make([]RoleClaim, len(user.Roles)) for i, role := range user.Roles { roles[i] = RoleClaim{ ID: role.ID, Name: role.Name, } } // Build permission codes (deduplicated) permSet := make(map[string]bool) for _, role := range user.Roles { for _, perm := range role.Permissions { permSet[perm.Code] = true } } permissions := make([]string, 0, len(permSet)) for perm := range permSet { permissions = append(permissions, perm) } claims := Claims{ UserID: user.ID, Email: user.Email, Roles: roles, Permissions: permissions, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpiration)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(jwtSecret) } // ValidateToken validates a JWT token and returns the claims func ValidateToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return jwtSecret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") } // SetJWTSecret sets the JWT secret key (for testing or configuration) func SetJWTSecret(secret []byte) { jwtSecret = secret }