Close all 25 codex audit findings and add KMP contract tests

Remediate all P0-S priority findings from cross-platform architecture audit:
- Add input validation and authorization checks across handlers
- Harden social auth (Apple/Google) token validation
- Add document ownership verification and file type validation
- Add rate limiting config and CORS origin restrictions
- Add subscription tier enforcement in handlers
- Add OpenAPI 3.0.3 spec (81 schemas, 104 operations)
- Add URL-level contract test (KMP API routes match spec paths)
- Add model-level contract test (65 schemas, 464 fields validated)
- Add CI workflow for backend tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-18 13:15:07 -06:00
parent 215e7c895d
commit bb7493f033
23 changed files with 6549 additions and 43 deletions

View File

@@ -28,12 +28,13 @@ type Config struct {
}
type ServerConfig struct {
Port int
Debug bool
AllowedHosts []string
Timezone string
StaticDir string // Directory for static landing page files
BaseURL string // Public base URL for email tracking links (e.g., https://casera.app)
Port int
Debug bool
AllowedHosts []string
CorsAllowedOrigins []string // Comma-separated origins for CORS (production only; debug uses wildcard)
Timezone string
StaticDir string // Directory for static landing page files
BaseURL string // Public base URL for email tracking links (e.g., https://casera.app)
}
type DatabaseConfig struct {
@@ -166,12 +167,13 @@ func Load() (*Config, error) {
cfg = &Config{
Server: ServerConfig{
Port: viper.GetInt("PORT"),
Debug: viper.GetBool("DEBUG"),
AllowedHosts: strings.Split(viper.GetString("ALLOWED_HOSTS"), ","),
Timezone: viper.GetString("TIMEZONE"),
StaticDir: viper.GetString("STATIC_DIR"),
BaseURL: viper.GetString("BASE_URL"),
Port: viper.GetInt("PORT"),
Debug: viper.GetBool("DEBUG"),
AllowedHosts: strings.Split(viper.GetString("ALLOWED_HOSTS"), ","),
CorsAllowedOrigins: parseCorsOrigins(viper.GetString("CORS_ALLOWED_ORIGINS")),
Timezone: viper.GetString("TIMEZONE"),
StaticDir: viper.GetString("STATIC_DIR"),
BaseURL: viper.GetString("BASE_URL"),
},
Database: dbConfig,
Redis: RedisConfig{
@@ -304,10 +306,13 @@ func setDefaults() {
func validate(cfg *Config) error {
if cfg.Security.SecretKey == "" {
// Use a default key but log a warning in production
cfg.Security.SecretKey = "change-me-in-production-secret-key-12345"
if !cfg.Server.Debug {
fmt.Println("WARNING: SECRET_KEY not set, using default (insecure)")
if cfg.Server.Debug {
// In debug mode, use a default key with a warning for local development
cfg.Security.SecretKey = "change-me-in-production-secret-key-12345"
fmt.Println("WARNING: SECRET_KEY not set, using default (debug mode only)")
} else {
// In production, refuse to start without a proper secret key
return fmt.Errorf("FATAL: SECRET_KEY environment variable is required in production (DEBUG=false)")
}
}
@@ -339,6 +344,23 @@ func (p *PushConfig) ReadAPNSKey() (string, error) {
return string(content), nil
}
// parseCorsOrigins splits a comma-separated CORS_ALLOWED_ORIGINS string
// into a slice, trimming whitespace. Returns nil if the input is empty.
func parseCorsOrigins(raw string) []string {
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
var origins []string
for _, p := range parts {
trimmed := strings.TrimSpace(p)
if trimmed != "" {
origins = append(origins, trimmed)
}
}
return origins
}
// parseDatabaseURL parses a PostgreSQL URL into DatabaseConfig
// Format: postgres://user:password@host:port/database?sslmode=disable
func parseDatabaseURL(databaseURL string) (*DatabaseConfig, error) {