summaryrefslogtreecommitdiff
path: root/internal/config/config.go
diff options
context:
space:
mode:
authorChia <Chia@93.nz>2026-08-05 14:48:00 +1200
committerChia <Chia@93.nz>2026-08-05 14:48:00 +1200
commitcd0dd91ab93653631904f2ea0e574ccde6d60339 (patch)
treec65417b880a3f4a35c504c44edae821bc2122f70 /internal/config/config.go
parent86b1f42e3c5601ff10621a9779cf0076590797a1 (diff)
add passkey, totp.
Diffstat (limited to 'internal/config/config.go')
-rw-r--r--internal/config/config.go171
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 {