feat(auth): add local and OIDC management to tht

This commit is contained in:
2026-08-16 19:23:30 +02:00
parent d17ad0e95b
commit 9646ae09a0
15 changed files with 992 additions and 13 deletions
+44 -2
View File
@@ -3,6 +3,7 @@ package config
import (
"os"
"path/filepath"
"strconv"
"strings"
"testing"
)
@@ -67,6 +68,7 @@ func TestLoadRequiresAndReturnsTypedWorkspaceRepositoryForGitInstallations(t *te
}
remote := "git@gitea.example.org:clinical/workspaces.git"
environment := strings.Join([]string{
"THT_AUTH_CONFIG_ROOT=" + strconv.Quote(filepath.Join(filepath.Dir(envFile), "auth")),
"THT_WORKSPACE_GIT_REMOTE=" + remote,
"THT_WORKSPACE_GIT_BRANCH=main",
"THT_WORKSPACE_GIT_SSH_KEY_FILE=" + privateKey,
@@ -77,6 +79,7 @@ func TestLoadRequiresAndReturnsTypedWorkspaceRepositoryForGitInstallations(t *te
}
contents := "profile: local\nprojectDirectory: " + projectDirectory +
"\nenvFile: " + envFile +
"\nauthentication:\n configDirectory: " + filepath.Join(filepath.Dir(envFile), "auth") +
"\nworkspaceRepository:\n remote: " + remote +
"\n branch: main\n access: ssh\noverrides:\n - " + gitOverride + "\n"
if err := os.WriteFile(installationPath, []byte(contents), 0o600); err != nil {
@@ -102,6 +105,7 @@ func TestLoadRejectsGitOverrideWithoutTypedWorkspaceRepository(t *testing.T) {
}
contents := "profile: local\nprojectDirectory: " + projectDirectory +
"\nenvFile: " + envFile + "\noverrides:\n - " + gitOverride + "\n"
contents = strings.Replace(contents, "\noverrides:", "\nauthentication:\n configDirectory: "+filepath.Join(filepath.Dir(envFile), "auth")+"\noverrides:", 1)
if err := os.WriteFile(installationPath, []byte(contents), 0o600); err != nil {
t.Fatal(err)
}
@@ -194,6 +198,7 @@ func TestPreservationPathsReturnsCanonicalBindRootsBackupsAndSecretFiles(t *test
}
wanted = append(wanted, secret)
lines = append(lines, "APP_TOKEN_FILE="+secret)
lines = append(lines, "THT_AUTH_CONFIG_ROOT="+strconv.Quote(filepath.Join(root, "auth")))
if err := os.WriteFile(envFile, []byte(strings.Join(lines, "\n")+"\n"), 0o600); err != nil {
t.Fatal(err)
}
@@ -239,6 +244,42 @@ func TestLoadRejectsRelativeInstallationPaths(t *testing.T) {
}
}
func TestLoadRequiresCanonicalAuthenticationDirectoryMatchingEnvironment(t *testing.T) {
installationPath, _, envFile, _ := writeInstallation(t, "local")
authDirectory := filepath.Join(filepath.Dir(installationPath), "auth")
contents, err := os.ReadFile(installationPath)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(envFile, []byte("THT_AUTH_CONFIG_ROOT="+strconv.Quote(authDirectory)+"\n"), 0o600); err != nil {
t.Fatal(err)
}
installation, err := Load(installationPath)
if err != nil {
t.Fatal(err)
}
if got := installation.AuthenticationDirectory(); got != authDirectory {
t.Fatalf("AuthenticationDirectory() = %q, want %q", got, authDirectory)
}
for _, invalid := range []string{"relative/auth", authDirectory + "/../auth"} {
bad := strings.Replace(string(contents), authDirectory, invalid, 1)
if err := os.WriteFile(installationPath, []byte(bad), 0o600); err != nil {
t.Fatal(err)
}
if _, err := Load(installationPath); err == nil {
t.Fatalf("Load accepted unsafe auth directory %q", invalid)
}
}
}
func TestParseAuthenticationDirectoryEnvironment(t *testing.T) {
values, err := parseComposeDotenv([]byte("THT_AUTH_CONFIG_ROOT=\"/tmp/auth\"\n"))
if err != nil || values["THT_AUTH_CONFIG_ROOT"] != "/tmp/auth" {
t.Fatalf("values=%#v err=%v", values, err)
}
}
func writeInstallation(t *testing.T, profile string) (string, string, string, string) {
t.Helper()
@@ -262,7 +303,8 @@ func writeInstallation(t *testing.T, profile string) (string, string, string, st
}
}
envFile := filepath.Join(root, "environment file.env")
if err := os.WriteFile(envFile, []byte("SAFE_VALUE=1\n"), 0o600); err != nil {
authDirectory := filepath.Join(root, "auth")
if err := os.WriteFile(envFile, []byte("SAFE_VALUE=1\nTHT_AUTH_CONFIG_ROOT="+strconv.Quote(authDirectory)+"\n"), 0o600); err != nil {
t.Fatal(err)
}
override := filepath.Join(root, "extra override.yaml")
@@ -270,7 +312,7 @@ func writeInstallation(t *testing.T, profile string) (string, string, string, st
t.Fatal(err)
}
installationPath := filepath.Join(root, "thothii-installation.yaml")
contents := "profile: " + profile + "\nprojectDirectory: " + projectDirectory + "\nenvFile: " + envFile + "\noverrides:\n - " + override + "\n"
contents := "profile: " + profile + "\nprojectDirectory: " + projectDirectory + "\nenvFile: " + envFile + "\nauthentication:\n configDirectory: " + authDirectory + "\noverrides:\n - " + override + "\n"
if err := os.WriteFile(installationPath, []byte(contents), 0o600); err != nil {
t.Fatal(err)
}