added hcaptcha
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
"tm/pkg/authorization"
|
||||
"tm/pkg/config"
|
||||
"tm/pkg/hcaptcha"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/mongo"
|
||||
"tm/pkg/notification"
|
||||
@@ -249,3 +250,21 @@ func InitOllamaService(conf config.OllamaConfig, log logger.Logger) ollama.SDK {
|
||||
|
||||
return *ollamaSDK
|
||||
}
|
||||
|
||||
// InitHCaptchaService initializes the hCaptcha verifier
|
||||
func InitHCaptchaService(conf config.HCaptchaConfig, log logger.Logger) hcaptcha.Verifier {
|
||||
hcaptchaConfig := &hcaptcha.Config{
|
||||
SecretKey: conf.SecretKey,
|
||||
VerifyURL: conf.VerifyURL,
|
||||
Timeout: conf.Timeout,
|
||||
}
|
||||
|
||||
hcaptchaVerifier := hcaptcha.NewVerifier(hcaptchaConfig, log)
|
||||
|
||||
log.Info("hCaptcha verifier initialized successfully", map[string]interface{}{
|
||||
"verify_url": conf.VerifyURL,
|
||||
"timeout": conf.Timeout,
|
||||
})
|
||||
|
||||
return hcaptchaVerifier
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type Config struct {
|
||||
Assets config.AssetsConfig
|
||||
Notification config.NotificationConfig
|
||||
Ollama config.OllamaConfig
|
||||
HCaptcha config.HCaptchaConfig
|
||||
Scraper ScraperConfig
|
||||
UserAuth AuthConfig
|
||||
CustomerAuth AuthConfig
|
||||
|
||||
+5
-2
@@ -144,6 +144,9 @@ func main() {
|
||||
// Initialize notification service
|
||||
notificationSDK := bootstrap.InitNotificationService(conf.Notification, logger)
|
||||
|
||||
// Initialize hCaptcha verifier
|
||||
hcaptchaVerifier := bootstrap.InitHCaptchaService(conf.HCaptcha, logger)
|
||||
|
||||
// Initialize authorization service
|
||||
userAuthService := bootstrap.InitAuthorizationService(conf.UserAuth.JWT, redisClient, logger)
|
||||
customerAuthService := bootstrap.InitAuthorizationService(conf.CustomerAuth.JWT, redisClient, logger)
|
||||
@@ -193,10 +196,10 @@ func main() {
|
||||
tenderHandler := tender.NewHandler(tenderService, logger)
|
||||
feedbackHandler := feedback.NewHandler(feedbackService, userHandler, customerHandler, logger)
|
||||
tenderApprovalHandler := tender_approval.NewHandler(tenderApprovalService, logger)
|
||||
inquiryHandler := inquiry.NewHandler(inquiryService, logger)
|
||||
inquiryHandler := inquiry.NewHandler(inquiryService, logger, hcaptchaVerifier)
|
||||
flagHandler := assets.NewHandler(flagService, logger)
|
||||
notificationHandler := notification.NewHandler(notificationService)
|
||||
contactHandler := contact.NewHandler(contactService)
|
||||
contactHandler := contact.NewHandler(contactService, hcaptchaVerifier)
|
||||
cmsHandler := cms.NewHandler(cmsService)
|
||||
scraperHandler := scraper.NewHandler(scraperService)
|
||||
logger.Info("Handlers initialized successfully", map[string]interface{}{
|
||||
|
||||
@@ -4,10 +4,11 @@ import "tm/pkg/response"
|
||||
|
||||
// CreateContactForm represents the form for creating a new contact message
|
||||
type CreateContactForm struct {
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)" example:"John Doe"`
|
||||
Email string `json:"email" valid:"required,email" example:"john.doe@example.com"`
|
||||
Phone string `json:"phone" valid:"required,length(10|20)" example:"+1234567890"`
|
||||
Message string `json:"message" valid:"required,length(10|1000)" example:"I have a question about your services"`
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)" example:"John Doe"`
|
||||
Email string `json:"email" valid:"required,email" example:"john.doe@example.com"`
|
||||
Phone string `json:"phone" valid:"required,length(10|20)" example:"+1234567890"`
|
||||
Message string `json:"message" valid:"required,length(10|1000)" example:"I have a question about your services"`
|
||||
HCaptchaToken string `json:"hcaptcha_token" valid:"required" example:"P0_eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."`
|
||||
}
|
||||
|
||||
// UpdateContactStatusForm represents the form for updating contact status (admin only)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package contact
|
||||
|
||||
import (
|
||||
"tm/pkg/hcaptcha"
|
||||
"tm/pkg/response"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
@@ -8,13 +9,15 @@ import (
|
||||
|
||||
// Handler handles HTTP requests for contact operations
|
||||
type Handler struct {
|
||||
service Service
|
||||
service Service
|
||||
hcaptchaVerifier hcaptcha.Verifier
|
||||
}
|
||||
|
||||
// NewHandler creates a new contact handler
|
||||
func NewHandler(service Service) *Handler {
|
||||
func NewHandler(service Service, hcaptchaVerifier hcaptcha.Verifier) *Handler {
|
||||
return &Handler{
|
||||
service: service,
|
||||
service: service,
|
||||
hcaptchaVerifier: hcaptchaVerifier,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +38,17 @@ func (h *Handler) Create(c echo.Context) error {
|
||||
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||
}
|
||||
|
||||
// Verify hCaptcha token
|
||||
remoteIP := c.RealIP()
|
||||
verificationResponse, err := h.hcaptchaVerifier.Verify(form.HCaptchaToken, remoteIP)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Failed to verify hCaptcha token", "")
|
||||
}
|
||||
|
||||
if !verificationResponse.Success {
|
||||
return response.BadRequest(c, "hCaptcha verification failed", "")
|
||||
}
|
||||
|
||||
// Create contact
|
||||
contact, err := h.service.Create(c.Request().Context(), form)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,10 +6,11 @@ import "tm/pkg/response"
|
||||
|
||||
// CreateInquiryForm represents the data required to create a new inquiry
|
||||
type CreateInquiryForm struct {
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)" example:"John Doe"` // Full name of the inquirer
|
||||
CompanyName string `json:"company_name" valid:"required,length(2|100)" example:"Opplens"` // Company name
|
||||
WorkEmail string `json:"work_email" valid:"required,email" example:"info@opplens.com"` // Work email address
|
||||
PhoneNumber string `json:"phone_number" valid:"required,length(10|20)" example:"+1234567890"` // Phone number
|
||||
FullName string `json:"full_name" valid:"required,length(2|100)" example:"John Doe"` // Full name of the inquirer
|
||||
CompanyName string `json:"company_name" valid:"required,length(2|100)" example:"Opplens"` // Company name
|
||||
WorkEmail string `json:"work_email" valid:"required,email" example:"info@opplens.com"` // Work email address
|
||||
PhoneNumber string `json:"phone_number" valid:"required,length(10|20)" example:"+1234567890"` // Phone number
|
||||
HCaptchaToken string `json:"hcaptcha_token" valid:"required" example:"P0_eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."`
|
||||
}
|
||||
|
||||
// UpdateInquiryStatusForm represents the data required to update inquiry status
|
||||
|
||||
@@ -2,6 +2,7 @@ package inquiry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"tm/pkg/hcaptcha"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
@@ -11,15 +12,17 @@ import (
|
||||
|
||||
// Handler handles HTTP requests for inquiry operations
|
||||
type Handler struct {
|
||||
service Service
|
||||
logger logger.Logger
|
||||
service Service
|
||||
logger logger.Logger
|
||||
hcaptchaVerifier hcaptcha.Verifier
|
||||
}
|
||||
|
||||
// NewInquiryHandler creates a new inquiry handler
|
||||
func NewHandler(service Service, logger logger.Logger) *Handler {
|
||||
func NewHandler(service Service, logger logger.Logger, hcaptchaVerifier hcaptcha.Verifier) *Handler {
|
||||
return &Handler{
|
||||
service: service,
|
||||
logger: logger,
|
||||
service: service,
|
||||
logger: logger,
|
||||
hcaptchaVerifier: hcaptchaVerifier,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +50,17 @@ func (h *Handler) CreateInquiry(c echo.Context) error {
|
||||
return response.ValidationError(c, err.Error(), "")
|
||||
}
|
||||
|
||||
// Verify hCaptcha token
|
||||
remoteIP := c.RealIP()
|
||||
verificationResponse, err := h.hcaptchaVerifier.Verify(form.HCaptchaToken, remoteIP)
|
||||
if err != nil {
|
||||
return response.BadRequest(c, "Failed to verify hCaptcha token", "")
|
||||
}
|
||||
|
||||
if !verificationResponse.Success {
|
||||
return response.BadRequest(c, "hCaptcha verification failed", "")
|
||||
}
|
||||
|
||||
// Call service
|
||||
inquiry, err := h.service.Create(c.Request().Context(), form)
|
||||
if err != nil {
|
||||
|
||||
@@ -89,6 +89,12 @@ type OllamaConfig struct {
|
||||
EnableStreaming bool `env:"OLLAMA_ENABLE_STREAMING" default:"false"`
|
||||
}
|
||||
|
||||
type HCaptchaConfig struct {
|
||||
SecretKey string `env:"HCAPTCHA_SECRET_KEY"`
|
||||
VerifyURL string `env:"HCAPTCHA_VERIFY_URL"`
|
||||
Timeout time.Duration `env:"HCAPTCHA_TIMEOUT"`
|
||||
}
|
||||
|
||||
// LoadConfig loads configuration with priority: OS environment variables > .env file
|
||||
// OS environment variables take precedence over .env file values
|
||||
func LoadConfig[T any](path string, config T) (T, error) {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package hcaptcha
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tm/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultVerifyURL is the default hCaptcha verification endpoint
|
||||
DefaultVerifyURL = "https://hcaptcha.com/siteverify"
|
||||
// DefaultTimeout is the default timeout for hCaptcha verification requests
|
||||
DefaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
// Verifier handles hCaptcha token verification
|
||||
type Verifier interface {
|
||||
Verify(token string, remoteIP string) (*VerificationResponse, error)
|
||||
}
|
||||
|
||||
// Config holds configuration for hCaptcha verification
|
||||
type Config struct {
|
||||
SecretKey string `env:"HCAPTCHA_SECRET_KEY"`
|
||||
VerifyURL string `env:"HCAPTCHA_VERIFY_URL"`
|
||||
Timeout time.Duration `env:"HCAPTCHA_TIMEOUT"`
|
||||
}
|
||||
|
||||
// VerificationResponse represents the response from hCaptcha verification API
|
||||
type VerificationResponse struct {
|
||||
Success bool `json:"success"`
|
||||
ChallengeTS string `json:"challenge_ts,omitempty"`
|
||||
Hostname string `json:"hostname,omitempty"`
|
||||
Credit bool `json:"credit,omitempty"`
|
||||
ErrorCodes []string `json:"error-codes,omitempty"`
|
||||
}
|
||||
|
||||
// verifier implements the Verifier interface
|
||||
type verifier struct {
|
||||
secretKey string
|
||||
verifyURL string
|
||||
timeout time.Duration
|
||||
logger logger.Logger
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewVerifier creates a new hCaptcha verifier
|
||||
func NewVerifier(config *Config, log logger.Logger) Verifier {
|
||||
verifyURL := config.VerifyURL
|
||||
if verifyURL == "" {
|
||||
verifyURL = DefaultVerifyURL
|
||||
}
|
||||
|
||||
timeout := config.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = DefaultTimeout
|
||||
}
|
||||
|
||||
return &verifier{
|
||||
secretKey: config.SecretKey,
|
||||
verifyURL: verifyURL,
|
||||
timeout: timeout,
|
||||
logger: log,
|
||||
client: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Verify verifies an hCaptcha token with the hCaptcha API
|
||||
func (v *verifier) Verify(token string, remoteIP string) (*VerificationResponse, error) {
|
||||
if token == "" {
|
||||
return &VerificationResponse{
|
||||
Success: false,
|
||||
ErrorCodes: []string{"missing-input-response"},
|
||||
}, fmt.Errorf("hCaptcha token is required")
|
||||
}
|
||||
|
||||
// Prepare form data
|
||||
data := url.Values{}
|
||||
data.Set("secret", v.secretKey)
|
||||
data.Set("response", token)
|
||||
if remoteIP != "" {
|
||||
data.Set("remoteip", remoteIP)
|
||||
}
|
||||
|
||||
// Create request with form data in body
|
||||
req, err := http.NewRequest("POST", v.verifyURL, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
v.logger.Error("Failed to create hCaptcha verification request", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to create verification request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
// Make request
|
||||
resp, err := v.client.Do(req)
|
||||
if err != nil {
|
||||
v.logger.Error("Failed to verify hCaptcha token", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to verify token: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
v.logger.Error("Failed to read hCaptcha verification response", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
})
|
||||
return nil, fmt.Errorf("failed to read verification response: %w", err)
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var verificationResponse VerificationResponse
|
||||
if err := json.Unmarshal(body, &verificationResponse); err != nil {
|
||||
v.logger.Error("Failed to parse hCaptcha verification response", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"body": string(body),
|
||||
"status": resp.StatusCode,
|
||||
})
|
||||
return nil, fmt.Errorf("failed to parse verification response: %w", err)
|
||||
}
|
||||
|
||||
// Log verification result
|
||||
if verificationResponse.Success {
|
||||
v.logger.Debug("hCaptcha token verified successfully", map[string]interface{}{
|
||||
"hostname": verificationResponse.Hostname,
|
||||
})
|
||||
} else {
|
||||
v.logger.Warn("hCaptcha token verification failed", map[string]interface{}{
|
||||
"error_codes": verificationResponse.ErrorCodes,
|
||||
})
|
||||
}
|
||||
|
||||
return &verificationResponse, nil
|
||||
}
|
||||
Reference in New Issue
Block a user