feat: complete device onboarding and mobile binding workflows
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-12 08:57:29 +08:00
parent 7846f30d0b
commit 98fb2965d0
116 changed files with 15669 additions and 64 deletions
+77
View File
@@ -0,0 +1,77 @@
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
}