package auth import ( "errors" "os" "strconv" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type JWTService struct { secretKey []byte issuer string } func NewJWTService(secret string, issuer string) *JWTService { return &JWTService{ secretKey: []byte(secret), issuer: issuer, } } func (s *JWTService) HashPassword(password string) (string, error) { pepper := os.Getenv("PASSWORD_PEPPER") if pepper == "" { // Log warning or fail? Ideally fail safe, but for now fallback or log. // For transparency, we will proceed but it's risky if configured to usage. } // Combine password and pepper passwordWithPepper := password + pepper bytes, err := bcrypt.GenerateFromPassword([]byte(passwordWithPepper), bcrypt.DefaultCost) return string(bytes), err } func (s *JWTService) VerifyPassword(hash, password string) bool { pepper := os.Getenv("PASSWORD_PEPPER") passwordWithPepper := password + pepper err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(passwordWithPepper)) return err == nil } func (s *JWTService) GenerateToken(userID, tenantID string, roles []string) (string, error) { // Parse expiration from env expirationStr := os.Getenv("JWT_EXPIRATION") var expirationDuration time.Duration var err error if expirationStr == "" { expirationDuration = time.Hour * 24 // Default 24h } else { if strings.HasSuffix(expirationStr, "d") { daysStr := strings.TrimSuffix(expirationStr, "d") days, err := strconv.Atoi(daysStr) if err == nil { expirationDuration = time.Hour * 24 * time.Duration(days) } else { expirationDuration = time.Hour * 24 // Fallback } } else { expirationDuration, err = time.ParseDuration(expirationStr) if err != nil { expirationDuration = time.Hour * 24 // Fallback } } } claims := jwt.MapClaims{ "sub": userID, "tenant": tenantID, "roles": roles, "iss": s.issuer, "exp": time.Now().Add(expirationDuration).Unix(), "iat": time.Now().Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.secretKey) } func (s *JWTService) ValidateToken(tokenString string) (map[string]interface{}, error) { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return s.secretKey, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") }