updates on the ai and agent and all thse things awith onboarding
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -49,6 +50,20 @@ type Config struct {
|
||||
SMTPUser string
|
||||
SMTPPassword string
|
||||
SMTPFrom string
|
||||
|
||||
// ClientOnboardingOwners are the console logins allowed to onboard a new
|
||||
// client (tenant + its console login). Comma-separated emails, compared
|
||||
// case-insensitively. Deliberately a short allow-list rather than a role:
|
||||
// every Doormile admin has roleid 1, and onboarding mints credentials.
|
||||
ClientOnboardingOwners []string
|
||||
|
||||
// The Agent Studio Test playground's model: any OpenAI-compatible chat
|
||||
// completions API (Groq by default; xAI works too). Empty API key leaves
|
||||
// the playground off — the endpoint answers 503 PLAYGROUND_NOT_CONFIGURED.
|
||||
// Set the key as a secret in the deployment, never in a tracked file.
|
||||
PlaygroundLLMBaseURL string
|
||||
PlaygroundLLMAPIKey string
|
||||
PlaygroundLLMModel string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
@@ -79,12 +94,52 @@ func Load() *Config {
|
||||
SMTPUser: getEnv("SMTP_USER", ""),
|
||||
SMTPPassword: getEnv("SMTP_PASSWORD", ""),
|
||||
SMTPFrom: getEnv("SMTP_FROM", ""),
|
||||
|
||||
ClientOnboardingOwners: splitEmails(getEnv("CLIENT_ONBOARDING_OWNERS", "admin@doormile.com")),
|
||||
|
||||
PlaygroundLLMBaseURL: getEnv("PLAYGROUND_LLM_BASE_URL", "https://api.groq.com/openai/v1"),
|
||||
PlaygroundLLMAPIKey: getEnv("PLAYGROUND_LLM_API_KEY", ""),
|
||||
PlaygroundLLMModel: getEnv("PLAYGROUND_LLM_MODEL", "openai/gpt-oss-120b"),
|
||||
}
|
||||
}
|
||||
|
||||
// requiredInProduction are the secrets whose development fallback above is a
|
||||
// literal committed to this repository. In production a missing one must stop
|
||||
// the boot: falling back would sign every token with a JWT secret anyone with
|
||||
// the source can read, and connect with a published password.
|
||||
var requiredInProduction = []string{"JWT_SECRET_KEY", "DB_PASSWORD", "NATS_PASSWORD"}
|
||||
|
||||
// MissingProductionSecrets names each required secret that is unset when
|
||||
// ENV=production. Always empty in any other environment, so local development
|
||||
// keeps running on the fallbacks.
|
||||
func (c *Config) MissingProductionSecrets() []string {
|
||||
if !strings.EqualFold(c.Env, "production") {
|
||||
return nil
|
||||
}
|
||||
var missing []string
|
||||
for _, key := range requiredInProduction {
|
||||
if os.Getenv(key) == "" {
|
||||
missing = append(missing, key)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// splitEmails parses a comma-separated email list: trimmed, lower-cased,
|
||||
// blanks dropped.
|
||||
func splitEmails(v string) []string {
|
||||
var out []string
|
||||
for _, e := range strings.Split(v, ",") {
|
||||
if e = strings.ToLower(strings.TrimSpace(e)); e != "" {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -82,6 +82,39 @@ func TestEnvironmentOverridesAreRead(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Production must not boot on a secret whose fallback is committed to the repo.
|
||||
func TestMissingProductionSecrets(t *testing.T) {
|
||||
setEnv(t, "ENV", "production")
|
||||
setEnv(t, "JWT_SECRET_KEY", "")
|
||||
setEnv(t, "DB_PASSWORD", "set")
|
||||
setEnv(t, "NATS_PASSWORD", "")
|
||||
|
||||
got := Load().MissingProductionSecrets()
|
||||
if len(got) != 2 || got[0] != "JWT_SECRET_KEY" || got[1] != "NATS_PASSWORD" {
|
||||
t.Fatalf("missing = %v, want [JWT_SECRET_KEY NATS_PASSWORD]", got)
|
||||
}
|
||||
|
||||
setEnv(t, "JWT_SECRET_KEY", "set")
|
||||
setEnv(t, "NATS_PASSWORD", "set")
|
||||
if got := Load().MissingProductionSecrets(); len(got) != 0 {
|
||||
t.Errorf("all secrets set, still reported missing: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Outside production the fallbacks are allowed, so local development runs
|
||||
// with no .env at all.
|
||||
func TestMissingProductionSecretsIgnoredOutsideProduction(t *testing.T) {
|
||||
setEnv(t, "JWT_SECRET_KEY", "")
|
||||
setEnv(t, "DB_PASSWORD", "")
|
||||
setEnv(t, "NATS_PASSWORD", "")
|
||||
for _, env := range []string{"", "development", "staging"} {
|
||||
setEnv(t, "ENV", env)
|
||||
if got := Load().MissingProductionSecrets(); len(got) != 0 {
|
||||
t.Errorf("ENV=%q reported missing secrets %v; only production should", env, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An empty env var must fall through to the default rather than blanking the
|
||||
// setting — an empty DB host is a service that cannot start with no clue why.
|
||||
func TestEmptyEnvFallsBackToTheDefault(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user