feat: ratelimit, ghcr package support, media path sensitization, LID to CN migration (#6)
* feat: ratelimit, ghcr package and proper project refactor from askarzh * fix(bridge): validate media path * update: dependencies * fix: added LID to contact number migration
This commit is contained in:
@@ -2,7 +2,9 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type dbConfig struct {
|
||||
@@ -14,41 +16,66 @@ type dbConfig struct {
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DB dbConfig
|
||||
JWTSecret []byte
|
||||
APIKey string
|
||||
WebhookUrl string
|
||||
DB dbConfig
|
||||
JWTSecret []byte
|
||||
APIKey string
|
||||
WebhookUrl string
|
||||
Host string
|
||||
Port int
|
||||
AuthLoginRate string
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
user, ok := os.LookupEnv("POSTGRES_USER")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_USER")
|
||||
}
|
||||
pass, ok := os.LookupEnv("POSTGRES_PASS")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_PASS")
|
||||
}
|
||||
host, ok := os.LookupEnv("POSTGRES_HOST")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_HOST")
|
||||
}
|
||||
port, ok := os.LookupEnv("POSTGRES_PORT")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_PORT")
|
||||
isPostgres := os.Getenv("IS_POSTGRES") == "true"
|
||||
|
||||
var user, pass, host, port string
|
||||
if isPostgres {
|
||||
var ok bool
|
||||
user, ok = os.LookupEnv("POSTGRES_USER")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_USER")
|
||||
}
|
||||
pass, ok = os.LookupEnv("POSTGRES_PASS")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_PASS")
|
||||
}
|
||||
host, ok = os.LookupEnv("POSTGRES_HOST")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_HOST")
|
||||
}
|
||||
port, ok = os.LookupEnv("POSTGRES_PORT")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing POSTGRES_PORT")
|
||||
}
|
||||
}
|
||||
|
||||
jwtSecret, ok := os.LookupEnv("JWT_SECRET")
|
||||
jwtSecret, ok := lookupEither("WHATSAPP_JWT_SECRET", "JWT_SECRET")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing JWT_SECRET")
|
||||
return nil, fmt.Errorf("missing WHATSAPP_JWT_SECRET")
|
||||
}
|
||||
apiKey, ok := os.LookupEnv("API_KEY")
|
||||
apiKey, ok := lookupEither("WHATSAPP_API_KEY", "API_KEY")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing API_KEY")
|
||||
return nil, fmt.Errorf("missing WHATSAPP_API_KEY")
|
||||
}
|
||||
if err := validateSecret("WHATSAPP_JWT_SECRET (or deprecated alias JWT_SECRET)", jwtSecret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := validateSecret("WHATSAPP_API_KEY (or deprecated alias API_KEY)", apiKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webhookUrl := os.Getenv("WEBHOOK_URL")
|
||||
|
||||
isPostgres := os.Getenv("IS_POSTGRES") == "true"
|
||||
serverHost := os.Getenv("HOST")
|
||||
serverPort := 8080
|
||||
if v, ok := os.LookupEnv("PORT"); ok {
|
||||
p, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid PORT %q: %w", v, err)
|
||||
}
|
||||
serverPort = p
|
||||
}
|
||||
|
||||
authLoginRate := os.Getenv("AUTH_LOGIN_RATE") // parsed in auth package; empty -> default
|
||||
|
||||
return &Config{
|
||||
DB: dbConfig{
|
||||
@@ -58,8 +85,53 @@ func LoadConfig() (*Config, error) {
|
||||
Port: port,
|
||||
IsPostgres: isPostgres,
|
||||
},
|
||||
JWTSecret: []byte(jwtSecret),
|
||||
APIKey: apiKey,
|
||||
WebhookUrl: webhookUrl,
|
||||
JWTSecret: []byte(jwtSecret),
|
||||
APIKey: apiKey,
|
||||
WebhookUrl: webhookUrl,
|
||||
Host: serverHost,
|
||||
Port: serverPort,
|
||||
AuthLoginRate: authLoginRate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const minSecretLen = 32
|
||||
|
||||
// knownPlaceholders are the example values shipped in docker-compose.yaml.
|
||||
// Operators who forget to override them must see startup fail loudly.
|
||||
var knownPlaceholders = []string{
|
||||
"c3VwZXItbG9uZy1yYW5kb20tc3RyaW5nLW1pbmltdW0tb2YtNjQtY2hhcmFjdGVycy15b3UtbmVlZC10by1wYXN0ZS1oZXJl",
|
||||
"YW5vdGhlci1zdXBlci1sb25nLXJhbmRvbS1zdHJpbmctbWluaW11bS1vZi02NC1jaGFyYWN0ZXJzLXlvdS1uZWVkLXRvLXBhc3RlLWhlcmU=",
|
||||
}
|
||||
|
||||
func validateSecret(name, value string) error {
|
||||
for _, ph := range knownPlaceholders {
|
||||
if value == ph {
|
||||
return fmt.Errorf("%s is set to a placeholder value; generate a real one with `openssl rand -base64 48`", name)
|
||||
}
|
||||
}
|
||||
if len(value) < minSecretLen {
|
||||
return fmt.Errorf("%s is too short (%d chars, need ≥%d)", name, len(value), minSecretLen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// envWarnFn is replaced in tests; in production it logs via slog.
|
||||
var envWarnFn = func(msg string, args ...any) {
|
||||
slog.Warn(msg, args...)
|
||||
}
|
||||
|
||||
// lookupEither returns the value for `primary` if set, otherwise falls back
|
||||
// to `deprecated` and emits a deprecation warning. Returns ("", false) only
|
||||
// when neither is set.
|
||||
func lookupEither(primary, deprecated string) (string, bool) {
|
||||
if v, ok := os.LookupEnv(primary); ok {
|
||||
return v, true
|
||||
}
|
||||
if v, ok := os.LookupEnv(deprecated); ok {
|
||||
envWarnFn("env var is deprecated, use the new name",
|
||||
"deprecated", deprecated,
|
||||
"use_instead", primary)
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user