diff options
| author | Chia <Chia@93.nz> | 2026-08-05 14:48:00 +1200 |
|---|---|---|
| committer | Chia <Chia@93.nz> | 2026-08-05 14:48:00 +1200 |
| commit | cd0dd91ab93653631904f2ea0e574ccde6d60339 (patch) | |
| tree | c65417b880a3f4a35c504c44edae821bc2122f70 /internal/config/config.go | |
| parent | 86b1f42e3c5601ff10621a9779cf0076590797a1 (diff) | |
add passkey, totp.
Diffstat (limited to '')
| -rw-r--r-- | internal/config/config.go | 171 |
1 files changed, 157 insertions, 14 deletions
diff --git a/internal/config/config.go b/internal/config/config.go index 97ecb21..376cf77 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -26,7 +26,8 @@ type Config struct { } type ServerConfig struct { - Address string `json:"address"` + Address string `json:"-"` + AddressEnv string `json:"address_env"` MaxBodyBytes int64 `json:"max_body_bytes"` ReadHeaderTimeoutSecs int `json:"read_header_timeout_seconds"` IdleTimeoutSecs int `json:"idle_timeout_seconds"` @@ -53,12 +54,40 @@ type ControlPlaneConfig struct { } type AdminConfig struct { - Enabled bool `json:"enabled"` - TokenEnv string `json:"token_env"` - BasePath string `json:"base_path"` - RegistrationEnabled bool `json:"registration_enabled"` - SessionTTLHours int `json:"session_ttl_hours"` - Token string `json:"-"` + Enabled bool `json:"enabled"` + TokenEnv string `json:"token_env"` + BasePath string `json:"base_path"` + RegistrationEnabled bool `json:"registration_enabled"` + SessionTTLHours int `json:"session_ttl_hours"` + PublicURL string `json:"-"` + PublicURLEnv string `json:"public_url_env"` + Mail MailConfig `json:"mail"` + WebAuthn WebAuthnConfig `json:"webauthn"` + Token string `json:"-"` +} + +type MailConfig struct { + Enabled bool `json:"enabled"` + FromName string `json:"from_name"` + TLSMode string `json:"tls_mode"` + FromAddressEnv string `json:"from_address_env"` + SMTPAddressEnv string `json:"smtp_address_env"` + SMTPUsernameEnv string `json:"smtp_username_env"` + SMTPPasswordEnv string `json:"smtp_password_env"` + SMTPImplicitTLS bool `json:"smtp_implicit_tls"` + FromAddress string `json:"-"` + SMTPAddress string `json:"-"` + SMTPUsername string `json:"-"` + SMTPPassword string `json:"-"` +} + +type WebAuthnConfig struct { + Enabled bool `json:"enabled"` + RPDisplayName string `json:"rp_display_name"` + RPIDEnv string `json:"rp_id_env"` + OriginsEnv string `json:"origins_env"` + RPID string `json:"-"` + Origins []string `json:"-"` } type UpstreamHTTPConfig struct { @@ -69,11 +98,12 @@ type UpstreamHTTPConfig struct { } type ProviderConfig struct { - ID string `json:"id"` - Protocol domain.Protocol `json:"protocol"` - BaseURL string `json:"base_url"` - APIKeyEnv string `json:"api_key_env"` - APIKey string `json:"-"` + ID string `json:"id"` + Protocol domain.Protocol `json:"protocol"` + BaseURL string `json:"-"` + BaseURLEnv string `json:"base_url_env"` + APIKeyEnv string `json:"api_key_env"` + APIKey string `json:"-"` } type ModelConfig struct { @@ -111,8 +141,10 @@ type StripeConfig struct { Enabled bool `json:"enabled"` APIKeyEnv string `json:"api_key_env"` WebhookSecretEnv string `json:"webhook_secret_env"` - SuccessURL string `json:"success_url"` - CancelURL string `json:"cancel_url"` + SuccessURL string `json:"-"` + CancelURL string `json:"-"` + SuccessURLEnv string `json:"success_url_env"` + CancelURLEnv string `json:"cancel_url_env"` APIKey string `json:"-"` WebhookSecret string `json:"-"` } @@ -148,6 +180,9 @@ func Load(path string) (Config, error) { } func applyDefaults(cfg *Config) { + if cfg.Server.AddressEnv == "" { + cfg.Server.AddressEnv = "AIGW_SERVER_ADDRESS" + } if cfg.Server.Address == "" { cfg.Server.Address = ":8080" } @@ -193,6 +228,36 @@ func applyDefaults(cfg *Config) { if cfg.Admin.SessionTTLHours == 0 { cfg.Admin.SessionTTLHours = 12 } + if cfg.Admin.PublicURLEnv == "" { + cfg.Admin.PublicURLEnv = "AIGW_PUBLIC_URL" + } + if cfg.Admin.Mail.FromName == "" { + cfg.Admin.Mail.FromName = "AIGW" + } + if cfg.Admin.Mail.TLSMode == "" { + cfg.Admin.Mail.TLSMode = "starttls" + } + if cfg.Admin.Mail.FromAddressEnv == "" { + cfg.Admin.Mail.FromAddressEnv = "AIGW_SMTP_FROM_ADDRESS" + } + if cfg.Admin.Mail.SMTPAddressEnv == "" { + cfg.Admin.Mail.SMTPAddressEnv = "AIGW_SMTP_ADDRESS" + } + if cfg.Admin.Mail.SMTPUsernameEnv == "" { + cfg.Admin.Mail.SMTPUsernameEnv = "AIGW_SMTP_USERNAME" + } + if cfg.Admin.Mail.SMTPPasswordEnv == "" { + cfg.Admin.Mail.SMTPPasswordEnv = "AIGW_SMTP_PASSWORD" + } + if cfg.Admin.WebAuthn.RPDisplayName == "" { + cfg.Admin.WebAuthn.RPDisplayName = "AIGW Console" + } + if cfg.Admin.WebAuthn.RPIDEnv == "" { + cfg.Admin.WebAuthn.RPIDEnv = "AIGW_WEBAUTHN_RP_ID" + } + if cfg.Admin.WebAuthn.OriginsEnv == "" { + cfg.Admin.WebAuthn.OriginsEnv = "AIGW_WEBAUTHN_ORIGINS" + } if cfg.UpstreamHTTP.MaxIdleConnections == 0 { cfg.UpstreamHTTP.MaxIdleConnections = 4096 } @@ -226,6 +291,12 @@ func applyDefaults(cfg *Config) { if cfg.Billing.Stripe.WebhookSecretEnv == "" { cfg.Billing.Stripe.WebhookSecretEnv = "AIGW_STRIPE_WEBHOOK_SECRET" } + if cfg.Billing.Stripe.SuccessURLEnv == "" { + cfg.Billing.Stripe.SuccessURLEnv = "AIGW_STRIPE_SUCCESS_URL" + } + if cfg.Billing.Stripe.CancelURLEnv == "" { + cfg.Billing.Stripe.CancelURLEnv = "AIGW_STRIPE_CANCEL_URL" + } for i := range cfg.Models { for j := range cfg.Models[i].Routes { if cfg.Models[i].Routes[j].Weight == 0 { @@ -236,6 +307,9 @@ func applyDefaults(cfg *Config) { } func resolveSecrets(cfg *Config) error { + if value := strings.TrimSpace(os.Getenv(cfg.Server.AddressEnv)); value != "" { + cfg.Server.Address = value + } if cfg.ControlPlane.Enabled { cfg.ControlPlane.DatabaseURL = os.Getenv(cfg.ControlPlane.DatabaseURLEnv) cfg.ControlPlane.RedisURL = os.Getenv(cfg.ControlPlane.RedisURLEnv) @@ -243,13 +317,42 @@ func resolveSecrets(cfg *Config) error { } if cfg.Admin.Enabled { cfg.Admin.Token = os.Getenv(cfg.Admin.TokenEnv) + if err := resolveRequiredEnv(&cfg.Admin.PublicURL, cfg.Admin.PublicURLEnv, "admin.public_url"); err != nil { + return err + } + if cfg.Admin.Mail.Enabled { + cfg.Admin.Mail.FromAddress = strings.TrimSpace(os.Getenv(cfg.Admin.Mail.FromAddressEnv)) + cfg.Admin.Mail.SMTPAddress = strings.TrimSpace(os.Getenv(cfg.Admin.Mail.SMTPAddressEnv)) + cfg.Admin.Mail.SMTPUsername = os.Getenv(cfg.Admin.Mail.SMTPUsernameEnv) + cfg.Admin.Mail.SMTPPassword = os.Getenv(cfg.Admin.Mail.SMTPPasswordEnv) + } + if cfg.Admin.WebAuthn.Enabled { + cfg.Admin.WebAuthn.RPID = strings.TrimSpace(os.Getenv(cfg.Admin.WebAuthn.RPIDEnv)) + for _, origin := range strings.Split(os.Getenv(cfg.Admin.WebAuthn.OriginsEnv), ",") { + if origin = strings.TrimSpace(origin); origin != "" { + cfg.Admin.WebAuthn.Origins = append(cfg.Admin.WebAuthn.Origins, origin) + } + } + } } if cfg.Billing.Enabled && cfg.Billing.Stripe.Enabled { cfg.Billing.Stripe.APIKey = os.Getenv(cfg.Billing.Stripe.APIKeyEnv) cfg.Billing.Stripe.WebhookSecret = os.Getenv(cfg.Billing.Stripe.WebhookSecretEnv) + if err := resolveRequiredEnv(&cfg.Billing.Stripe.SuccessURL, cfg.Billing.Stripe.SuccessURLEnv, "billing.stripe.success_url"); err != nil { + return err + } + if err := resolveRequiredEnv(&cfg.Billing.Stripe.CancelURL, cfg.Billing.Stripe.CancelURLEnv, "billing.stripe.cancel_url"); err != nil { + return err + } } for i := range cfg.Providers { provider := &cfg.Providers[i] + if provider.BaseURLEnv == "" { + return fmt.Errorf("provider %q: base_url_env is required", provider.ID) + } + if err := resolveRequiredEnv(&provider.BaseURL, provider.BaseURLEnv, fmt.Sprintf("provider %q base_url", provider.ID)); err != nil { + return err + } if provider.APIKeyEnv == "" { continue } @@ -261,6 +364,18 @@ func resolveSecrets(cfg *Config) error { return nil } +func resolveRequiredEnv(target *string, environment, field string) error { + if environment == "" { + return nil + } + value := strings.TrimSpace(os.Getenv(environment)) + if value == "" { + return fmt.Errorf("%s: environment variable %s is empty", field, environment) + } + *target = value + return nil +} + func Validate(cfg Config) error { if cfg.Server.MaxBodyBytes < 1024 { return errors.New("server.max_body_bytes must be at least 1024") @@ -293,6 +408,34 @@ func Validate(cfg Config) error { if cfg.Admin.SessionTTLHours < 1 || cfg.Admin.SessionTTLHours > 720 { return errors.New("admin.session_ttl_hours must be between 1 and 720") } + publicURL, err := url.Parse(cfg.Admin.PublicURL) + if err != nil || publicURL.Host == "" || (publicURL.Scheme != "http" && publicURL.Scheme != "https") { + return errors.New("admin.public_url must resolve from an environment variable to an absolute http(s) URL") + } + if cfg.Admin.Mail.Enabled { + if cfg.Admin.Mail.FromAddress == "" || cfg.Admin.Mail.SMTPAddress == "" { + return errors.New("admin.mail requires SMTP address and from address environment variables") + } + if (cfg.Admin.Mail.SMTPUsername == "") != (cfg.Admin.Mail.SMTPPassword == "") { + return errors.New("admin.mail SMTP username and password must both be set or both be empty") + } + switch cfg.Admin.Mail.TLSMode { + case "starttls", "tls", "none": + default: + return errors.New("admin.mail.tls_mode must be starttls, tls, or none") + } + } + if cfg.Admin.WebAuthn.Enabled { + if cfg.Admin.WebAuthn.RPID == "" || len(cfg.Admin.WebAuthn.Origins) == 0 { + return errors.New("admin.webauthn requires RP ID and origins environment variables") + } + for _, origin := range cfg.Admin.WebAuthn.Origins { + parsed, err := url.Parse(origin) + if err != nil || parsed.Host == "" || (parsed.Scheme != "https" && !(parsed.Scheme == "http" && parsed.Hostname() == "localhost")) { + return fmt.Errorf("admin.webauthn origin %q must use https (http is allowed only for localhost)", origin) + } + } + } } if cfg.Billing.Enabled { if !cfg.ControlPlane.Enabled { |
