Parse CORS_HEADER as a list: * for all origins, or reflect matching request Origin when multiple specific origins are configured. Add Vary: Origin for the allowlist case. Update .env.example and CORS tests.
182 lines
4.2 KiB
Go
182 lines
4.2 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
type LightningConfig struct {
|
|
Enabled bool
|
|
LNbitsURL string
|
|
LNbitsInvoiceKey string
|
|
PriceYearlySats int64
|
|
PriceLifetimeSats int64
|
|
InvoiceExpiryMins int
|
|
InvoiceMemoYearly string
|
|
InvoiceMemoLifetime string
|
|
}
|
|
|
|
type NostrConfig struct {
|
|
Relays []string
|
|
UsernameSyncEnabled bool
|
|
SyncIntervalMins int
|
|
}
|
|
|
|
type DMConfig struct {
|
|
Enabled bool
|
|
Nsec string
|
|
Kind int
|
|
MessagesFile string
|
|
}
|
|
|
|
type ExpiryConfig struct {
|
|
ReminderDays []int
|
|
GraceDays int
|
|
CronHourUTC int
|
|
}
|
|
|
|
type WebhookConfig struct {
|
|
URL string
|
|
Secret string
|
|
TimeoutSecs int
|
|
MaxRetries int
|
|
}
|
|
|
|
type Config struct {
|
|
Domain string
|
|
Port int
|
|
AdminAPIKey string
|
|
FrontendURL string
|
|
DatabasePath string
|
|
|
|
Lightning LightningConfig
|
|
Nostr NostrConfig
|
|
DM DMConfig
|
|
Expiry ExpiryConfig
|
|
Webhook WebhookConfig
|
|
|
|
LogLevel string
|
|
RateLimitPerMin int
|
|
ReservedUsernames []string
|
|
|
|
// CORSOrigins is parsed from the CORS_HEADER env var (comma-separated).
|
|
// Use "*" to allow all origins, or list specific origins like
|
|
// "https://example.com,https://other.example".
|
|
CORSOrigins []string
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
_ = godotenv.Load()
|
|
|
|
c := &Config{
|
|
Domain: env("DOMAIN", ""),
|
|
Port: envInt("PORT", 8080),
|
|
AdminAPIKey: env("ADMIN_API_KEY", ""),
|
|
FrontendURL: env("FRONTEND_URL", ""),
|
|
DatabasePath: env("DATABASE_PATH", ".data/nip05.db"),
|
|
Lightning: LightningConfig{
|
|
Enabled: envBool("LIGHTNING_ENABLED", true),
|
|
LNbitsURL: env("LNBITS_URL", ""),
|
|
LNbitsInvoiceKey: env("LNBITS_INVOICE_KEY", ""),
|
|
PriceYearlySats: int64(envInt("PRICE_YEARLY_SATS", 1000)),
|
|
PriceLifetimeSats: int64(envInt("PRICE_LIFETIME_SATS", 10000)),
|
|
InvoiceExpiryMins: envInt("INVOICE_EXPIRY_MINUTES", 30),
|
|
InvoiceMemoYearly: env("INVOICE_MEMO_YEARLY", "Noderunners Relay yearly Access"),
|
|
InvoiceMemoLifetime: env("INVOICE_MEMO_LIFETIME", "Noderunners Relay lifetime Access"),
|
|
},
|
|
Nostr: NostrConfig{
|
|
Relays: csv(env("RELAYS", "")),
|
|
UsernameSyncEnabled: envBool("USERNAME_SYNC_ENABLED", true),
|
|
SyncIntervalMins: envInt("SYNC_INTERVAL_MINUTES", 15),
|
|
},
|
|
DM: DMConfig{
|
|
Enabled: envBool("DM_ENABLED", true),
|
|
Nsec: env("DM_NSEC", ""),
|
|
Kind: envInt("DM_KIND", 1059),
|
|
MessagesFile: env("MESSAGES_FILE", "messages.yaml"),
|
|
},
|
|
Expiry: ExpiryConfig{
|
|
ReminderDays: csvInt(env("EXPIRY_REMINDER_DAYS", "7")),
|
|
GraceDays: envInt("USERNAME_GRACE_DAYS", 30),
|
|
CronHourUTC: envInt("EXPIRY_CRON_HOUR_UTC", 9),
|
|
},
|
|
Webhook: WebhookConfig{
|
|
URL: env("WEBHOOK_URL", ""),
|
|
Secret: env("WEBHOOK_SECRET", ""),
|
|
TimeoutSecs: envInt("WEBHOOK_TIMEOUT_SECONDS", 10),
|
|
MaxRetries: envInt("WEBHOOK_MAX_RETRIES", 5),
|
|
},
|
|
LogLevel: env("LOG_LEVEL", "info"),
|
|
RateLimitPerMin: envInt("RATE_LIMIT_PER_MIN", 30),
|
|
ReservedUsernames: csv(env("RESERVED_USERNAMES", "")),
|
|
CORSOrigins: csv(env("CORS_HEADER", "*")),
|
|
}
|
|
|
|
if err := Validate(c); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
func env(key, def string) string {
|
|
if v, ok := os.LookupEnv(key); ok && v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envInt(key string, def int) int {
|
|
if v, ok := os.LookupEnv(key); ok && v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
return n
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
func envBool(key string, def bool) bool {
|
|
if v, ok := os.LookupEnv(key); ok && v != "" {
|
|
switch strings.ToLower(v) {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
case "0", "false", "no", "off":
|
|
return false
|
|
}
|
|
}
|
|
return def
|
|
}
|
|
|
|
func csv(v string) []string {
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(v, ",")
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func csvInt(v string) []int {
|
|
parts := csv(v)
|
|
out := make([]int, 0, len(parts))
|
|
for _, p := range parts {
|
|
n, err := strconv.Atoi(p)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
out = append(out, n)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *Config) Addr() string { return fmt.Sprintf(":%d", c.Port) }
|