package config import ( "encoding/hex" "errors" "github.com/google/uuid" "net/netip" "net/url" "os" "strings" ) type OnboardingConfig struct { Enabled, PauseClaims, ActivationEnabled, SMSLoginEnabled, AllowLocalHTTP bool MasterKey, EntryURL, SMSGatewayURL, SMSGatewayToken string TrustedProxyCIDRs, SelfSignupTenantIDs []string } func loadOnboardingConfig(secrets map[string]string) (OnboardingConfig, error) { c := OnboardingConfig{MasterKey: secrets["ONBOARDING_MASTER_KEY"], SMSGatewayToken: secrets["ONBOARDING_SMS_GATEWAY_TOKEN"], EntryURL: strings.TrimSpace(os.Getenv("ONBOARDING_ENTRY_URL")), SMSGatewayURL: strings.TrimSpace(os.Getenv("ONBOARDING_SMS_GATEWAY_URL"))} for name, target := range map[string]*bool{"ONBOARDING_ENABLED": &c.Enabled, "ONBOARDING_PAUSE_CLAIMS": &c.PauseClaims, "ONBOARDING_ACTIVATION_ENABLED": &c.ActivationEnabled, "ONBOARDING_SMS_LOGIN_ENABLED": &c.SMSLoginEnabled, "ONBOARDING_ALLOW_LOCAL_HTTP": &c.AllowLocalHTTP} { v, err := boolEnv(name, false) if err != nil { return c, err } *target = v } split := func(name string) []string { items := []string{} for _, v := range strings.Split(os.Getenv(name), ",") { if strings.TrimSpace(v) != "" { items = append(items, strings.TrimSpace(v)) } } return items } c.TrustedProxyCIDRs = split("ONBOARDING_TRUSTED_PROXY_CIDRS") c.SelfSignupTenantIDs = split("ONBOARDING_SELF_SIGNUP_TENANT_IDS") return c, nil } func (c OnboardingConfig) validate(environment string) error { if c.AllowLocalHTTP && environment != EnvironmentLocal && environment != EnvironmentTest { return errors.New("ONBOARDING_ALLOW_LOCAL_HTTP is limited to local/test") } if c.MasterKey == "" { if c.Enabled || c.ActivationEnabled || c.SMSLoginEnabled { return errors.New("onboarding requires ONBOARDING_MASTER_KEY") } return nil } key, err := hex.DecodeString(c.MasterKey) if err != nil || len(key) != 32 { return errors.New("ONBOARDING_MASTER_KEY must encode 32 bytes as hex") } u, err := url.Parse(c.EntryURL) if err != nil || u.Host == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || (u.Scheme != "https" && !(c.AllowLocalHTTP && u.Scheme == "http" && (u.Hostname() == "localhost" || u.Hostname() == "127.0.0.1"))) { return errors.New("ONBOARDING_ENTRY_URL must be a fixed HTTPS URL") } for _, cidr := range c.TrustedProxyCIDRs { p, err := netip.ParsePrefix(cidr) if err != nil || p.Bits() == 0 { return errors.New("onboarding trusted proxies must be explicit non-global CIDRs") } } for _, id := range c.SelfSignupTenantIDs { if _, err := uuid.Parse(id); err != nil { return errors.New("onboarding signup tenants must be UUIDs") } } if c.SMSLoginEnabled { u, err := url.Parse(c.SMSGatewayURL) if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.RawQuery != "" || u.Fragment != "" || c.SMSGatewayToken == "" { return errors.New("onboarding SMS requires HTTPS gateway and token") } } return nil }