feat(auth): integrate authentication with installation lifecycle

This commit is contained in:
2026-08-17 20:21:42 +02:00
parent 0a2c667231
commit 9558eaa508
30 changed files with 756 additions and 120 deletions
+74
View File
@@ -11,6 +11,7 @@ import (
"strconv"
"strings"
"github.com/aritmolab/thothii/tools/tht/internal/authconfig"
"github.com/aritmolab/thothii/tools/tht/internal/compose"
"github.com/aritmolab/thothii/tools/tht/internal/config"
"github.com/aritmolab/thothii/tools/tht/internal/doctor"
@@ -53,6 +54,9 @@ func Run(ctx context.Context, runner compose.Runner, request Request, input io.R
return Result{}, fmt.Errorf("setup generated configuration is invalid: %w", err)
}
result := Result{DescriptorPath: files.DescriptorPath, ProjectName: installation.ProjectName(), Configured: true}
if err := configureAuthentication(ctx, installation, request, input, output); err != nil {
return Result{}, err
}
if err := runCompose(ctx, runner, installation, "config", "--quiet"); err != nil {
return Result{}, fmt.Errorf("setup Compose configuration: %w", err)
}
@@ -78,6 +82,76 @@ func Run(ctx context.Context, runner compose.Runner, request Request, input io.R
return result, nil
}
func configureAuthentication(ctx context.Context, installation config.Installation, request Request, input io.Reader, output io.Writer) error {
directory := installation.AuthenticationDirectory()
if _, _, err := authconfig.Load(directory); err == nil {
return nil
}
if _, err := os.Lstat(filepath.Join(directory, "auth.yaml")); !errors.Is(err, os.ErrNotExist) {
return errors.New("setup authentication configuration is invalid")
}
args, err := authenticationConfigureArgs(request)
if err != nil {
return err
}
if exitCode := authconfig.Run(ctx, installation, args, input, io.Discard, output); exitCode != 0 {
return errors.New("setup authentication configuration failed")
}
if _, _, err := authconfig.Load(directory); err != nil {
return errors.New("setup authentication configuration is invalid")
}
return nil
}
func authenticationConfigureArgs(request Request) ([]string, error) {
answers := request.Answers
mode := answers.AuthMode
if mode == "" && !request.NonInteractive {
mode = "local"
}
if mode != "local" && mode != "oidc" {
return nil, errors.New("setup requires --auth-mode local or oidc")
}
if mode == "local" && request.NonInteractive && (answers.AuthAdminUser == "" || answers.AuthAdminDisplayName == "" || answers.AuthPasswordFile == "") {
return nil, errors.New("non-interactive local authentication requires --auth-admin-user, --auth-admin-display-name, and --auth-password-file")
}
publicURL := answers.AuthPublicURL
if publicURL == "" && !request.NonInteractive && mode == "local" {
publicURL = "http://127.0.0.1:8080"
}
if publicURL == "" {
return nil, errors.New("setup requires --auth-public-url")
}
args := []string{"configure", "--mode", mode, "--public-url", publicURL}
if mode == "local" {
if answers.AuthAdminUser != "" {
args = append(args, "--admin-user", answers.AuthAdminUser)
}
if answers.AuthAdminDisplayName != "" {
args = append(args, "--admin-display-name", answers.AuthAdminDisplayName)
}
if answers.AuthPasswordFile != "" {
args = append(args, "--password-file", answers.AuthPasswordFile)
}
return args, nil
}
for _, option := range []struct {
name, value string
}{
{"--issuer", answers.AuthIssuer},
{"--client-id", answers.AuthClientID},
{"--authentik-base-url", answers.AuthAuthentikBaseURL},
{"--user-group", answers.AuthUserGroup},
{"--admin-group", answers.AuthAdminGroup},
} {
if option.value == "" {
return nil, errors.New("OIDC authentication requires complete provider and group options")
}
args = append(args, option.name, option.value)
}
return args, nil
}
func checkHost(ctx context.Context, runner compose.Runner, root string) error {
checks := []struct {
name string