package management import ( "encoding/json" "os" "strings" "github.com/spf13/viper" ) type TokenSpec struct { Principal string `json:"principal"` Realm string `json:"realm"` Scopes []string `json:"scopes"` Resources map[string][]string `json:"resources,omitempty"` } type Config struct { Addr string Mode string DBPath string MockCellApply bool MockCellFailures map[string]string SeedDemo bool AllowPrivateAgent bool AgentAllowlist map[string]bool AllowedOrigins map[string]bool Tokens map[string]TokenSpec } func DefaultConfig() Config { return Config{ Addr: "127.0.0.1:8080", Mode: ModeMock, DBPath: "./data/management.db", MockCellApply: false, MockCellFailures: map[string]string{}, SeedDemo: false, AgentAllowlist: map[string]bool{}, AllowedOrigins: map[string]bool{"http://localhost:3000": true, "http://127.0.0.1:3000": true}, Tokens: defaultTokens(), } } func defaultTokens() map[string]TokenSpec { all := []string{"sip.provider.read", "sip.provider.write", "sip.trunk.read", "sip.trunk.write", "sip.trunk.publish", "sip.trunk.disable", "sip.trunk.rollback", "sip.cell.read", "sip.cell.write", "sip.status.read", "sip.statistics.read", "sip.calls.read", "sip.audit.read", "sip.operation.read"} return map[string]TokenSpec{ "dev-admin-token": {Principal: "operator:local-admin", Realm: "admin", Scopes: all}, "dev-config-token": {Principal: "operator:config", Realm: "admin", Scopes: []string{"sip.provider.read", "sip.provider.write", "sip.trunk.read", "sip.trunk.write", "sip.cell.read", "sip.status.read", "sip.statistics.read", "sip.calls.read"}}, "dev-publisher-token": {Principal: "operator:publisher", Realm: "admin", Scopes: []string{"sip.provider.read", "sip.trunk.read", "sip.trunk.publish", "sip.trunk.disable", "sip.trunk.rollback", "sip.cell.read", "sip.status.read", "sip.operation.read"}}, "dev-observer-token": {Principal: "operator:observer", Realm: "admin", Scopes: []string{"sip.provider.read", "sip.trunk.read", "sip.cell.read", "sip.status.read", "sip.statistics.read", "sip.calls.read"}}, "dev-audit-token": {Principal: "operator:auditor", Realm: "admin", Scopes: []string{"sip.provider.read", "sip.trunk.read", "sip.audit.read", "sip.operation.read"}}, "dev-saas-token": {Principal: "saas:readonly", Realm: "saas", Scopes: []string{"sip.trunk.read"}}, "dev-scheduler-token": {Principal: "scheduler:ordinary", Realm: "scheduler", Scopes: []string{"sip.trunk.read"}}, } } func LoadConfig(v *viper.Viper) Config { cfg := DefaultConfig() v.SetDefault("addr", cfg.Addr) v.SetDefault("mode", cfg.Mode) v.SetDefault("db_path", cfg.DBPath) v.SetDefault("mock_cell_apply", cfg.MockCellApply) v.SetDefault("seed_demo", cfg.SeedDemo) v.SetDefault("allow_private_agent", cfg.AllowPrivateAgent) v.SetDefault("mock_cell_failures", "") v.SetDefault("agent_allowlist", "") v.SetDefault("allowed_origins", "") v.SetDefault("tokens_json", "") cfg.Addr = v.GetString("addr") cfg.Mode = strings.ToLower(v.GetString("mode")) if cfg.Mode != ModeReal { cfg.Mode = ModeMock } cfg.DBPath = v.GetString("db_path") cfg.MockCellApply = v.GetBool("mock_cell_apply") cfg.MockCellFailures = map[string]string{} for _, item := range strings.Split(v.GetString("mock_cell_failures"), ",") { parts := strings.SplitN(strings.TrimSpace(item), "=", 2) if len(parts) == 2 && validID(parts[0]) && parts[1] != "" { cfg.MockCellFailures[parts[0]] = parts[1] } } cfg.SeedDemo = v.GetBool("seed_demo") cfg.AllowPrivateAgent = v.GetBool("allow_private_agent") cfg.AgentAllowlist = map[string]bool{} for _, host := range strings.Split(v.GetString("agent_allowlist"), ",") { if host = strings.TrimSpace(strings.ToLower(host)); host != "" { cfg.AgentAllowlist[host] = true } } cfg.AllowedOrigins = map[string]bool{} for _, origin := range strings.Split(v.GetString("allowed_origins"), ",") { if origin = strings.TrimSpace(origin); origin != "" { cfg.AllowedOrigins[origin] = true } } if len(cfg.AllowedOrigins) == 0 { cfg.AllowedOrigins = DefaultConfig().AllowedOrigins } if raw := v.GetString("tokens_json"); raw != "" { var tokens map[string]TokenSpec if json.Unmarshal([]byte(raw), &tokens) == nil && len(tokens) > 0 { cfg.Tokens = tokens } } return cfg } func ConfigFromEnv() Config { v := viper.New() v.SetEnvPrefix("MANAGEMENT") v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) v.AutomaticEnv() return LoadConfig(v) } func (c Config) PublicMode() string { if c.Mode == ModeReal { return ModeReal } return ModeMock } func EnsureDevTokenWarning(c Config) bool { return c.Mode == ModeMock && os.Getenv("MANAGEMENT_SUPPRESS_DEV_TOKEN_WARNING") == "" }