Load the super-admin config in the product composition root #218

Merged
weichen merged 1 commits from fix/link-super-admin-config into main 2026-10-19 12:46:13 +00:00
2 changed files with 94 additions and 1 deletions
+19 -1
View File
@@ -6,15 +6,33 @@ import (
"os" "os"
"agent-system-backend/internal/app" "agent-system-backend/internal/app"
"agent-system-backend/internal/config"
"agent-system-backend/pkg/extension" "agent-system-backend/pkg/extension"
) )
func init() { func init() {
extension.SetRunner(func(opt extension.Options) error { extension.SetRunner(func(opt extension.Options) error {
return app.Run(appOptions(opt)) appOpt, err := runOptions(opt)
if err != nil {
return err
}
return app.Run(appOpt)
}) })
} }
// runOptions 在 appOptions 基础上补齐超级管理员凭据,与 cmd/agent-system 同源:
// 进程环境优先,未设置的键从 AGENT_SYSTEM_ENV_FILE 指向的受保护 dotenv 补齐。
func runOptions(opt extension.Options) (app.Options, error) {
appOpt := appOptions(opt)
authEnabled := config.Resolved{Config: config.Config{AuthMode: opt.AuthMode, StorageDSN: opt.StorageDSN}}.AuthEnabled()
superAdmin, err := config.LoadSuperAdmin(os.LookupEnv, "", authEnabled)
if err != nil {
return app.Options{}, err
}
appOpt.SuperAdmin = superAdmin
return appOpt, nil
}
// appOptions 把产品启动参数映射到基座。执行环境三项产品旗标留空时读环境变量, // appOptions 把产品启动参数映射到基座。执行环境三项产品旗标留空时读环境变量,
// 与 cmd/agent-system 的 config 键一致,这样 svc 注入环境即可,不必改产品 main。 // 与 cmd/agent-system 的 config 键一致,这样 svc 注入环境即可,不必改产品 main。
func appOptions(opt extension.Options) app.Options { func appOptions(opt extension.Options) app.Options {
+75
View File
@@ -1,11 +1,86 @@
package link package link
import ( import (
"errors"
"os"
"path/filepath"
"testing" "testing"
"agent-system-backend/internal/config"
"agent-system-backend/pkg/extension" "agent-system-backend/pkg/extension"
) )
const fakeSuperAdminPassword = "Fake-Test-Passw0rd-Only"
func clearSuperAdminEnv(t *testing.T) {
t.Helper()
for _, key := range []string{config.EnvSuperAdminOwner, config.EnvSuperAdminPassword, config.EnvFileKey} {
t.Setenv(key, "")
os.Unsetenv(key)
}
}
func writeEnvFile(t *testing.T, mode os.FileMode) string {
t.Helper()
path := filepath.Join(t.TempDir(), "super-admin.env")
content := "SUPER_ADMIN_OWNER=fake-file-owner\nSUPER_ADMIN_PASSWORD=" + fakeSuperAdminPassword + "\n"
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chmod(path, mode); err != nil {
t.Fatal(err)
}
return path
}
func TestRunOptionsLoadsSuperAdminFromEnv(t *testing.T) {
clearSuperAdminEnv(t)
t.Setenv(config.EnvSuperAdminOwner, "fake-env-owner")
t.Setenv(config.EnvSuperAdminPassword, fakeSuperAdminPassword)
got, err := runOptions(extension.Options{AuthMode: "on", RequireProjectContext: true})
if err != nil {
t.Fatalf("runOptions: %v", err)
}
if got.SuperAdmin.Owner != "fake-env-owner" || got.SuperAdmin.Password() == "" {
t.Fatalf("super admin not loaded from env: %v", got.SuperAdmin)
}
if !got.RequireProjectContext || got.AuthMode != "on" {
t.Fatalf("appOptions mapping lost: auth=%q requireProject=%t", got.AuthMode, got.RequireProjectContext)
}
}
func TestRunOptionsLoadsSuperAdminFromEnvFile(t *testing.T) {
clearSuperAdminEnv(t)
t.Setenv(config.EnvFileKey, writeEnvFile(t, 0o600))
got, err := runOptions(extension.Options{AuthMode: "on"})
if err != nil {
t.Fatalf("runOptions: %v", err)
}
if got.SuperAdmin.Owner != "fake-file-owner" || got.SuperAdmin.Password() == "" {
t.Fatalf("super admin not loaded from env file: %v", got.SuperAdmin)
}
}
func TestRunOptionsSkipsSuperAdminWithoutAuthentication(t *testing.T) {
clearSuperAdminEnv(t)
got, err := runOptions(extension.Options{AuthMode: "off"})
if err != nil {
t.Fatalf("runOptions without auth: %v", err)
}
if got.SuperAdmin.Owner != "" || got.SuperAdmin.Password() != "" {
t.Fatalf("super admin should be empty without auth: %v", got.SuperAdmin)
}
}
func TestRunOptionsRejectsExposedEnvFile(t *testing.T) {
clearSuperAdminEnv(t)
t.Setenv(config.EnvFileKey, writeEnvFile(t, 0o640))
_, err := runOptions(extension.Options{AuthMode: "on"})
if !errors.Is(err, config.ErrSuperAdminConfig) {
t.Fatalf("exposed env file accepted: %v", err)
}
}
func TestAppOptionsPrefersExplicitExecPaths(t *testing.T) { func TestAppOptionsPrefersExplicitExecPaths(t *testing.T) {
t.Setenv("AGENT_SYSTEM_EXEC_DEPS_DIR", "/from/env/deps") t.Setenv("AGENT_SYSTEM_EXEC_DEPS_DIR", "/from/env/deps")
t.Setenv("AGENT_SYSTEM_EXEC_WORKSPACE", "overlay") t.Setenv("AGENT_SYSTEM_EXEC_WORKSPACE", "overlay")