updates on the ai and agent and all thse things awith onboarding

This commit is contained in:
2026-09-30 14:47:58 +05:30
parent 44ba33eda2
commit 0ac5d3d54f
37 changed files with 7755 additions and 0 deletions

View File

@@ -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
}

View File

@@ -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) {