Some checks failed
Go / build-and-release (push) Has been cancelled
Major refactoring of event handling into clean, testable domain services: - Add pkg/event/validation: JSON hex validation, signature verification, timestamp bounds, NIP-70 protected tag validation - Add pkg/event/authorization: Policy and ACL authorization decisions, auth challenge handling, access level determination - Add pkg/event/routing: Event router registry with ephemeral and delete handlers, kind-based dispatch - Add pkg/event/processing: Event persistence, delivery to subscribers, and post-save hooks (ACL reconfig, sync, relay groups) - Reduce handle-event.go from 783 to 296 lines (62% reduction) - Add comprehensive unit tests for all new domain services - Refactor database tests to use shared TestMain setup - Fix blossom URL test expectations (missing "/" separator) - Add go-memory-optimization skill and analysis documentation - Update DDD_ANALYSIS.md to reflect completed decomposition Files modified: - app/handle-event.go: Slim orchestrator using domain services - app/server.go: Service initialization and interface wrappers - app/handle-event-types.go: Shared types (OkHelper, result types) - pkg/event/validation/*: New validation service package - pkg/event/authorization/*: New authorization service package - pkg/event/routing/*: New routing service package - pkg/event/processing/*: New processing service package - pkg/database/*_test.go: Refactored to shared TestMain - pkg/blossom/http_test.go: Fixed URL format expectations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
// Package validation provides event validation services for the ORLY relay.
|
|
// It handles structural validation (hex case, JSON format), cryptographic
|
|
// validation (signature, ID), and protocol validation (timestamp, NIP-70).
|
|
package validation
|
|
|
|
import (
|
|
"git.mleku.dev/mleku/nostr/encoders/event"
|
|
)
|
|
|
|
// ReasonCode identifies the type of validation failure for response formatting.
|
|
type ReasonCode int
|
|
|
|
const (
|
|
ReasonNone ReasonCode = iota
|
|
ReasonBlocked
|
|
ReasonInvalid
|
|
ReasonError
|
|
)
|
|
|
|
// Result contains the outcome of a validation check.
|
|
type Result struct {
|
|
Valid bool
|
|
Code ReasonCode // For response formatting
|
|
Msg string // Human-readable error message
|
|
}
|
|
|
|
// OK returns a successful validation result.
|
|
func OK() Result {
|
|
return Result{Valid: true}
|
|
}
|
|
|
|
// Blocked returns a blocked validation result.
|
|
func Blocked(msg string) Result {
|
|
return Result{Valid: false, Code: ReasonBlocked, Msg: msg}
|
|
}
|
|
|
|
// Invalid returns an invalid validation result.
|
|
func Invalid(msg string) Result {
|
|
return Result{Valid: false, Code: ReasonInvalid, Msg: msg}
|
|
}
|
|
|
|
// Error returns an error validation result.
|
|
func Error(msg string) Result {
|
|
return Result{Valid: false, Code: ReasonError, Msg: msg}
|
|
}
|
|
|
|
// Validator validates events before processing.
|
|
type Validator interface {
|
|
// ValidateRawJSON validates raw message before unmarshaling.
|
|
// This catches issues like uppercase hex that are lost after unmarshal.
|
|
ValidateRawJSON(msg []byte) Result
|
|
|
|
// ValidateEvent validates an unmarshaled event.
|
|
// Checks ID computation, signature, and timestamp.
|
|
ValidateEvent(ev *event.E) Result
|
|
|
|
// ValidateProtectedTag checks NIP-70 protected tag requirements.
|
|
// The authedPubkey is the authenticated pubkey of the connection.
|
|
ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result
|
|
}
|
|
|
|
// Config holds configuration for the validation service.
|
|
type Config struct {
|
|
// MaxFutureSeconds is how far in the future a timestamp can be (default: 3600 = 1 hour)
|
|
MaxFutureSeconds int64
|
|
}
|
|
|
|
// DefaultConfig returns the default validation configuration.
|
|
func DefaultConfig() *Config {
|
|
return &Config{
|
|
MaxFutureSeconds: 3600,
|
|
}
|
|
}
|
|
|
|
// Service implements the Validator interface.
|
|
type Service struct {
|
|
cfg *Config
|
|
}
|
|
|
|
// New creates a new validation service with default configuration.
|
|
func New() *Service {
|
|
return &Service{cfg: DefaultConfig()}
|
|
}
|
|
|
|
// NewWithConfig creates a new validation service with the given configuration.
|
|
func NewWithConfig(cfg *Config) *Service {
|
|
if cfg == nil {
|
|
cfg = DefaultConfig()
|
|
}
|
|
return &Service{cfg: cfg}
|
|
}
|
|
|
|
// ValidateRawJSON validates raw message before unmarshaling.
|
|
func (s *Service) ValidateRawJSON(msg []byte) Result {
|
|
if errMsg := ValidateLowercaseHexInJSON(msg); errMsg != "" {
|
|
return Blocked(errMsg)
|
|
}
|
|
return OK()
|
|
}
|
|
|
|
// ValidateEvent validates an unmarshaled event.
|
|
func (s *Service) ValidateEvent(ev *event.E) Result {
|
|
// Validate event ID
|
|
if result := ValidateEventID(ev); !result.Valid {
|
|
return result
|
|
}
|
|
|
|
// Validate timestamp
|
|
if result := ValidateTimestamp(ev, s.cfg.MaxFutureSeconds); !result.Valid {
|
|
return result
|
|
}
|
|
|
|
// Validate signature
|
|
if result := ValidateSignature(ev); !result.Valid {
|
|
return result
|
|
}
|
|
|
|
return OK()
|
|
}
|
|
|
|
// ValidateProtectedTag checks NIP-70 protected tag requirements.
|
|
func (s *Service) ValidateProtectedTag(ev *event.E, authedPubkey []byte) Result {
|
|
return ValidateProtectedTagMatch(ev, authedPubkey)
|
|
}
|