You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
124 lines
3.3 KiB
124 lines
3.3 KiB
// 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) |
|
}
|
|
|