Files
api-golang/internal/config/config.go
Flavio Fois 3ec7bb5222
Some checks failed
Build & Publish Docker Image / build-and-push (push) Failing after 11s
add Cloudflare R2 storage integration and update bug report handling
2026-05-27 21:35:26 +02:00

170 lines
4.1 KiB
Go

package config
import (
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
type RateLimitConfig struct {
UnauthMaxReqs int
UnauthWindow time.Duration
UnauthMaxFails int
UnauthBanDur time.Duration
AuthMaxReqs int
AuthWindow time.Duration
AuthMaxFails int
AuthBanDur time.Duration
}
type R2Config struct {
AccountID string
AccessKeyID string
SecretAccessKey string
BucketName string
Region string
Endpoint string
}
type Config struct {
Port string
DSN string
Database string
APIKey string
AdminKey string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime int
UpdatesEnabled bool
UseS3CompatibleStorage bool
RateLimit RateLimitConfig
R2 R2Config
}
var (
instance *Config
once sync.Once
)
func Load() *Config {
once.Do(func() { instance = load() })
return instance
}
func load() *Config {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
raw := os.Getenv("API_KEY")
var apiKey string
for _, k := range strings.Split(raw, ",") {
k = strings.TrimSpace(k)
if k != "" {
apiKey = k
break
}
}
raw = os.Getenv("ADMIN_KEY")
var adminKey string
for _, k := range strings.Split(raw, ",") {
k = strings.TrimSpace(k)
if k != "" {
adminKey = k
break
}
}
maxOpenConns, err := strconv.Atoi(os.Getenv("DB_MAX_OPEN_CONNS"))
if err != nil {
maxOpenConns = 30
}
maxIdleConns, err := strconv.Atoi(os.Getenv("DB_MAX_IDLE_CONNS"))
if err != nil {
maxIdleConns = 5
}
connMaxLifetime, err := strconv.Atoi(os.Getenv("DB_CONN_MAX_LIFETIME"))
if err != nil {
connMaxLifetime = 5
}
dbName := os.Getenv("DATABASE_NAME")
if dbName == "" {
panic("DATABASE_NAME environment variable is required")
}
dbNameRegex := regexp.MustCompile("^[a-zA-Z0-9_]+$")
// Test the regex against the dbName, otherwise panic to prevent potential SQL injection
validDbName, err := regexp.Match(dbNameRegex.String(), []byte(dbName))
if err != nil {
panic("failed to validate database name: " + err.Error())
}
if !validDbName {
panic("invalid database name: must match regex " + dbNameRegex.String())
}
if os.Getenv("DB_DSN") == "" {
panic("DB_DSN environment variable is required")
}
return &Config{
Port: port,
DSN: os.Getenv("DB_DSN"),
Database: dbName,
APIKey: apiKey,
AdminKey: adminKey,
MaxOpenConns: maxOpenConns,
MaxIdleConns: maxIdleConns,
ConnMaxLifetime: connMaxLifetime,
UpdatesEnabled: strings.ToLower(strings.TrimSpace(os.Getenv("UPDATES_ENABLED"))) == "true",
UseS3CompatibleStorage: strings.ToLower(strings.TrimSpace(os.Getenv("USE_S3_COMPATIBLE_STORAGE"))) == "true",
R2: R2Config{
AccountID: os.Getenv("CF_ACCOUNT_ID"),
AccessKeyID: os.Getenv("CF_R2_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("CF_R2_SECRET_ACCESS_KEY"),
BucketName: os.Getenv("CF_R2_BUCKET_NAME"),
Region: envString("CF_R2_REGION", "auto"),
Endpoint: os.Getenv("CF_R2_ENDPOINT"),
},
RateLimit: RateLimitConfig{
UnauthMaxReqs: envInt("RL_UNAUTH_MAX_REQS", 10),
UnauthWindow: envDuration("RL_UNAUTH_WINDOW", 5*time.Minute),
UnauthMaxFails: envInt("RL_UNAUTH_MAX_FAILS", 5),
UnauthBanDur: envDuration("RL_UNAUTH_BAN_DUR", 15*time.Minute),
AuthMaxReqs: envInt("RL_AUTH_MAX_REQS", 100),
AuthWindow: envDuration("RL_AUTH_WINDOW", time.Minute),
AuthMaxFails: envInt("RL_AUTH_MAX_FAILS", 20),
AuthBanDur: envDuration("RL_AUTH_BAN_DUR", 5*time.Minute),
},
}
}
func envString(key, fallback string) string {
if s := os.Getenv(key); s != "" {
return s
}
return fallback
}
func envInt(key string, fallback int) int {
if s := os.Getenv(key); s != "" {
if n, err := strconv.Atoi(s); err == nil {
return n
}
}
return fallback
}
func envDuration(key string, fallback time.Duration) time.Duration {
if s := os.Getenv(key); s != "" {
if d, err := time.ParseDuration(s); err == nil {
return d
}
}
return fallback
}