The decentralization stack overcomplicated the project. Removed:
internal/pow, internal/smt, internal/checkpoint, internal/bft,
internal/lightnode, cmd/lightnode, relay gossip/checkpoint/proof/BFT
endpoints, their docs, vectors and the blake3 dependency.
Kept: WebSocket streaming on the relay, the full protocol v1 object set
including DelegationClaim (0x07) and KeyRotation request/confirm
(0x08/0x09) with chain resolution in verify.Graph, TrustedIssuers,
batch fetch, stable cursor pagination, per-type metrics.
INV-1 reverts to its original form: the relay holds no keys again.
Everything removed remains reachable at commit 20cc52c.
137 lines
3.9 KiB
Go
137 lines
3.9 KiB
Go
package server
|
|
|
|
import (
|
|
"os"
|
|
"time"
|
|
|
|
"git.n1ko.dev/Niko/niko_trust/internal/tce"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Config holds every operator-tunable knob for the relay. All fields have sane
|
|
// defaults (see DefaultConfig); a YAML file overrides only the keys it sets.
|
|
type Config struct {
|
|
ListenAddr string // HTTP listen address, e.g. ":8080"
|
|
Audience string // audience bound into auth assertions
|
|
DataDir string // directory to persist objects ("" = in-memory)
|
|
LogLevel string // debug | info | warn | error
|
|
|
|
// Per-IP fixed-window rate limits.
|
|
PutLimit int // POST /v1/objects requests per window
|
|
PutWindow time.Duration // window for PutLimit
|
|
ChallengeLimit int // POST /v1/auth/challenge requests per window
|
|
ChallengeWindow time.Duration // window for ChallengeLimit
|
|
|
|
MaxPerSubject int // max claims stored per subject address
|
|
|
|
ChallengeTTL time.Duration // validity of an issued auth challenge
|
|
SessionTTL time.Duration // validity of a verified auth session
|
|
|
|
MaxBodyBytes int64 // hard cap on any request body
|
|
}
|
|
|
|
// configFile mirrors Config but keeps durations as strings so they can be
|
|
// written as "5m" / "1m" in YAML (yaml.v3 does not parse time.Duration).
|
|
type configFile struct {
|
|
ListenAddr string `yaml:"listen_addr"`
|
|
Audience string `yaml:"audience"`
|
|
DataDir string `yaml:"data_dir"`
|
|
LogLevel string `yaml:"log_level"`
|
|
PutLimit int `yaml:"put_limit"`
|
|
PutWindow string `yaml:"put_window"`
|
|
ChallengeLimit int `yaml:"challenge_limit"`
|
|
ChallengeWindow string `yaml:"challenge_window"`
|
|
MaxPerSubject int `yaml:"max_per_subject"`
|
|
ChallengeTTL string `yaml:"challenge_ttl"`
|
|
SessionTTL string `yaml:"session_ttl"`
|
|
MaxBodyBytes int64 `yaml:"max_body_bytes"`
|
|
}
|
|
|
|
// DefaultConfig returns the built-in defaults.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
ListenAddr: ":8080",
|
|
Audience: "trust.n1ko.dev",
|
|
DataDir: "data", // persist objects; "" would be in-memory only
|
|
LogLevel: "info",
|
|
PutLimit: 60,
|
|
PutWindow: time.Minute,
|
|
ChallengeLimit: 30,
|
|
ChallengeWindow: time.Minute,
|
|
MaxPerSubject: 1000,
|
|
ChallengeTTL: 5 * time.Minute,
|
|
SessionTTL: 30 * time.Minute,
|
|
MaxBodyBytes: tce.MaxClaimTCE*2 + 1024,
|
|
}
|
|
}
|
|
|
|
// LoadConfig reads a YAML config file. A missing file yields the defaults
|
|
// (so the server can run config-less); any other read or parse error is
|
|
// returned.
|
|
func LoadConfig(path string) (Config, error) {
|
|
cfg := DefaultConfig()
|
|
if path == "" {
|
|
return cfg, nil
|
|
}
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return cfg, nil
|
|
}
|
|
return cfg, err
|
|
}
|
|
return ParseConfig(b)
|
|
}
|
|
|
|
// ParseConfig decodes YAML bytes into a Config, layering overrides on top of
|
|
// the defaults.
|
|
func ParseConfig(b []byte) (Config, error) {
|
|
cfg := DefaultConfig()
|
|
var f configFile
|
|
if err := yaml.Unmarshal(b, &f); err != nil {
|
|
return cfg, err
|
|
}
|
|
applyConfigFile(&cfg, &f)
|
|
return cfg, nil
|
|
}
|
|
|
|
func applyConfigFile(cfg *Config, f *configFile) {
|
|
if f.ListenAddr != "" {
|
|
cfg.ListenAddr = f.ListenAddr
|
|
}
|
|
if f.Audience != "" {
|
|
cfg.Audience = f.Audience
|
|
}
|
|
if f.DataDir != "" {
|
|
cfg.DataDir = f.DataDir
|
|
}
|
|
if f.LogLevel != "" {
|
|
cfg.LogLevel = f.LogLevel
|
|
}
|
|
if f.PutLimit != 0 {
|
|
cfg.PutLimit = f.PutLimit
|
|
}
|
|
cfg.PutWindow = parseDur(f.PutWindow, cfg.PutWindow)
|
|
if f.ChallengeLimit != 0 {
|
|
cfg.ChallengeLimit = f.ChallengeLimit
|
|
}
|
|
cfg.ChallengeWindow = parseDur(f.ChallengeWindow, cfg.ChallengeWindow)
|
|
if f.MaxPerSubject != 0 {
|
|
cfg.MaxPerSubject = f.MaxPerSubject
|
|
}
|
|
cfg.ChallengeTTL = parseDur(f.ChallengeTTL, cfg.ChallengeTTL)
|
|
cfg.SessionTTL = parseDur(f.SessionTTL, cfg.SessionTTL)
|
|
if f.MaxBodyBytes != 0 {
|
|
cfg.MaxBodyBytes = f.MaxBodyBytes
|
|
}
|
|
}
|
|
|
|
func parseDur(s string, def time.Duration) time.Duration {
|
|
if s == "" {
|
|
return def
|
|
}
|
|
if d, err := time.ParseDuration(s); err == nil {
|
|
return d
|
|
}
|
|
return def
|
|
}
|