mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2025-12-08 04:45:43 +09:00
- Added `entDomainValidator` implementation to validate `(domain, client_api_key)` combinations from the `Domain` table using `ent.Client`. - Replaced dummy validator with the new ent-based validator in server initialization. - Updated documentation and progress tracking for domain validation implementation. - Ensured compatibility with `host` and `host:port` formats by normalizing domain strings during validation.
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/dalbodeule/hop-gate/ent"
|
|
entdomain "github.com/dalbodeule/hop-gate/ent/domain"
|
|
"github.com/dalbodeule/hop-gate/internal/dtls"
|
|
"github.com/dalbodeule/hop-gate/internal/logging"
|
|
)
|
|
|
|
// entDomainValidator 는 ent.Client 를 사용해 Domain 테이블에서
|
|
// (domain, client_api_key) 조합을 검증하는 DomainValidator 구현체입니다.
|
|
type entDomainValidator struct {
|
|
logger logging.Logger
|
|
client *ent.Client
|
|
}
|
|
|
|
// NewEntDomainValidator 는 ent 기반 DomainValidator 를 생성합니다.
|
|
// - domain 파라미터는 "host" 또는 "host:port" 형태 모두 허용하며,
|
|
// DB 조회 시에는 host 부분만 사용합니다.
|
|
func NewEntDomainValidator(logger logging.Logger, client *ent.Client) dtls.DomainValidator {
|
|
return &entDomainValidator{
|
|
logger: logger.With(logging.Fields{"component": "domain_validator"}),
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
// canonicalDomainForLookup 는 handshake 에서 전달된 domain 문자열을
|
|
// DB 조회용 정규 도메인으로 변환합니다.
|
|
// - "host:port" 형태인 경우 port 를 제거하고 host 만 사용합니다.
|
|
// - 공백 제거 및 소문자 변환 후, normalizeDomain 을 통해 기본 형식을 검증합니다.
|
|
func canonicalDomainForLookup(raw string) string {
|
|
d := strings.TrimSpace(raw)
|
|
if d == "" {
|
|
return ""
|
|
}
|
|
|
|
// host:port 형태를 우선적으로 처리합니다.
|
|
if h, _, err := net.SplitHostPort(d); err == nil && strings.TrimSpace(h) != "" {
|
|
d = h
|
|
} else {
|
|
// net.SplitHostPort 가 실패했지만 콜론이 포함되어 있는 경우 (예: 잘못된 포맷),
|
|
// IPv6 를 고려하지 않는 단순 환경에서는 마지막 콜론 기준으로 host 부분만 시도해볼 수 있습니다.
|
|
if idx := strings.LastIndex(d, ":"); idx > 0 && !strings.Contains(d, "]") {
|
|
if h := strings.TrimSpace(d[:idx]); h != "" {
|
|
d = h
|
|
}
|
|
}
|
|
}
|
|
|
|
// admin/service.go 에 정의된 normalizeDomain 과 동일한 규칙을 적용합니다.
|
|
return normalizeDomain(d)
|
|
}
|
|
|
|
// ValidateDomainAPIKey 는 (domain, client_api_key) 조합을 DB 에서 검증합니다.
|
|
func (v *entDomainValidator) ValidateDomainAPIKey(ctx context.Context, domain, clientAPIKey string) error {
|
|
if v.client == nil {
|
|
return fmt.Errorf("domain validator: ent client is nil")
|
|
}
|
|
|
|
d := canonicalDomainForLookup(domain)
|
|
key := strings.TrimSpace(clientAPIKey)
|
|
|
|
if d == "" || key == "" {
|
|
return fmt.Errorf("domain validator: invalid domain or client_api_key")
|
|
}
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
log := v.logger.With(logging.Fields{
|
|
"domain": d,
|
|
"client_api_key_masked": maskKey(key),
|
|
})
|
|
|
|
// Domain 테이블에서 정확히 일치하는 (domain, client_api_key) 를 조회합니다.
|
|
exists, err := v.client.Domain.
|
|
Query().
|
|
Where(
|
|
entdomain.DomainEQ(d),
|
|
entdomain.ClientAPIKeyEQ(key),
|
|
).
|
|
Exist(ctx)
|
|
if err != nil {
|
|
log.Error("failed to query domain/client_api_key from db", logging.Fields{
|
|
"error": err.Error(),
|
|
})
|
|
return fmt.Errorf("domain validator: db query failed: %w", err)
|
|
}
|
|
|
|
if !exists {
|
|
log.Warn("no matching domain/client_api_key found", nil)
|
|
return fmt.Errorf("domain validator: domain/api_key not found")
|
|
}
|
|
|
|
log.Debug("domain/api_key validation succeeded", nil)
|
|
return nil
|
|
}
|