internal/api: centralize constants; improve validation & santize errors
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I3c3e4acb12a5a965bfaba950bf9aa5776a6a6964
This commit is contained in:
parent
8392992b41
commit
8187608b38
1 changed files with 17 additions and 16 deletions
|
|
@ -4,12 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"notashelf.dev/watchdog/internal/limits"
|
||||||
maxEventSize = 4 * 1024 // 4KB
|
|
||||||
maxPathLen = 2048
|
|
||||||
maxRefLen = 2048
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Represents an incoming analytics event
|
// Represents an incoming analytics event
|
||||||
|
|
@ -24,20 +20,20 @@ type Event struct {
|
||||||
// Parses an event from the request body with size limits
|
// Parses an event from the request body with size limits
|
||||||
func ParseEvent(body io.Reader) (*Event, error) {
|
func ParseEvent(body io.Reader) (*Event, error) {
|
||||||
// Limit read size to prevent memory exhaustion
|
// Limit read size to prevent memory exhaustion
|
||||||
limited := io.LimitReader(body, maxEventSize+1)
|
limited := io.LimitReader(body, limits.MaxEventSize+1)
|
||||||
|
|
||||||
data, err := io.ReadAll(limited)
|
data, err := io.ReadAll(limited)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read body: %w", err)
|
return nil, fmt.Errorf("failed to read body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) > maxEventSize {
|
if len(data) > limits.MaxEventSize {
|
||||||
return nil, fmt.Errorf("event payload too large: %d bytes (max %d)", len(data), maxEventSize)
|
return nil, fmt.Errorf("event payload too large")
|
||||||
}
|
}
|
||||||
|
|
||||||
var event Event
|
var event Event
|
||||||
if err := json.Unmarshal(data, &event); err != nil {
|
if err := json.Unmarshal(data, &event); err != nil {
|
||||||
return nil, fmt.Errorf("invalid JSON: %w", err)
|
return nil, fmt.Errorf("invalid JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &event, nil
|
return &event, nil
|
||||||
|
|
@ -46,23 +42,28 @@ func ParseEvent(body io.Reader) (*Event, error) {
|
||||||
// Validate checks if the event is valid for the given domain
|
// Validate checks if the event is valid for the given domain
|
||||||
func (e *Event) Validate(expectedDomain string) error {
|
func (e *Event) Validate(expectedDomain string) error {
|
||||||
if e.Domain == "" {
|
if e.Domain == "" {
|
||||||
return fmt.Errorf("domain is required")
|
return fmt.Errorf("domain required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Domain != expectedDomain {
|
if e.Domain != expectedDomain {
|
||||||
return fmt.Errorf("domain mismatch: got %q, expected %q", e.Domain, expectedDomain)
|
return fmt.Errorf("domain mismatch")
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Path == "" {
|
if e.Path == "" {
|
||||||
return fmt.Errorf("path is required")
|
return fmt.Errorf("path required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.Path) > maxPathLen {
|
if len(e.Path) > limits.MaxPathLen {
|
||||||
return fmt.Errorf("path too long: %d bytes (max %d)", len(e.Path), maxPathLen)
|
return fmt.Errorf("path too long")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.Referrer) > maxRefLen {
|
if len(e.Referrer) > limits.MaxRefLen {
|
||||||
return fmt.Errorf("referrer too long: %d bytes (max %d)", len(e.Referrer), maxRefLen)
|
return fmt.Errorf("referrer too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate screen width is in reasonable range
|
||||||
|
if e.Width < 0 || e.Width > limits.MaxWidth {
|
||||||
|
return fmt.Errorf("invalid width")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue