fix: harden terminal-scoped analytics contracts
Keep JSON and bounded CSV exports on one canonical predicate, replace replayable offsets with encrypted tenant/filter-bound keyset cursors, and suppress merchant-grain peers under terminal scope. Validate period filters and align OpenAPI and capability specs with runtime behavior.
This commit is contained in:
@@ -16,6 +16,7 @@ type Config struct {
|
||||
DemoUsername string
|
||||
DemoPassword string
|
||||
SessionSecret string
|
||||
CursorSecret string
|
||||
FrontendOrigin string
|
||||
CookieSecure bool
|
||||
}
|
||||
@@ -30,6 +31,7 @@ func Load() (Config, error) {
|
||||
DemoUsername: value("DEMO_USERNAME", "merchant-0001"),
|
||||
DemoPassword: os.Getenv("DEMO_PASSWORD"),
|
||||
SessionSecret: os.Getenv("SESSION_SECRET"),
|
||||
CursorSecret: os.Getenv("CURSOR_SECRET"),
|
||||
FrontendOrigin: value("FRONTEND_ORIGIN", "http://localhost:5173"),
|
||||
}
|
||||
|
||||
@@ -48,6 +50,12 @@ func (c Config) ValidateAPI() error {
|
||||
if len(c.SessionSecret) < 32 {
|
||||
return errors.New("config: SESSION_SECRET must contain at least 32 characters")
|
||||
}
|
||||
if len(c.CursorSecret) < 32 {
|
||||
return errors.New("config: CURSOR_SECRET must contain at least 32 characters")
|
||||
}
|
||||
if c.CursorSecret == c.SessionSecret {
|
||||
return errors.New("config: CURSOR_SECRET must differ from SESSION_SECRET")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,25 @@ func TestLoadRequiresDatabaseURL(t *testing.T) {
|
||||
|
||||
func TestValidateAPIRequiresLongSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := Config{SessionSecret: "short"}
|
||||
cfg := Config{SessionSecret: "short", CursorSecret: "cursor-secret-that-is-at-least-32-bytes"}
|
||||
if err := cfg.ValidateAPI(); err == nil {
|
||||
t.Fatal("ValidateAPI() error = nil, want secret length error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAPIRequiresLongCursorSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg := Config{SessionSecret: "session-secret-that-is-at-least-32-bytes", CursorSecret: "short"}
|
||||
if err := cfg.ValidateAPI(); err == nil {
|
||||
t.Fatal("ValidateAPI() error = nil, want cursor secret length error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAPIRequiresIndependentCursorSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
secret := "shared-secret-that-is-at-least-32-characters"
|
||||
cfg := Config{SessionSecret: secret, CursorSecret: secret}
|
||||
if err := cfg.ValidateAPI(); err == nil {
|
||||
t.Fatal("ValidateAPI() error = nil, want independent secrets error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user