From 2a66a115b51b839205cf4b67631f96a2df98cb67 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 10:36:21 -0400 Subject: [PATCH 01/34] docs: document install/service/CI flow and add UI screenshots Add Installation (MSI/binary/Docker), Running & Service Management (the CLI commands incl. idempotent initialize/remove), and CI/CD (two-job release + required REGISTRY_PASSWORD secret) sections; note the pure-Go build. Add dashboard/rules/credentials/login/schedules screenshots and a Screenshots section. Co-Authored-By: Claude Opus 4.8 --- README.md | 127 ++++++++++++++++++++++++++----- docs/screenshots/credentials.png | Bin 0 -> 113075 bytes docs/screenshots/dashboard.png | Bin 0 -> 170321 bytes docs/screenshots/login.png | Bin 0 -> 260317 bytes docs/screenshots/rules.png | Bin 0 -> 112072 bytes docs/screenshots/schedules.png | Bin 0 -> 115267 bytes 6 files changed, 107 insertions(+), 20 deletions(-) create mode 100644 docs/screenshots/credentials.png create mode 100644 docs/screenshots/dashboard.png create mode 100644 docs/screenshots/login.png create mode 100644 docs/screenshots/rules.png create mode 100644 docs/screenshots/schedules.png diff --git a/README.md b/README.md index 587d8ba..e37bdf7 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,20 @@ All configuration is managed through the UI or API β€” **no manual file editing --- +## πŸ“Έ Screenshots + +| Dashboard | Rules | +| --- | --- | +| [![Dashboard](docs/screenshots/dashboard.png)](docs/screenshots/dashboard.png) | [![Rules](docs/screenshots/rules.png)](docs/screenshots/rules.png) | + +| Credentials | Sign in | +| --- | --- | +| [![Credentials](docs/screenshots/credentials.png)](docs/screenshots/credentials.png) | [![Sign in](docs/screenshots/login.png)](docs/screenshots/login.png) | + +The entire UI is served by the single backend binary (embedded Next.js static export) β€” no separate web server to run. + +--- + ## ✨ Key Features ### πŸ” Rule-Based Automation @@ -151,39 +165,112 @@ Supports: --- +## πŸ“₯ Installation + +### Windows (MSI) + +Download `OrchestrAD--x64.msi` from the [latest release](https://prod.git.gracesolution.info/gsadmin/OrchestrAD/releases) and run it. The installer: + +* Installs to `C:\Program Files\OrchestrAD` +* Registers and starts the **OrchestrAD** Windows service (auto-start) +* Records the install directory and version under `HKLM\Software\Grace Solutions\OrchestrAD` +* On upgrade, updates only the binaries β€” the database (`…\OrchestrAD\data`) is preserved +* On uninstall, stops and removes the service and deletes the program files (the data directory is left in place) + +### Standalone binary (Windows / macOS / Linux) + +Download the archive for your platform from the release (`…--.tar.gz` / `.zip`), extract the single `orchestrad` binary, and run it. See **Running & Service Management** below. + +### Docker + +```bash +docker pull prod.git.gracesolution.info/gsadmin/orchestrad:latest +docker run -d --name orchestrad -p 8080:8080 \ + -e ORCHESTRAD_SECRET_KEY=<32+ byte secret> \ + -v orchestrad-data:/data \ + prod.git.gracesolution.info/gsadmin/orchestrad:latest +``` + +Or use the bundled [`docker-compose.yml`](docker-compose.yml). Images are tagged `latest` and the unified commit date `yyyy.MM.dd.HHmm`. + +--- + +## ▢️ Running & Service Management + +The single binary is both the app and its own service manager (Windows service, systemd/upstart/sysv, or launchd). + +``` +orchestrad + + run Run in the foreground (also the service entry point; used by Docker) + initialize Install and start the service (idempotent) + remove Stop and remove the service (idempotent) + install Alias for initialize + uninstall Alias for remove + start Start the installed service + stop Stop the installed service + init Initialize the database and apply migrations + migrate Apply database migrations + backup Create a manual backup + restore --file Restore the database from a backup + doctor Validate configuration and system health + version Show version information +``` + +`initialize` and `remove` are idempotent, so they are safe to re-run (and are what the MSI uses under the hood). On first start the default admin account is `admin` / `admin`, and a password change is forced at first login. + +Key environment variables (see the CLI help for the full list): + +| Variable | Default | Purpose | +| --- | --- | --- | +| `ORCHESTRAD_DATA_PATH` | `./data` (next to the binary for the service) | Database, logs, backups | +| `ORCHESTRAD_SECRET_KEY` | insecure dev key | AEAD key for credential encryption (**set in production**) | +| `ORCHESTRAD_HOST` / `ORCHESTRAD_PORT` | `0.0.0.0` / `8080` | HTTP bind address | +| `ORCHESTRAD_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | + +--- + ## πŸ“¦ Build & Versioning -* Version format: `yyyy.MM.dd.HHmm` -* Multi-platform builds: +* Version format: `yyyy.MM.dd.HHmm` (derived from the HEAD commit date in CI) +* **Pure Go** β€” SQLite via `modernc.org/sqlite`, so `CGO_ENABLED=0` and every target cross-compiles from one machine with no C toolchain +* Multi-platform: Windows, macOS, Linux (amd64 + arm64) +* Windows binaries embed the application icon and version metadata from `/resources/icons` (via `goversioninfo`) - * Windows (amd64, arm64) - * macOS (amd64, arm64) - * Linux (amd64, arm64) +Local build (Windows host, builds the frontend and stages it into the binary): -Output structure: - -``` -/binaries - /windows - /macos - /linux +```powershell +./scripts/build.ps1 -All # or -Windows / -MacOS / -Linux ``` -Windows builds embed application icon from `/resources/icons`. +Output lands under `/binaries///`. --- ## 🐳 Docker -OrchestrAD supports containerized deployment: +OrchestrAD ships as a single small (~60 MB) image built by a multi-stage `Dockerfile` that compiles the Next.js UI, embeds it, and builds the static Go binary: -* Runs in foreground mode by default -* Supports bind mounts for: +* Runs in foreground mode by default; graceful shutdown on `SIGTERM` +* `/data` holds the database, logs, and backups (bind-mount or named volume) +* `HEALTHCHECK` probes `/health` (answers both GET and HEAD) - * database - * logs - * backups -* Health endpoints available for orchestration +--- + +## πŸš€ CI/CD + +A single Gitea Actions workflow ([`.gitea/workflows/release.yml`](.gitea/workflows/release.yml)) runs **only on merge to `main`** (docs-only merges are skipped β€” no per-commit or per-PR CI): + +1. **`release`** job (`ubuntu-host`): runs the Go test suite as a gate, builds the frontend, cross-compiles all six binaries, builds and pushes the container image (`latest` + version), and creates the Gitea release with the binaries + checksums attached. +2. **`msi`** job (`windows-host`): wraps the Windows binary into the MSI with WiX and attaches it to the release. + +### Required repository secret + +| Secret | Purpose | +| --- | --- | +| `REGISTRY_PASSWORD` | A Gitea personal access token with `write:package` scope, used to push images to the container registry (with `REGISTRY_HOST` + `REGISTRY_USERNAME`). | + +`REGISTRY_HOST` / `REGISTRY_USERNAME` point at the container registry (the Gitea instance itself by default). The release itself is created with the auto-injected Actions token, so no other secrets are needed. --- diff --git a/docs/screenshots/credentials.png b/docs/screenshots/credentials.png new file mode 100644 index 0000000000000000000000000000000000000000..e99169b82db18a084aeee6f7ec5461eaf57cf333 GIT binary patch literal 113075 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≧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 bFNu%0{zlfbIoRPhB>cI8x_rq~i#PuRCS&7e literal 0 HcmV?d00001 diff --git a/docs/screenshots/login.png b/docs/screenshots/login.png new file mode 100644 index 0000000000000000000000000000000000000000..b57d04ddf3b95b96d7df62f69664ac7630cb4dff GIT binary patch literal 260317 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

    T?B8b6Y>ygbD&ocHFRk&sHOS@Ac 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�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

    A%B4$} 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 V^^xc)6X?H1P>=yhqa=-k{y*FO2*CgV literal 0 HcmV?d00001 diff --git a/docs/screenshots/schedules.png b/docs/screenshots/schedules.png new file mode 100644 index 0000000000000000000000000000000000000000..41195c9fdf57108d8e71f22167710f7ed0d3a4b5 GIT binary patch literal 115267 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�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�hJ`^)W_Yo}g_P2K=0tQ4)j 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 wY_0!1-T#~7tCKp^xjEU Date: Wed, 2 Sep 2026 10:56:05 -0400 Subject: [PATCH 02/34] feat(config): default listen address 0.0.0.0:18090 Change the default HTTP port from 8080 to 18090 and propagate it through the Dockerfile (EXPOSE + healthcheck), docker-compose, and the README. Host default stays 0.0.0.0. Co-Authored-By: Claude Opus 4.8 --- Dockerfile | 4 ++-- README.md | 4 ++-- backend/internal/config/config.go | 2 +- backend/internal/config/config_test.go | 4 ++-- docker-compose.yml | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index f8eabc6..e06bb75 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,11 +77,11 @@ ENV ORCHESTRAD_DATA_PATH=/data ENV ORCHESTRAD_LOG_LEVEL=info # Expose port -EXPOSE 8080 +EXPOSE 18090 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + CMD wget --no-verbose --tries=1 --spider http://localhost:18090/health || exit 1 # Switch to non-root user USER orchestrad diff --git a/README.md b/README.md index e37bdf7..3fdd06e 100644 --- a/README.md +++ b/README.md @@ -185,7 +185,7 @@ Download the archive for your platform from the release (`…--.tar.gz ```bash docker pull prod.git.gracesolution.info/gsadmin/orchestrad:latest -docker run -d --name orchestrad -p 8080:8080 \ +docker run -d --name orchestrad -p 18090:18090 \ -e ORCHESTRAD_SECRET_KEY=<32+ byte secret> \ -v orchestrad-data:/data \ prod.git.gracesolution.info/gsadmin/orchestrad:latest @@ -225,7 +225,7 @@ Key environment variables (see the CLI help for the full list): | --- | --- | --- | | `ORCHESTRAD_DATA_PATH` | `./data` (next to the binary for the service) | Database, logs, backups | | `ORCHESTRAD_SECRET_KEY` | insecure dev key | AEAD key for credential encryption (**set in production**) | -| `ORCHESTRAD_HOST` / `ORCHESTRAD_PORT` | `0.0.0.0` / `8080` | HTTP bind address | +| `ORCHESTRAD_HOST` / `ORCHESTRAD_PORT` | `0.0.0.0` / `18090` | HTTP bind address | | `ORCHESTRAD_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | --- diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 9681d6b..b2a9907 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -86,7 +86,7 @@ func Load() (*Config, error) { SecretKey: secretKey, Server: ServerConfig{ Host: getEnv("ORCHESTRAD_HOST", "0.0.0.0"), - Port: getEnvInt("ORCHESTRAD_PORT", 8080), + Port: getEnvInt("ORCHESTRAD_PORT", 18090), TrustedProxies: splitCSV(getEnv("ORCHESTRAD_TRUSTED_PROXIES", "")), }, Database: DatabaseConfig{ diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index b979dee..08c6421 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -20,8 +20,8 @@ func TestLoadDefaults(t *testing.T) { if cfg.Server.Host != "0.0.0.0" { t.Errorf("Host = %q, want 0.0.0.0", cfg.Server.Host) } - if cfg.Server.Port != 8080 { - t.Errorf("Port = %d, want 8080", cfg.Server.Port) + if cfg.Server.Port != 18090 { + t.Errorf("Port = %d, want 18090", cfg.Server.Port) } if cfg.Logging.Level != "info" { t.Errorf("Log level = %q, want info", cfg.Logging.Level) diff --git a/docker-compose.yml b/docker-compose.yml index 12c70c6..56ff490 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: container_name: orchestrad restart: unless-stopped ports: - - "8080:8080" + - "18090:18090" volumes: - orchestrad-data:/data environment: @@ -21,9 +21,9 @@ services: - ORCHESTRAD_LOG_LEVEL=info - ORCHESTRAD_SECRET_KEY=${ORCHESTRAD_SECRET_KEY:-} - ORCHESTRAD_HOST=0.0.0.0 - - ORCHESTRAD_PORT=8080 + - ORCHESTRAD_PORT=18090 healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:18090/health"] interval: 30s timeout: 10s retries: 3 -- 2.52.0 From 5eae8cc102b50c47ad8eea7e9a3349c75a85a4f8 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 10:56:06 -0400 Subject: [PATCH 03/34] feat(services): DB-over-env settings precedence resolver Add SettingsService.ResolveString/Bool/Int resolving runtime config as app_settings (UI) > environment variable > default. Env seeds bootstrap; any value set in the UI persists to app_settings and wins. Foundation for OIDC and TLS UI configuration. Unit-tested for the precedence order. Co-Authored-By: Claude Opus 4.8 --- .../internal/services/settings_resolver.go | 63 ++++++++++++++ .../services/settings_resolver_test.go | 82 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 backend/internal/services/settings_resolver.go create mode 100644 backend/internal/services/settings_resolver_test.go diff --git a/backend/internal/services/settings_resolver.go b/backend/internal/services/settings_resolver.go new file mode 100644 index 0000000..87c5b72 --- /dev/null +++ b/backend/internal/services/settings_resolver.go @@ -0,0 +1,63 @@ +// Package services - settings precedence resolution. +// +// Runtime configuration resolves with a fixed precedence: +// +// app_settings (set via the UI/API) > environment variable > built-in default +// +// This lets environment variables seed a working configuration for bootstrap +// (containers, first run, the MSI), while any value an administrator sets in the +// UI is persisted to app_settings and wins from then on. Bootstrap-only settings +// that must be known before the database opens (data path, secret key, listen +// address/port) are NOT resolved here β€” they stay in package config. +package services + +import ( + "os" + "strconv" + "strings" +) + +// ResolveString returns the effective string value for key: the app_settings +// value if one is set (non-empty), else the environment variable envVar if set, +// else def. A blank envVar skips the environment lookup. +func (s *SettingsService) ResolveString(key, envVar, def string) string { + if setting, err := s.Get(key); err == nil && setting != nil && setting.Value != "" { + return setting.Value + } + if envVar != "" { + if v := os.Getenv(envVar); v != "" { + return v + } + } + return def +} + +// ResolveBool resolves a boolean setting with the same precedence. Recognized +// true values (case-insensitive): 1, t, true, yes, on. +func (s *SettingsService) ResolveBool(key, envVar string, def bool) bool { + raw := s.ResolveString(key, envVar, "") + if raw == "" { + return def + } + switch strings.ToLower(strings.TrimSpace(raw)) { + case "1", "t", "true", "yes", "on": + return true + case "0", "f", "false", "no", "off": + return false + default: + return def + } +} + +// ResolveInt resolves an integer setting with the same precedence, falling back +// to def when unset or unparseable. +func (s *SettingsService) ResolveInt(key, envVar string, def int) int { + raw := s.ResolveString(key, envVar, "") + if raw == "" { + return def + } + if i, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil { + return i + } + return def +} diff --git a/backend/internal/services/settings_resolver_test.go b/backend/internal/services/settings_resolver_test.go new file mode 100644 index 0000000..013cf60 --- /dev/null +++ b/backend/internal/services/settings_resolver_test.go @@ -0,0 +1,82 @@ +package services + +import ( + "database/sql" + "testing" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + _ "modernc.org/sqlite" +) + +// newTestSettings spins up an in-memory database with just the app_settings +// table and returns a SettingsService bound to it. +func newTestSettings(t *testing.T) *SettingsService { + t.Helper() + db, err := sql.Open("sqlite", "file:resolver_test?mode=memory&cache=shared&_pragma=foreign_keys(on)") + if err != nil { + t.Fatalf("open db: %v", err) + } + t.Cleanup(func() { db.Close() }) + if _, err := db.Exec(` + CREATE TABLE app_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL, + value_type TEXT NOT NULL DEFAULT 'string', + description TEXT, + is_sensitive INTEGER NOT NULL DEFAULT 0, + updated_utc TEXT NOT NULL + )`); err != nil { + t.Fatalf("create table: %v", err) + } + return NewSettingsService(db, logging.Default()) +} + +func TestResolveStringPrecedence(t *testing.T) { + s := newTestSettings(t) + const key, env = "oidc.provider_url", "ORCHESTRAD_OIDC_PROVIDER_URL" + + // 1) nothing set -> default + if got := s.ResolveString(key, env, "def"); got != "def" { + t.Fatalf("no value: got %q, want def", got) + } + + // 2) env set -> env wins over default + t.Setenv(env, "from-env") + if got := s.ResolveString(key, env, "def"); got != "from-env" { + t.Fatalf("env value: got %q, want from-env", got) + } + + // 3) DB (UI) set -> DB wins over env + if err := s.Upsert(Setting{Key: key, Value: "from-ui", ValueType: "string", UpdatedUTC: time.Now().UTC()}); err != nil { + t.Fatalf("upsert: %v", err) + } + if got := s.ResolveString(key, env, "def"); got != "from-ui" { + t.Fatalf("db value: got %q, want from-ui (UI must win over env)", got) + } +} + +func TestResolveBoolAndInt(t *testing.T) { + s := newTestSettings(t) + + if got := s.ResolveBool("oidc.enabled", "ORCHESTRAD_OIDC_ENABLED", false); got != false { + t.Errorf("bool default: got %v, want false", got) + } + t.Setenv("ORCHESTRAD_OIDC_ENABLED", "true") + if got := s.ResolveBool("oidc.enabled", "ORCHESTRAD_OIDC_ENABLED", false); got != true { + t.Errorf("bool env: got %v, want true", got) + } + // UI override to off wins over env=true. + _ = s.Upsert(Setting{Key: "oidc.enabled", Value: "off", ValueType: "bool", UpdatedUTC: time.Now().UTC()}) + if got := s.ResolveBool("oidc.enabled", "ORCHESTRAD_OIDC_ENABLED", false); got != false { + t.Errorf("bool ui-override: got %v, want false", got) + } + + if got := s.ResolveInt("x.count", "ORCHESTRAD_X_COUNT", 42); got != 42 { + t.Errorf("int default: got %d, want 42", got) + } + _ = s.Upsert(Setting{Key: "x.count", Value: "7", ValueType: "int", UpdatedUTC: time.Now().UTC()}) + if got := s.ResolveInt("x.count", "ORCHESTRAD_X_COUNT", 42); got != 7 { + t.Errorf("int ui: got %d, want 7", got) + } +} -- 2.52.0 From f38702dae506b29a4d40d6050a9d8f65fcf389b1 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:06:42 -0400 Subject: [PATCH 04/34] feat(pki): self-managed CA (root+intermediate+leaf) with PEM/DER/PFX export Add internal/pki: generate a Root CA -> Intermediate CA -> server leaf, re-issue leaves under a persisted CA for renewal, and export the chain as PEM (cert/key/fullchain), DER (.cer), and PKCS#12 (.pfx). Unit-tested: the chain verifies, the PFX decodes with the CA chain, and a reloaded CA re-issues a valid leaf. Co-Authored-By: Claude Opus 4.8 --- backend/go.mod | 1 + backend/go.sum | 2 + backend/internal/pki/export.go | 131 ++++++++++++++++++++ backend/internal/pki/pki.go | 198 +++++++++++++++++++++++++++++++ backend/internal/pki/pki_test.go | 117 ++++++++++++++++++ 5 files changed, 449 insertions(+) create mode 100644 backend/internal/pki/export.go create mode 100644 backend/internal/pki/pki.go create mode 100644 backend/internal/pki/pki_test.go diff --git a/backend/go.mod b/backend/go.mod index e7ea6cd..01a2c9f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,6 +13,7 @@ require ( golang.org/x/crypto v0.48.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 modernc.org/sqlite v1.58.0 + software.sslmate.com/src/go-pkcs12 v0.7.3 ) require ( diff --git a/backend/go.sum b/backend/go.sum index 59b5cc5..eb9322b 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -106,3 +106,5 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +software.sslmate.com/src/go-pkcs12 v0.7.3 h1:JBQD3FDqYjTeyDAeZQklj2ar88ykBLtALloPJHyAauU= +software.sslmate.com/src/go-pkcs12 v0.7.3/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/backend/internal/pki/export.go b/backend/internal/pki/export.go new file mode 100644 index 0000000..11d59aa --- /dev/null +++ b/backend/internal/pki/export.go @@ -0,0 +1,131 @@ +package pki + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "path/filepath" + + pkcs12 "software.sslmate.com/src/go-pkcs12" +) + +// Export writes the full chain to dir in PEM, DER, and PKCS#12 form. Every call +// overwrites the files, so it is safe to call on each renewal to keep the +// exports current. Private-key files are written 0600. +// +// Files written: +// +// root.crt / root.key root CA (PEM) +// intermediate.crt / intermediate.key intermediate CA (PEM) +// server.crt / server.key leaf (PEM) +// fullchain.pem leaf + intermediate + root (PEM) +// *.cer DER encodings of each certificate +// server.pfx leaf key + leaf cert + CA chain (PKCS#12) +func (c *Chain) Export(dir, pfxPassword string) error { + if err := os.MkdirAll(dir, 0o700); err != nil { + return fmt.Errorf("creating tls dir: %w", err) + } + + writes := []struct { + name string + data []byte + mode os.FileMode + }{ + {"root.crt", certPEM(c.Root.DER), 0o644}, + {"root.key", keyPEM(c.Root.Key), 0o600}, + {"intermediate.crt", certPEM(c.Intermediate.DER), 0o644}, + {"intermediate.key", keyPEM(c.Intermediate.Key), 0o600}, + {"server.crt", certPEM(c.Leaf.DER), 0o644}, + {"server.key", keyPEM(c.Leaf.Key), 0o600}, + {"fullchain.pem", c.FullChainPEM(), 0o644}, + // DER encodings. + {"root.cer", c.Root.DER, 0o644}, + {"intermediate.cer", c.Intermediate.DER, 0o644}, + {"server.cer", c.Leaf.DER, 0o644}, + } + for _, w := range writes { + if err := os.WriteFile(filepath.Join(dir, w.name), w.data, w.mode); err != nil { + return fmt.Errorf("writing %s: %w", w.name, err) + } + } + + // PKCS#12 bundle: leaf key + leaf cert + CA chain (intermediate, root). + pfx, err := pkcs12.Modern.Encode( + c.Leaf.Key, + c.Leaf.Certificate, + []*x509.Certificate{c.Intermediate.Certificate, c.Root.Certificate}, + pfxPassword, + ) + if err != nil { + return fmt.Errorf("encoding pfx: %w", err) + } + if err := os.WriteFile(filepath.Join(dir, "server.pfx"), pfx, 0o600); err != nil { + return fmt.Errorf("writing server.pfx: %w", err) + } + return nil +} + +// LoadCA reads the root and intermediate CA (cert + key) previously written by +// Export from dir, so renewals can re-issue leaves under the same chain. +// Returns (nil, nil, nil) when the CA files are not present yet. +func LoadCA(dir string) (root, intermediate *CertKey, err error) { + rootCrt := filepath.Join(dir, "root.crt") + if _, statErr := os.Stat(rootCrt); os.IsNotExist(statErr) { + return nil, nil, nil + } + root, err = loadCertKey(filepath.Join(dir, "root.crt"), filepath.Join(dir, "root.key")) + if err != nil { + return nil, nil, fmt.Errorf("loading root CA: %w", err) + } + intermediate, err = loadCertKey(filepath.Join(dir, "intermediate.crt"), filepath.Join(dir, "intermediate.key")) + if err != nil { + return nil, nil, fmt.Errorf("loading intermediate CA: %w", err) + } + return root, intermediate, nil +} + +func loadCertKey(certPath, keyPath string) (*CertKey, error) { + certBytes, err := os.ReadFile(certPath) + if err != nil { + return nil, err + } + keyBytes, err := os.ReadFile(keyPath) + if err != nil { + return nil, err + } + cert, err := ParseCertPEM(certBytes) + if err != nil { + return nil, err + } + key, err := ParseKeyPEM(keyBytes) + if err != nil { + return nil, err + } + return &CertKey{Certificate: cert, DER: cert.Raw, Key: key}, nil +} + +// ParseCertPEM parses the first CERTIFICATE block from PEM bytes. +func ParseCertPEM(b []byte) (*x509.Certificate, error) { + block, _ := pem.Decode(b) + if block == nil || block.Type != "CERTIFICATE" { + return nil, fmt.Errorf("no CERTIFICATE PEM block found") + } + return x509.ParseCertificate(block.Bytes) +} + +// ParseKeyPEM parses an RSA private key from a PKCS#8 or PKCS#1 PEM block. +func ParseKeyPEM(b []byte) (*rsa.PrivateKey, error) { + block, _ := pem.Decode(b) + if block == nil { + return nil, fmt.Errorf("no PRIVATE KEY PEM block found") + } + if k, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil { + if rk, ok := k.(*rsa.PrivateKey); ok { + return rk, nil + } + return nil, fmt.Errorf("private key is not RSA") + } + return x509.ParsePKCS1PrivateKey(block.Bytes) +} diff --git a/backend/internal/pki/pki.go b/backend/internal/pki/pki.go new file mode 100644 index 0000000..a4a272f --- /dev/null +++ b/backend/internal/pki/pki.go @@ -0,0 +1,198 @@ +// Package pki implements OrchestrAD's self-managed certificate authority: it +// generates a Root CA, an Intermediate CA, and a server (leaf) certificate, and +// exports the material to disk in PEM, DER, and PKCS#12 (.pfx) form. The CA is +// persisted so leaves can be renewed under the same chain of trust; only the +// leaf is re-issued on renewal. +package pki + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "time" +) + +const ( + rootValidity = 10 * 365 * 24 * time.Hour + intermediateValidity = 5 * 365 * 24 * time.Hour + // DefaultLeafValidity is the default lifetime of an issued server cert. + DefaultLeafValidity = 397 * 24 * time.Hour + + rootKeyBits = 4096 + intermediateKeyBits = 4096 + leafKeyBits = 2048 +) + +// CertKey bundles a parsed certificate, its DER encoding, and its RSA private +// key. +type CertKey struct { + Certificate *x509.Certificate + DER []byte + Key *rsa.PrivateKey +} + +// Chain is a full Root -> Intermediate -> Leaf certificate chain. +type Chain struct { + Root *CertKey + Intermediate *CertKey + Leaf *CertKey +} + +func serialNumber() (*big.Int, error) { + limit := new(big.Int).Lsh(big.NewInt(1), 128) + return rand.Int(rand.Reader, limit) +} + +// NewCA generates a fresh Root CA and Intermediate CA. The intermediate is +// path-length constrained so it can only issue leaf certificates. +func NewCA(rootCN, intermediateCN string) (root, intermediate *CertKey, err error) { + now := time.Now().UTC().Add(-5 * time.Minute) + + // Root + rootKey, err := rsa.GenerateKey(rand.Reader, rootKeyBits) + if err != nil { + return nil, nil, fmt.Errorf("root key: %w", err) + } + rootSerial, err := serialNumber() + if err != nil { + return nil, nil, err + } + rootTmpl := &x509.Certificate{ + SerialNumber: rootSerial, + Subject: pkix.Name{CommonName: rootCN, Organization: []string{"OrchestrAD"}}, + NotBefore: now, + NotAfter: now.Add(rootValidity), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + } + rootDER, err := x509.CreateCertificate(rand.Reader, rootTmpl, rootTmpl, &rootKey.PublicKey, rootKey) + if err != nil { + return nil, nil, fmt.Errorf("root cert: %w", err) + } + rootCert, err := x509.ParseCertificate(rootDER) + if err != nil { + return nil, nil, err + } + root = &CertKey{Certificate: rootCert, DER: rootDER, Key: rootKey} + + // Intermediate, signed by root + intKey, err := rsa.GenerateKey(rand.Reader, intermediateKeyBits) + if err != nil { + return nil, nil, fmt.Errorf("intermediate key: %w", err) + } + intSerial, err := serialNumber() + if err != nil { + return nil, nil, err + } + intTmpl := &x509.Certificate{ + SerialNumber: intSerial, + Subject: pkix.Name{CommonName: intermediateCN, Organization: []string{"OrchestrAD"}}, + NotBefore: now, + NotAfter: now.Add(intermediateValidity), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + } + intDER, err := x509.CreateCertificate(rand.Reader, intTmpl, rootCert, &intKey.PublicKey, rootKey) + if err != nil { + return nil, nil, fmt.Errorf("intermediate cert: %w", err) + } + intCert, err := x509.ParseCertificate(intDER) + if err != nil { + return nil, nil, err + } + intermediate = &CertKey{Certificate: intCert, DER: intDER, Key: intKey} + return root, intermediate, nil +} + +// IssueLeaf issues a server certificate signed by the intermediate CA for the +// given common name and SANs. It is used both for first issuance and renewal. +func IssueLeaf(intermediate *CertKey, cn string, dnsNames []string, ips []net.IP, validity time.Duration) (*CertKey, error) { + if validity <= 0 { + validity = DefaultLeafValidity + } + now := time.Now().UTC().Add(-5 * time.Minute) + + leafKey, err := rsa.GenerateKey(rand.Reader, leafKeyBits) + if err != nil { + return nil, fmt.Errorf("leaf key: %w", err) + } + serial, err := serialNumber() + if err != nil { + return nil, err + } + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: cn, Organization: []string{"OrchestrAD"}}, + NotBefore: now, + NotAfter: now.Add(validity), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: dnsNames, + IPAddresses: ips, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, intermediate.Certificate, &leafKey.PublicKey, intermediate.Key) + if err != nil { + return nil, fmt.Errorf("leaf cert: %w", err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, err + } + return &CertKey{Certificate: cert, DER: der, Key: leafKey}, nil +} + +// GenerateChain creates a full Root -> Intermediate -> Leaf chain in one call. +func GenerateChain(cn string, dnsNames []string, ips []net.IP, leafValidity time.Duration) (*Chain, error) { + root, intermediate, err := NewCA("OrchestrAD Root CA", "OrchestrAD Intermediate CA") + if err != nil { + return nil, err + } + leaf, err := IssueLeaf(intermediate, cn, dnsNames, ips, leafValidity) + if err != nil { + return nil, err + } + return &Chain{Root: root, Intermediate: intermediate, Leaf: leaf}, nil +} + +// certPEM / keyPEM helpers. +func certPEM(der []byte) []byte { + return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) +} + +func keyPEM(key *rsa.PrivateKey) []byte { + return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8(key)}) +} + +func pkcs8(key *rsa.PrivateKey) []byte { + b, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + // RSA keys always marshal; fall back to PKCS1 defensively. + return x509.MarshalPKCS1PrivateKey(key) + } + return b +} + +// FullChainPEM returns leaf + intermediate + root concatenated in PEM order +// (leaf first), suitable for a server fullchain file. +func (c *Chain) FullChainPEM() []byte { + out := append([]byte{}, certPEM(c.Leaf.DER)...) + out = append(out, certPEM(c.Intermediate.DER)...) + out = append(out, certPEM(c.Root.DER)...) + return out +} + +// TLSCertificate builds a tls.Certificate (leaf + intermediate chain) for use by +// the HTTPS server. +func (c *Chain) TLSCertificate() (leafDER, intDER []byte, key *rsa.PrivateKey) { + return c.Leaf.DER, c.Intermediate.DER, c.Leaf.Key +} diff --git a/backend/internal/pki/pki_test.go b/backend/internal/pki/pki_test.go new file mode 100644 index 0000000..a6ecbf8 --- /dev/null +++ b/backend/internal/pki/pki_test.go @@ -0,0 +1,117 @@ +package pki + +import ( + "crypto/x509" + "net" + "os" + "path/filepath" + "testing" + "time" + + pkcs12 "software.sslmate.com/src/go-pkcs12" +) + +func TestGenerateChainVerifies(t *testing.T) { + chain, err := GenerateChain("orchestrad.local", []string{"orchestrad.local", "localhost"}, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour) + if err != nil { + t.Fatalf("GenerateChain: %v", err) + } + + // Leaf must chain to the root through the intermediate. + roots := x509.NewCertPool() + roots.AddCert(chain.Root.Certificate) + inter := x509.NewCertPool() + inter.AddCert(chain.Intermediate.Certificate) + + if _, err := chain.Leaf.Certificate.Verify(x509.VerifyOptions{ + Roots: roots, + Intermediates: inter, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + }); err != nil { + t.Fatalf("leaf does not verify to root via intermediate: %v", err) + } + + // SANs present. + if err := chain.Leaf.Certificate.VerifyHostname("orchestrad.local"); err != nil { + t.Errorf("hostname SAN missing: %v", err) + } + if !chain.Root.Certificate.IsCA || !chain.Intermediate.Certificate.IsCA { + t.Error("CA certificates must have IsCA set") + } + if chain.Leaf.Certificate.IsCA { + t.Error("leaf must not be a CA") + } +} + +func TestExportAndReload(t *testing.T) { + dir := t.TempDir() + chain, err := GenerateChain("orchestrad.local", []string{"localhost"}, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour) + if err != nil { + t.Fatalf("GenerateChain: %v", err) + } + if err := chain.Export(dir, "orchestrad"); err != nil { + t.Fatalf("Export: %v", err) + } + + // All expected files exist. + for _, name := range []string{ + "root.crt", "root.key", "intermediate.crt", "intermediate.key", + "server.crt", "server.key", "fullchain.pem", + "root.cer", "intermediate.cer", "server.cer", "server.pfx", + } { + if _, err := os.Stat(filepath.Join(dir, name)); err != nil { + t.Errorf("expected export %s: %v", name, err) + } + } + + // The PFX decodes back to the leaf key + cert + CA chain. + pfx, err := os.ReadFile(filepath.Join(dir, "server.pfx")) + if err != nil { + t.Fatalf("read pfx: %v", err) + } + key, cert, caCerts, err := pkcs12.DecodeChain(pfx, "orchestrad") + if err != nil { + t.Fatalf("decode pfx: %v", err) + } + if key == nil || cert == nil { + t.Fatal("pfx missing key or cert") + } + if cert.Subject.CommonName != "orchestrad.local" { + t.Errorf("pfx cert CN = %q, want orchestrad.local", cert.Subject.CommonName) + } + if len(caCerts) != 2 { + t.Errorf("pfx CA chain = %d certs, want 2 (intermediate + root)", len(caCerts)) + } + + // LoadCA reconstructs the CA so renewal can re-issue a leaf. + root, inter, err := LoadCA(dir) + if err != nil { + t.Fatalf("LoadCA: %v", err) + } + if root == nil || inter == nil { + t.Fatal("LoadCA returned nil for an existing CA") + } + newLeaf, err := IssueLeaf(inter, "orchestrad.local", []string{"localhost"}, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour) + if err != nil { + t.Fatalf("re-issue leaf from loaded CA: %v", err) + } + roots := x509.NewCertPool() + roots.AddCert(root.Certificate) + interPool := x509.NewCertPool() + interPool.AddCert(inter.Certificate) + if _, err := newLeaf.Certificate.Verify(x509.VerifyOptions{Roots: roots, Intermediates: interPool, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}); err != nil { + t.Fatalf("renewed leaf does not verify under loaded CA: %v", err) + } +} + +// LoadCA on an empty dir returns nils (not an error) so first-run generation is +// triggered. +func TestLoadCAAbsent(t *testing.T) { + root, inter, err := LoadCA(t.TempDir()) + if err != nil { + t.Fatalf("LoadCA on empty dir: %v", err) + } + if root != nil || inter != nil { + t.Error("expected nil CA for an empty directory") + } +} -- 2.52.0 From 300866d5cf23ac12986b67502b505ab809151ec5 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:06:42 -0400 Subject: [PATCH 05/34] feat(tls): auto-managed HTTPS with renewal; disable for edge termination Add internal/tlsmgr: on startup, load-or-generate the CA, issue/reuse the leaf, export the chain under /tls, and hot-swap the cert on renewal via GetCertificate. The server listens HTTPS when a manager is present. TLS is on by default for the standalone binary/service (tls.enabled setting or ORCHESTRAD_TLS_ENABLED, UI wins over env); the container image defaults it off so it runs plain HTTP behind a TLS-terminating proxy. Startup logs a clickable URL. Verified end to end: HTTPS serves, plain HTTP rejected, disable serves HTTP. Co-Authored-By: Claude Opus 4.8 --- Dockerfile | 4 + backend/internal/cli/cli.go | 27 ++- backend/internal/server/server.go | 33 +++- backend/internal/tlsmgr/manager.go | 231 ++++++++++++++++++++++++ backend/internal/tlsmgr/manager_test.go | 62 +++++++ docker-compose.yml | 3 + 6 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 backend/internal/tlsmgr/manager.go create mode 100644 backend/internal/tlsmgr/manager_test.go diff --git a/Dockerfile b/Dockerfile index e06bb75..08da293 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,6 +75,10 @@ RUN mkdir -p /data/logs /data/backups && \ # Default environment ENV ORCHESTRAD_DATA_PATH=/data ENV ORCHESTRAD_LOG_LEVEL=info +# Containers typically sit behind an ingress/proxy that terminates TLS, so the +# image serves plain HTTP by default and the healthcheck below is HTTP. Set +# ORCHESTRAD_TLS_ENABLED=true to have the container manage its own certificate. +ENV ORCHESTRAD_TLS_ENABLED=false # Expose port EXPOSE 18090 diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index d820a01..d1f9ba8 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -19,6 +19,7 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/scheduler" "github.com/Grace-Solutions/OrchestrAD/internal/server" "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/Grace-Solutions/OrchestrAD/internal/tlsmgr" ) // RunInit initializes the application: validates config, initializes DB, runs migrations @@ -101,6 +102,29 @@ func runServer(ctx context.Context) error { connService := services.NewConnectionService(database.Conn(), encryptor, logger) ruleRunner := runner.New(database.Conn(), connService, logger) auditService := audit.NewService(database.Conn()) + settingsService := services.NewSettingsService(database.Conn(), logger) + + // TLS: on by default (self-managed CA + leaf, auto-renewed, exported under + // /tls). Disable via the tls.enabled setting or ORCHESTRAD_TLS_ENABLED + // to serve plain HTTP behind a TLS-terminating proxy. A UI value wins over + // the env var. + var tlsManager *tlsmgr.Manager + if settingsService.ResolveBool("tls.enabled", "ORCHESTRAD_TLS_ENABLED", true) { + tlsManager = tlsmgr.New( + filepath.Join(cfg.DataPath, "tls"), + tlsmgr.Options{ + PFXPassword: settingsService.ResolveString("tls.pfx_password", "ORCHESTRAD_TLS_PFX_PASSWORD", "orchestrad"), + }, + logger, + ) + if err := tlsManager.Ensure(); err != nil { + return fmt.Errorf("initializing TLS: %w", err) + } + go tlsManager.Start(ctx) + } else { + logger.Info("TLS", "TLS disabled; serving plain HTTP (expecting TLS termination upstream)") + } + deps := server.Dependencies{ Runner: ruleRunner, Engine: engine.NewEngine(logger), @@ -116,9 +140,10 @@ func runServer(ctx context.Context) error { UserRepo: repository.NewUserRepository(database.Conn()), APIKeyService: services.NewAPIKeyService(database.Conn(), logger), BackupService: services.NewBackupService(database, filepath.Join(cfg.DataPath, "backups"), 10, logger), - SettingsService: services.NewSettingsService(database.Conn(), logger), + SettingsService: settingsService, DashboardService: services.NewDashboardService(database.Conn(), logger), ConfigService: services.NewConfigService(database.Conn(), logger), + TLS: tlsManager, } // Record a service-start audit event so the trail is bootstrapped diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 81f4f69..345493c 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -17,6 +17,7 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/rules/engine" "github.com/Grace-Solutions/OrchestrAD/internal/rules/runner" "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/Grace-Solutions/OrchestrAD/internal/tlsmgr" "github.com/Grace-Solutions/OrchestrAD/internal/version" "github.com/Grace-Solutions/OrchestrAD/internal/webui" "github.com/go-chi/chi/v5" @@ -43,6 +44,10 @@ type Dependencies struct { ScheduleRepo *repository.ScheduleRepository RunRepo *repository.RuleRunRepository UserRepo *repository.UserRepository + + // TLS, when non-nil, makes the server listen over HTTPS using the managed + // certificate. Nil serves plain HTTP (e.g. behind a TLS-terminating proxy). + TLS *tlsmgr.Manager } // Server represents the HTTP server @@ -296,6 +301,7 @@ func (s *Server) handleNotImplemented(w http.ResponseWriter, r *http.Request) { // Run starts the HTTP server and blocks until shutdown func (s *Server) Run(ctx context.Context) error { addr := fmt.Sprintf("%s:%d", s.config.Server.Host, s.config.Server.Port) + useTLS := s.deps.TLS != nil s.httpSrv = &http.Server{ Addr: addr, Handler: s.router, @@ -303,12 +309,35 @@ func (s *Server) Run(ctx context.Context) error { WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } + if useTLS { + s.httpSrv.TLSConfig = s.deps.TLS.TLSConfig() + } - s.logger.Info("Server", "Starting HTTP server on %s", addr) + scheme, schemeLower := "HTTP", "http" + if useTLS { + scheme, schemeLower = "HTTPS", "https" + } + s.logger.Info("Server", "Starting %s server on %s", scheme, addr) + + // Log a clickable URL. 0.0.0.0/:: are not directly reachable, so show + // localhost for those. + accessHost := s.config.Server.Host + if accessHost == "0.0.0.0" || accessHost == "::" || accessHost == "" { + accessHost = "localhost" + } + s.logger.Info("Server", "OrchestrAD is available at %s://%s:%d/", schemeLower, accessHost, s.config.Server.Port) errChan := make(chan error, 1) go func() { - if err := s.httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + var err error + if useTLS { + // Certificates come from the manager's GetCertificate, so the file + // arguments are intentionally empty. + err = s.httpSrv.ListenAndServeTLS("", "") + } else { + err = s.httpSrv.ListenAndServe() + } + if err != nil && err != http.ErrServerClosed { errChan <- err } }() diff --git a/backend/internal/tlsmgr/manager.go b/backend/internal/tlsmgr/manager.go new file mode 100644 index 0000000..67bc6dc --- /dev/null +++ b/backend/internal/tlsmgr/manager.go @@ -0,0 +1,231 @@ +// Package tlsmgr manages the lifecycle of the server's TLS certificate: it +// loads or generates the self-managed CA + leaf on startup, exports the chain +// (PEM/DER/PFX) to the TLS directory, renews the leaf before it expires, and +// hands the HTTPS server a live certificate that is hot-swapped on renewal +// without a restart. +package tlsmgr + +import ( + "context" + "crypto/tls" + "net" + "os" + "path/filepath" + "sync" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/pki" +) + +// Manager owns the TLS material for the server. +type Manager struct { + dir string + cn string + dnsNames []string + ips []net.IP + leafValidity time.Duration + renewBefore time.Duration + pfxPassword string + logger *logging.Logger + + mu sync.RWMutex + current *tls.Certificate +} + +// Options configures a Manager. Zero values fall back to sensible defaults. +type Options struct { + CN string + DNSNames []string + IPs []net.IP + LeafValidity time.Duration + RenewBefore time.Duration + PFXPassword string +} + +// New creates a Manager writing to dir (typically /tls). Missing SANs +// default to the machine hostname plus loopback. +func New(dir string, opts Options, logger *logging.Logger) *Manager { + cn, dns, ips := opts.CN, opts.DNSNames, opts.IPs + if cn == "" || len(dns) == 0 { + dcn, ddns, dips := DefaultSANs() + if cn == "" { + cn = dcn + } + if len(dns) == 0 { + dns = ddns + } + if len(ips) == 0 { + ips = dips + } + } + validity := opts.LeafValidity + if validity <= 0 { + validity = pki.DefaultLeafValidity + } + renew := opts.RenewBefore + if renew <= 0 { + renew = validity / 3 + } + return &Manager{ + dir: dir, + cn: cn, + dnsNames: dns, + ips: ips, + leafValidity: validity, + renewBefore: renew, + pfxPassword: opts.PFXPassword, + logger: logger, + } +} + +// Ensure loads the CA (generating it on first run), reuses a still-valid leaf or +// issues a fresh one, exports the chain, and installs it as the current serving +// certificate. Safe to call repeatedly. +func (m *Manager) Ensure() error { + root, inter, err := pki.LoadCA(m.dir) + if err != nil { + return err + } + generatedCA := false + if root == nil || inter == nil { + m.logf("Generating self-managed CA (root + intermediate)") + root, inter, err = pki.NewCA("OrchestrAD Root CA", "OrchestrAD Intermediate CA") + if err != nil { + return err + } + generatedCA = true + } + + // Reuse an existing leaf only if the CA was loaded (consistent chain) and it + // is not close to expiry; otherwise issue a fresh one. + var leaf *pki.CertKey + if !generatedCA { + if existing := m.loadLeaf(); existing != nil && !m.expiringSoon(existing.Certificate.NotAfter) { + leaf = existing + } + } + if leaf == nil { + m.logf("Issuing server certificate for CN=%s (SANs: %v)", m.cn, m.dnsNames) + leaf, err = pki.IssueLeaf(inter, m.cn, m.dnsNames, m.ips, m.leafValidity) + if err != nil { + return err + } + } + + chain := &pki.Chain{Root: root, Intermediate: inter, Leaf: leaf} + if err := chain.Export(m.dir, m.pfxPassword); err != nil { + return err + } + m.setCurrent(chain) + return nil +} + +// GetCertificate is the tls.Config.GetCertificate callback; it always returns +// the current (possibly just-renewed) certificate. +func (m *Manager) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { + m.mu.RLock() + defer m.mu.RUnlock() + return m.current, nil +} + +// TLSConfig returns a server tls.Config wired to this manager. +func (m *Manager) TLSConfig() *tls.Config { + return &tls.Config{ + GetCertificate: m.GetCertificate, + MinVersion: tls.VersionTLS12, + } +} + +// Start runs the renewal loop until ctx is cancelled, checking periodically and +// re-issuing the leaf when it enters the renewal window. +func (m *Manager) Start(ctx context.Context) { + ticker := time.NewTicker(12 * time.Hour) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.mu.RLock() + exp := time.Time{} + if m.current != nil && m.current.Leaf != nil { + exp = m.current.Leaf.NotAfter + } + m.mu.RUnlock() + if exp.IsZero() || m.expiringSoon(exp) { + m.logf("Certificate renewal window reached; re-issuing") + if err := m.Ensure(); err != nil { + m.logErr("certificate renewal failed: %v", err) + } + } + } + } +} + +func (m *Manager) expiringSoon(notAfter time.Time) bool { + return time.Now().Add(m.renewBefore).After(notAfter) +} + +func (m *Manager) loadLeaf() *pki.CertKey { + certBytes, err := os.ReadFile(filepath.Join(m.dir, "server.crt")) + if err != nil { + return nil + } + keyBytes, err := os.ReadFile(filepath.Join(m.dir, "server.key")) + if err != nil { + return nil + } + cert, err := pki.ParseCertPEM(certBytes) + if err != nil { + return nil + } + key, err := pki.ParseKeyPEM(keyBytes) + if err != nil { + return nil + } + return &pki.CertKey{Certificate: cert, DER: cert.Raw, Key: key} +} + +func (m *Manager) setCurrent(chain *pki.Chain) { + cert := &tls.Certificate{ + Certificate: [][]byte{chain.Leaf.DER, chain.Intermediate.DER}, + PrivateKey: chain.Leaf.Key, + Leaf: chain.Leaf.Certificate, + } + m.mu.Lock() + m.current = cert + m.mu.Unlock() +} + +func (m *Manager) logf(format string, args ...any) { + if m.logger != nil { + m.logger.Info("TLS", format, args...) + } +} + +func (m *Manager) logErr(format string, args ...any) { + if m.logger != nil { + m.logger.Error("TLS", format, args...) + } +} + +// DefaultSANs derives certificate SANs from the host: the hostname, localhost, +// loopback, and any non-loopback interface IPs. +func DefaultSANs() (cn string, dnsNames []string, ips []net.IP) { + host, _ := os.Hostname() + if host == "" { + host = "localhost" + } + cn = host + dnsNames = []string{host, "localhost"} + ips = []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback} + if addrs, err := net.InterfaceAddrs(); err == nil { + for _, a := range addrs { + if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + ips = append(ips, ipnet.IP) + } + } + } + return cn, dnsNames, ips +} diff --git a/backend/internal/tlsmgr/manager_test.go b/backend/internal/tlsmgr/manager_test.go new file mode 100644 index 0000000..47f1e08 --- /dev/null +++ b/backend/internal/tlsmgr/manager_test.go @@ -0,0 +1,62 @@ +package tlsmgr + +import ( + "net" + "testing" + "time" +) + +func testManager(t *testing.T, renewBefore time.Duration) *Manager { + t.Helper() + return New(t.TempDir(), Options{ + CN: "orchestrad.local", + DNSNames: []string{"orchestrad.local", "localhost"}, + IPs: []net.IP{net.ParseIP("127.0.0.1")}, + LeafValidity: time.Hour, + RenewBefore: renewBefore, + PFXPassword: "orchestrad", + }, nil) +} + +func TestEnsureAndServe(t *testing.T) { + m := testManager(t, time.Minute) + if err := m.Ensure(); err != nil { + t.Fatalf("Ensure: %v", err) + } + cert, err := m.GetCertificate(nil) + if err != nil || cert == nil || cert.Leaf == nil { + t.Fatalf("GetCertificate returned no cert: cert=%v err=%v", cert, err) + } + if len(cert.Certificate) != 2 { + t.Errorf("expected leaf+intermediate in the chain, got %d certs", len(cert.Certificate)) + } + first := cert.Leaf.SerialNumber.String() + + // Re-ensure: the leaf is not near expiry, so it is reused. + if err := m.Ensure(); err != nil { + t.Fatalf("second Ensure: %v", err) + } + cert2, _ := m.GetCertificate(nil) + if cert2.Leaf.SerialNumber.String() != first { + t.Error("leaf was re-issued despite not being near expiry") + } +} + +func TestRenewalReissues(t *testing.T) { + // renewBefore >> validity forces the leaf to always look "expiring soon", + // so a second Ensure re-issues it. + m := testManager(t, 2*time.Hour) + if err := m.Ensure(); err != nil { + t.Fatalf("Ensure: %v", err) + } + c1, _ := m.GetCertificate(nil) + first := c1.Leaf.SerialNumber.String() + + if err := m.Ensure(); err != nil { + t.Fatalf("renew Ensure: %v", err) + } + c2, _ := m.GetCertificate(nil) + if c2.Leaf.SerialNumber.String() == first { + t.Error("leaf should have been re-issued in the renewal window") + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 56ff490..d1b336b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,9 @@ services: - ORCHESTRAD_SECRET_KEY=${ORCHESTRAD_SECRET_KEY:-} - ORCHESTRAD_HOST=0.0.0.0 - ORCHESTRAD_PORT=18090 + # Plain HTTP by default (terminate TLS at your ingress/proxy). Set to true + # to have the container manage its own self-signed certificate. + - ORCHESTRAD_TLS_ENABLED=false healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:18090/health"] interval: 30s -- 2.52.0 From a4698ee9f9a8200cb234363ffc8fb8cf6d592210 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:21:00 -0400 Subject: [PATCH 06/34] feat(tls): certificate modes (auto/provided/windows-store) + config API + richer SANs Add runtime TLS modes resolved via settings (UI wins over env): auto (self-managed), provided (bring-your-own upload), and windows-store (Windows cert store, enumeration/serving scaffolded). New /api/v1/tls endpoints: status, mode, certificate upload, and windows-store list; changes reload the live manager without a restart, falling back to the self-managed cert if a configured source is unavailable. Auto SANs now cover the hostname (CN), localhost, every non-loopback IP, and host. + the suffix for each detected DNS suffix (Windows registry / resolv.conf). Bring-your-own is validated and unit-tested. Co-Authored-By: Claude Opus 4.8 --- backend/go.mod | 2 +- backend/internal/api/tls_handlers.go | 148 +++++++++ backend/internal/cli/cli.go | 9 +- backend/internal/server/server.go | 10 + backend/internal/tlsmgr/manager.go | 316 ++++++++++++++++---- backend/internal/tlsmgr/manager_test.go | 55 +++- backend/internal/tlsmgr/store.go | 15 + backend/internal/tlsmgr/store_other.go | 17 ++ backend/internal/tlsmgr/store_windows.go | 21 ++ backend/internal/tlsmgr/suffixes_other.go | 48 +++ backend/internal/tlsmgr/suffixes_windows.go | 41 +++ 11 files changed, 623 insertions(+), 59 deletions(-) create mode 100644 backend/internal/api/tls_handlers.go create mode 100644 backend/internal/tlsmgr/store.go create mode 100644 backend/internal/tlsmgr/store_other.go create mode 100644 backend/internal/tlsmgr/store_windows.go create mode 100644 backend/internal/tlsmgr/suffixes_other.go create mode 100644 backend/internal/tlsmgr/suffixes_windows.go diff --git a/backend/go.mod b/backend/go.mod index 01a2c9f..a2c9193 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -11,6 +11,7 @@ require ( github.com/kardianos/service v1.3.0 github.com/robfig/cron/v3 v3.0.1 golang.org/x/crypto v0.48.0 + golang.org/x/sys v0.47.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 modernc.org/sqlite v1.58.0 software.sslmate.com/src/go-pkcs12 v0.7.3 @@ -26,7 +27,6 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/sys v0.47.0 // indirect modernc.org/libc v1.75.6 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.12.1 // indirect diff --git a/backend/internal/api/tls_handlers.go b/backend/internal/api/tls_handlers.go new file mode 100644 index 0000000..98c1bfe --- /dev/null +++ b/backend/internal/api/tls_handlers.go @@ -0,0 +1,148 @@ +// Package api - TLS configuration handlers (certificate mode, bring-your-own +// upload, and Windows-store selection). Settings persist to app_settings so the +// UI choice overrides the environment, and the live TLS manager is reloaded so +// changes take effect without a restart. +package api + +import ( + "net/http" + + "github.com/Grace-Solutions/OrchestrAD/internal/audit" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/Grace-Solutions/OrchestrAD/internal/tlsmgr" +) + +// TLSHandler handles /api/v1/tls endpoints. manager may be nil when TLS is +// disabled (plain HTTP behind a proxy). +type TLSHandler struct { + settings *services.SettingsService + manager *tlsmgr.Manager + auditService *audit.Service + logger *logging.Logger +} + +// NewTLSHandler creates a TLSHandler. +func NewTLSHandler(settings *services.SettingsService, manager *tlsmgr.Manager, auditService *audit.Service, logger *logging.Logger) *TLSHandler { + return &TLSHandler{settings: settings, manager: manager, auditService: auditService, logger: logger} +} + +type tlsStatusResponse struct { + Enabled bool `json:"enabled"` + WindowsSupported bool `json:"windowsStoreSupported"` + Certificate *tlsmgr.Info `json:"certificate,omitempty"` +} + +// Status handles GET /api/v1/tls/status +func (h *TLSHandler) Status(w http.ResponseWriter, r *http.Request) { + resp := tlsStatusResponse{ + Enabled: h.manager != nil, + WindowsSupported: tlsmgr.WindowsStoreSupported(), + } + if h.manager != nil { + info := h.manager.CurrentInfo() + resp.Certificate = &info + } + WriteJSON(w, http.StatusOK, resp) +} + +type tlsModeRequest struct { + Mode string `json:"mode"` + WindowsThumbprint string `json:"windowsThumbprint,omitempty"` +} + +// SetMode handles POST /api/v1/tls/mode +func (h *TLSHandler) SetMode(w http.ResponseWriter, r *http.Request) { + if h.manager == nil { + WriteError(w, http.StatusConflict, ErrCodeValidation, "TLS is disabled; enable it before configuring a certificate mode") + return + } + var req tlsModeRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + switch req.Mode { + case tlsmgr.ModeAuto, tlsmgr.ModeProvided, tlsmgr.ModeWindowsStore: + default: + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "mode must be auto, provided, or windows-store") + return + } + if req.Mode == tlsmgr.ModeWindowsStore { + if !tlsmgr.WindowsStoreSupported() { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "the Windows certificate store is only available on Windows") + return + } + if req.WindowsThumbprint == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "windowsThumbprint is required for windows-store mode") + return + } + if err := h.settings.Upsert(services.Setting{Key: "tls.windows_thumbprint", Value: req.WindowsThumbprint, ValueType: "string"}); err != nil { + h.internalError(w, "persisting thumbprint", err) + return + } + } + if err := h.settings.Upsert(services.Setting{Key: "tls.mode", Value: req.Mode, ValueType: "string"}); err != nil { + h.internalError(w, "persisting mode", err) + return + } + h.reloadAndRespond(w, r, "SetMode", req.Mode) +} + +type tlsUploadRequest struct { + CertificatePEM string `json:"certificatePem"` + PrivateKeyPEM string `json:"privateKeyPem"` + ChainPEM string `json:"chainPem,omitempty"` +} + +// UploadCertificate handles POST /api/v1/tls/certificate (bring-your-own). +func (h *TLSHandler) UploadCertificate(w http.ResponseWriter, r *http.Request) { + if h.manager == nil { + WriteError(w, http.StatusConflict, ErrCodeValidation, "TLS is disabled; enable it before uploading a certificate") + return + } + var req tlsUploadRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + if req.CertificatePEM == "" || req.PrivateKeyPEM == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "certificatePem and privateKeyPem are required") + return + } + if err := h.manager.SaveProvided([]byte(req.CertificatePEM), []byte(req.PrivateKeyPEM), []byte(req.ChainPEM)); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "Invalid certificate/key: "+err.Error()) + return + } + if err := h.settings.Upsert(services.Setting{Key: "tls.mode", Value: tlsmgr.ModeProvided, ValueType: "string"}); err != nil { + h.internalError(w, "persisting mode", err) + return + } + h.reloadAndRespond(w, r, "UploadCertificate", tlsmgr.ModeProvided) +} + +// WindowsCerts handles GET /api/v1/tls/windows-store +func (h *TLSHandler) WindowsCerts(w http.ResponseWriter, r *http.Request) { + certs, err := tlsmgr.ListWindowsCerts() + if err != nil { + WriteError(w, http.StatusNotImplemented, ErrCodeInternalError, err.Error()) + return + } + WriteJSON(w, http.StatusOK, certs) +} + +func (h *TLSHandler) reloadAndRespond(w http.ResponseWriter, r *http.Request, action, mode string) { + if err := h.manager.Reload(); err != nil { + emitAudit(h.auditService, r, audit.EventConfigChange, "TLS", mode, action, false, nil, err.Error()) + h.internalError(w, "reloading TLS", err) + return + } + info := h.manager.CurrentInfo() + emitAudit(h.auditService, r, audit.EventConfigChange, "TLS", mode, action, true, map[string]any{"mode": mode, "fallback": info.Fallback}, "") + WriteJSON(w, http.StatusOK, info) +} + +func (h *TLSHandler) internalError(w http.ResponseWriter, ctx string, err error) { + h.logger.Error("TLSHandler", "%s failed: %v", ctx, err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "TLS operation failed") +} diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index d1f9ba8..3e4fd57 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -112,8 +112,13 @@ func runServer(ctx context.Context) error { if settingsService.ResolveBool("tls.enabled", "ORCHESTRAD_TLS_ENABLED", true) { tlsManager = tlsmgr.New( filepath.Join(cfg.DataPath, "tls"), - tlsmgr.Options{ - PFXPassword: settingsService.ResolveString("tls.pfx_password", "ORCHESTRAD_TLS_PFX_PASSWORD", "orchestrad"), + tlsmgr.Options{}, + func() tlsmgr.Config { + return tlsmgr.Config{ + Mode: settingsService.ResolveString("tls.mode", "ORCHESTRAD_TLS_MODE", tlsmgr.ModeAuto), + PFXPassword: settingsService.ResolveString("tls.pfx_password", "ORCHESTRAD_TLS_PFX_PASSWORD", "orchestrad"), + WindowsThumbprint: settingsService.ResolveString("tls.windows_thumbprint", "ORCHESTRAD_TLS_WINDOWS_THUMBPRINT", ""), + } }, logger, ) diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 345493c..7b80185 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -267,6 +267,16 @@ func (s *Server) setupRoutes() { r.Get("/export", configHandler.Export) r.Post("/import", configHandler.Import) }) + + // TLS certificate configuration (mode, bring-your-own upload, + // Windows-store selection). + tlsHandler := api.NewTLSHandler(s.deps.SettingsService, s.deps.TLS, s.deps.AuditService, s.logger) + r.Route("/tls", func(r chi.Router) { + r.Get("/status", tlsHandler.Status) + r.Post("/mode", tlsHandler.SetMode) + r.Post("/certificate", tlsHandler.UploadCertificate) + r.Get("/windows-store", tlsHandler.WindowsCerts) + }) }) // end protected group }) diff --git a/backend/internal/tlsmgr/manager.go b/backend/internal/tlsmgr/manager.go index 67bc6dc..c1fbdff 100644 --- a/backend/internal/tlsmgr/manager.go +++ b/backend/internal/tlsmgr/manager.go @@ -1,8 +1,14 @@ -// Package tlsmgr manages the lifecycle of the server's TLS certificate: it -// loads or generates the self-managed CA + leaf on startup, exports the chain -// (PEM/DER/PFX) to the TLS directory, renews the leaf before it expires, and -// hands the HTTPS server a live certificate that is hot-swapped on renewal -// without a restart. +// Package tlsmgr manages the lifecycle of the server's TLS certificate. It +// supports three sources, chosen at runtime (UI/env) via a config callback: +// +// auto self-managed CA + leaf, auto-renewed, exported under /tls +// provided an administrator-supplied cert/key (bring-your-own), stored +// under /tls/provided +// windows-store a certificate selected from the Windows "My" store (Windows +// only) referenced by thumbprint +// +// In every mode the server is handed a live certificate through GetCertificate, +// so a mode change or renewal is picked up without a restart (call Reload). package tlsmgr import ( @@ -11,6 +17,7 @@ import ( "net" "os" "path/filepath" + "strings" "sync" "time" @@ -18,34 +25,54 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/pki" ) -// Manager owns the TLS material for the server. -type Manager struct { - dir string - cn string - dnsNames []string - ips []net.IP - leafValidity time.Duration - renewBefore time.Duration - pfxPassword string - logger *logging.Logger +// Modes. +const ( + ModeAuto = "auto" + ModeProvided = "provided" + ModeWindowsStore = "windows-store" + providedSubdir = "provided" + providedCertFile = "server.crt" + providedKeyFile = "server.key" + providedChainFile = "chain.pem" +) - mu sync.RWMutex - current *tls.Certificate +// Config is the runtime configuration resolved on each Ensure/Reload (so a UI +// change takes effect without restart). +type Config struct { + Mode string // ModeAuto (default), ModeProvided, ModeWindowsStore + PFXPassword string + WindowsThumbprint string } -// Options configures a Manager. Zero values fall back to sensible defaults. +// Options holds build-time parameters that do not change at runtime. type Options struct { CN string DNSNames []string IPs []net.IP LeafValidity time.Duration RenewBefore time.Duration - PFXPassword string } -// New creates a Manager writing to dir (typically /tls). Missing SANs -// default to the machine hostname plus loopback. -func New(dir string, opts Options, logger *logging.Logger) *Manager { +// Manager owns the TLS material for the server. +type Manager struct { + dir string + configFn func() Config + cn string + dnsNames []string + ips []net.IP + leafValidity time.Duration + renewBefore time.Duration + logger *logging.Logger + + mu sync.RWMutex + current *tls.Certificate + mode string + fallback bool // true when the configured mode failed and auto is serving +} + +// New creates a Manager writing to dir (typically /tls). configFn supplies +// the runtime mode/params; a nil configFn means auto mode. +func New(dir string, opts Options, configFn func() Config, logger *logging.Logger) *Manager { cn, dns, ips := opts.CN, opts.DNSNames, opts.IPs if cn == "" || len(dns) == 0 { dcn, ddns, dips := DefaultSANs() @@ -69,20 +96,73 @@ func New(dir string, opts Options, logger *logging.Logger) *Manager { } return &Manager{ dir: dir, + configFn: configFn, cn: cn, dnsNames: dns, ips: ips, leafValidity: validity, renewBefore: renew, - pfxPassword: opts.PFXPassword, logger: logger, } } -// Ensure loads the CA (generating it on first run), reuses a still-valid leaf or -// issues a fresh one, exports the chain, and installs it as the current serving -// certificate. Safe to call repeatedly. +func (m *Manager) config() Config { + if m.configFn == nil { + return Config{Mode: ModeAuto} + } + c := m.configFn() + if c.Mode == "" { + c.Mode = ModeAuto + } + return c +} + +// Ensure installs the current serving certificate according to the configured +// mode. On failure of a non-auto mode it falls back to the self-managed cert so +// the server still comes up over HTTPS. func (m *Manager) Ensure() error { + cfg := m.config() + switch cfg.Mode { + case ModeProvided: + if err := m.ensureProvided(); err != nil { + m.logErr("provided certificate unavailable (%v); falling back to self-managed", err) + return m.ensureAutoFallback(cfg.PFXPassword, ModeProvided) + } + m.setMode(ModeProvided, false) + m.logf("Serving administrator-provided certificate") + return nil + case ModeWindowsStore: + if err := m.ensureWindowsStore(cfg.WindowsThumbprint); err != nil { + m.logErr("windows-store certificate unavailable (%v); falling back to self-managed", err) + return m.ensureAutoFallback(cfg.PFXPassword, ModeWindowsStore) + } + m.setMode(ModeWindowsStore, false) + m.logf("Serving certificate from the Windows store (thumbprint %s)", cfg.WindowsThumbprint) + return nil + default: + if err := m.ensureAuto(cfg.PFXPassword); err != nil { + return err + } + m.setMode(ModeAuto, false) + return nil + } +} + +// Reload re-reads the configured mode and re-applies it. Called after the UI +// changes the TLS configuration. +func (m *Manager) Reload() error { return m.Ensure() } + +func (m *Manager) ensureAutoFallback(pfxPassword, attempted string) error { + if err := m.ensureAuto(pfxPassword); err != nil { + return err + } + m.setMode(ModeAuto, true) + return nil +} + +// ensureAuto loads-or-generates the CA, reuses or renews the leaf, exports the +// chain, and installs it. +func (m *Manager) ensureAuto(pfxPassword string) error { root, inter, err := pki.LoadCA(m.dir) if err != nil { return err @@ -97,11 +177,9 @@ func (m *Manager) Ensure() error { generatedCA = true } - // Reuse an existing leaf only if the CA was loaded (consistent chain) and it - // is not close to expiry; otherwise issue a fresh one. var leaf *pki.CertKey if !generatedCA { - if existing := m.loadLeaf(); existing != nil && !m.expiringSoon(existing.Certificate.NotAfter) { + if existing := m.loadLeaf(m.dir); existing != nil && !m.expiringSoon(existing.Certificate.NotAfter) { leaf = existing } } @@ -114,15 +192,44 @@ func (m *Manager) Ensure() error { } chain := &pki.Chain{Root: root, Intermediate: inter, Leaf: leaf} - if err := chain.Export(m.dir, m.pfxPassword); err != nil { + if err := chain.Export(m.dir, pfxPassword); err != nil { return err } - m.setCurrent(chain) + m.setCurrent(&tls.Certificate{ + Certificate: [][]byte{chain.Leaf.DER, chain.Intermediate.DER}, + PrivateKey: chain.Leaf.Key, + Leaf: chain.Leaf.Certificate, + }) return nil } -// GetCertificate is the tls.Config.GetCertificate callback; it always returns -// the current (possibly just-renewed) certificate. +// ensureProvided loads a bring-your-own cert/key (plus optional chain) written +// under

    /provided by the upload API. +func (m *Manager) ensureProvided() error { + pdir := filepath.Join(m.dir, providedSubdir) + certPEM, err := os.ReadFile(filepath.Join(pdir, providedCertFile)) + if err != nil { + return err + } + keyPEM, err := os.ReadFile(filepath.Join(pdir, providedKeyFile)) + if err != nil { + return err + } + // tls.X509KeyPair validates that the key matches the cert and parses the + // chain if the cert file contains one. + if chain, err := os.ReadFile(filepath.Join(pdir, providedChainFile)); err == nil { + certPEM = append(certPEM, '\n') + certPEM = append(certPEM, chain...) + } + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return err + } + m.setCurrent(&cert) + return nil +} + +// GetCertificate is the tls.Config.GetCertificate callback. func (m *Manager) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { m.mu.RLock() defer m.mu.RUnlock() @@ -131,14 +238,38 @@ func (m *Manager) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) // TLSConfig returns a server tls.Config wired to this manager. func (m *Manager) TLSConfig() *tls.Config { - return &tls.Config{ - GetCertificate: m.GetCertificate, - MinVersion: tls.VersionTLS12, - } + return &tls.Config{GetCertificate: m.GetCertificate, MinVersion: tls.VersionTLS12} } -// Start runs the renewal loop until ctx is cancelled, checking periodically and -// re-issuing the leaf when it enters the renewal window. +// Info describes the currently served certificate for the status API. +type Info struct { + Mode string `json:"mode"` + Fallback bool `json:"fallback"` + Subject string `json:"subject"` + Issuer string `json:"issuer"` + DNSNames []string `json:"dnsNames"` + NotBefore time.Time `json:"notBefore"` + NotAfter time.Time `json:"notAfter"` +} + +// Info returns metadata about the current serving certificate. +func (m *Manager) CurrentInfo() Info { + m.mu.RLock() + defer m.mu.RUnlock() + info := Info{Mode: m.mode, Fallback: m.fallback} + if m.current != nil && m.current.Leaf != nil { + l := m.current.Leaf + info.Subject = l.Subject.String() + info.Issuer = l.Issuer.String() + info.DNSNames = l.DNSNames + info.NotBefore = l.NotBefore + info.NotAfter = l.NotAfter + } + return info +} + +// Start runs the renewal loop until ctx is cancelled. Only the auto mode renews +// (provided / windows-store certs are managed externally). func (m *Manager) Start(ctx context.Context) { ticker := time.NewTicker(12 * time.Hour) defer ticker.Stop() @@ -148,11 +279,15 @@ func (m *Manager) Start(ctx context.Context) { return case <-ticker.C: m.mu.RLock() - exp := time.Time{} + mode := m.mode + var exp time.Time if m.current != nil && m.current.Leaf != nil { exp = m.current.Leaf.NotAfter } m.mu.RUnlock() + if mode != ModeAuto { + continue + } if exp.IsZero() || m.expiringSoon(exp) { m.logf("Certificate renewal window reached; re-issuing") if err := m.Ensure(); err != nil { @@ -167,12 +302,12 @@ func (m *Manager) expiringSoon(notAfter time.Time) bool { return time.Now().Add(m.renewBefore).After(notAfter) } -func (m *Manager) loadLeaf() *pki.CertKey { - certBytes, err := os.ReadFile(filepath.Join(m.dir, "server.crt")) +func (m *Manager) loadLeaf(dir string) *pki.CertKey { + certBytes, err := os.ReadFile(filepath.Join(dir, "server.crt")) if err != nil { return nil } - keyBytes, err := os.ReadFile(filepath.Join(m.dir, "server.key")) + keyBytes, err := os.ReadFile(filepath.Join(dir, "server.key")) if err != nil { return nil } @@ -187,17 +322,19 @@ func (m *Manager) loadLeaf() *pki.CertKey { return &pki.CertKey{Certificate: cert, DER: cert.Raw, Key: key} } -func (m *Manager) setCurrent(chain *pki.Chain) { - cert := &tls.Certificate{ - Certificate: [][]byte{chain.Leaf.DER, chain.Intermediate.DER}, - PrivateKey: chain.Leaf.Key, - Leaf: chain.Leaf.Certificate, - } +func (m *Manager) setCurrent(cert *tls.Certificate) { m.mu.Lock() m.current = cert m.mu.Unlock() } +func (m *Manager) setMode(mode string, fallback bool) { + m.mu.Lock() + m.mode = mode + m.fallback = fallback + m.mu.Unlock() +} + func (m *Manager) logf(format string, args ...any) { if m.logger != nil { m.logger.Info("TLS", format, args...) @@ -210,22 +347,91 @@ func (m *Manager) logErr(format string, args ...any) { } } -// DefaultSANs derives certificate SANs from the host: the hostname, localhost, -// loopback, and any non-loopback interface IPs. +// DefaultSANs derives certificate SANs from the host: +// - Subject CN: the short hostname. +// - DNS SANs: the short hostname, localhost, the fully-qualified hostname for +// every detected DNS suffix (host.), and the DNS suffixes themselves. +// - IP SANs: loopback plus every non-loopback interface address. +// +// Duplicates are removed. DNS suffixes are read from the OS (Windows registry / +// resolv.conf) via dnsSuffixes. func DefaultSANs() (cn string, dnsNames []string, ips []net.IP) { host, _ := os.Hostname() if host == "" { host = "localhost" } - cn = host - dnsNames = []string{host, "localhost"} - ips = []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback} + short := host + if i := strings.IndexByte(host, '.'); i >= 0 { + short = host[:i] + } + cn = short + + dnsSeen := map[string]bool{} + addDNS := func(n string) { + n = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(n, "."))) + if n != "" && !dnsSeen[n] { + dnsSeen[n] = true + dnsNames = append(dnsNames, n) + } + } + addDNS(short) + addDNS("localhost") + if host != short { // the hostname is already an FQDN + addDNS(host) + } + for _, sfx := range dnsSuffixes() { + addDNS(short + "." + sfx) + addDNS(sfx) + } + + ipSeen := map[string]bool{} + addIP := func(ip net.IP) { + if ip == nil { + return + } + if k := ip.String(); !ipSeen[k] { + ipSeen[k] = true + ips = append(ips, ip) + } + } + addIP(net.IPv4(127, 0, 0, 1)) + addIP(net.IPv6loopback) if addrs, err := net.InterfaceAddrs(); err == nil { for _, a := range addrs { if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - ips = append(ips, ipnet.IP) + addIP(ipnet.IP) } } } return cn, dnsNames, ips } + +// SaveProvided writes a bring-your-own cert (and optional chain) + key under +// /provided after validating that they form a usable key pair. Returns the +// parsed leaf certificate. +func (m *Manager) SaveProvided(certPEM, keyPEM, chainPEM []byte) error { + full := certPEM + if len(chainPEM) > 0 { + full = append(append([]byte{}, certPEM...), '\n') + full = append(full, chainPEM...) + } + if _, err := tls.X509KeyPair(full, keyPEM); err != nil { + return err + } + pdir := filepath.Join(m.dir, providedSubdir) + if err := os.MkdirAll(pdir, 0o700); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(pdir, providedCertFile), certPEM, 0o644); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(pdir, providedKeyFile), keyPEM, 0o600); err != nil { + return err + } + if len(chainPEM) > 0 { + if err := os.WriteFile(filepath.Join(pdir, providedChainFile), chainPEM, 0o644); err != nil { + return err + } + } + return nil +} diff --git a/backend/internal/tlsmgr/manager_test.go b/backend/internal/tlsmgr/manager_test.go index 47f1e08..788be81 100644 --- a/backend/internal/tlsmgr/manager_test.go +++ b/backend/internal/tlsmgr/manager_test.go @@ -1,9 +1,13 @@ package tlsmgr import ( + "crypto/x509" + "encoding/pem" "net" "testing" "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/pki" ) func testManager(t *testing.T, renewBefore time.Duration) *Manager { @@ -14,7 +18,8 @@ func testManager(t *testing.T, renewBefore time.Duration) *Manager { IPs: []net.IP{net.ParseIP("127.0.0.1")}, LeafValidity: time.Hour, RenewBefore: renewBefore, - PFXPassword: "orchestrad", + }, func() Config { + return Config{Mode: ModeAuto, PFXPassword: "orchestrad"} }, nil) } @@ -60,3 +65,51 @@ func TestRenewalReissues(t *testing.T) { t.Error("leaf should have been re-issued in the renewal window") } } + +func TestProvidedMode(t *testing.T) { + dir := t.TempDir() + mode := ModeProvided + m := New(dir, Options{}, func() Config { return Config{Mode: mode, PFXPassword: "x"} }, nil) + + // Produce a bring-your-own leaf + key (PEM) and its CA chain. + chain, err := pki.GenerateChain("byo.local", []string{"byo.local"}, []net.IP{net.ParseIP("127.0.0.1")}, time.Hour) + if err != nil { + t.Fatalf("GenerateChain: %v", err) + } + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: chain.Leaf.DER}) + keyDER, _ := x509.MarshalPKCS8PrivateKey(chain.Leaf.Key) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + chainPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: chain.Intermediate.DER}) + + if err := m.SaveProvided(certPEM, keyPEM, chainPEM); err != nil { + t.Fatalf("SaveProvided: %v", err) + } + if err := m.Ensure(); err != nil { + t.Fatalf("Ensure(provided): %v", err) + } + cert, err := m.GetCertificate(nil) + if err != nil || cert == nil || cert.Leaf == nil { + t.Fatalf("no provided cert served: %v", err) + } + if cert.Leaf.Subject.CommonName != "byo.local" { + t.Errorf("served CN = %q, want byo.local", cert.Leaf.Subject.CommonName) + } + if info := m.CurrentInfo(); info.Mode != ModeProvided || info.Fallback { + t.Errorf("info = %+v, want mode=provided fallback=false", info) + } +} + +func TestProvidedFallsBackToAuto(t *testing.T) { + // Provided mode with no uploaded cert must fall back to a self-managed cert + // so the server still starts. + m := New(t.TempDir(), Options{LeafValidity: time.Hour}, func() Config { + return Config{Mode: ModeProvided, PFXPassword: "x"} + }, nil) + if err := m.Ensure(); err != nil { + t.Fatalf("Ensure: %v", err) + } + info := m.CurrentInfo() + if info.Mode != ModeAuto || !info.Fallback { + t.Errorf("info = %+v, want mode=auto fallback=true", info) + } +} diff --git a/backend/internal/tlsmgr/store.go b/backend/internal/tlsmgr/store.go new file mode 100644 index 0000000..d12ba3e --- /dev/null +++ b/backend/internal/tlsmgr/store.go @@ -0,0 +1,15 @@ +package tlsmgr + +import "time" + +// StoreCert describes a certificate available in the operating system store +// (currently the Windows "My" store) for the UI to list and select. +type StoreCert struct { + Thumbprint string `json:"thumbprint"` + Subject string `json:"subject"` + Issuer string `json:"issuer"` + NotBefore time.Time `json:"notBefore"` + NotAfter time.Time `json:"notAfter"` + HasPrivateKey bool `json:"hasPrivateKey"` + DNSNames []string `json:"dnsNames"` +} diff --git a/backend/internal/tlsmgr/store_other.go b/backend/internal/tlsmgr/store_other.go new file mode 100644 index 0000000..61a3616 --- /dev/null +++ b/backend/internal/tlsmgr/store_other.go @@ -0,0 +1,17 @@ +//go:build !windows + +package tlsmgr + +import "errors" + +// errWindowsOnly is returned when Windows-store operations are attempted on a +// non-Windows platform. +var errWindowsOnly = errors.New("the Windows certificate store is only available on Windows") + +func (m *Manager) ensureWindowsStore(string) error { return errWindowsOnly } + +// ListWindowsCerts is unavailable off Windows. +func ListWindowsCerts() ([]StoreCert, error) { return nil, errWindowsOnly } + +// WindowsStoreSupported reports whether the Windows store is usable here. +func WindowsStoreSupported() bool { return false } diff --git a/backend/internal/tlsmgr/store_windows.go b/backend/internal/tlsmgr/store_windows.go new file mode 100644 index 0000000..36d3b88 --- /dev/null +++ b/backend/internal/tlsmgr/store_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package tlsmgr + +import "errors" + +// TODO(tls): implement real Windows "My" store enumeration and a CNG/CAPI-backed +// crypto.Signer so a selected certificate can be served without exporting its +// private key. Until then ensureWindowsStore reports unavailable (the manager +// falls back to the self-managed certificate) and ListWindowsCerts returns a +// clear error so the UI can surface it. + +var errWindowsStoreTODO = errors.New("windows-store certificate selection is not implemented yet") + +func (m *Manager) ensureWindowsStore(thumbprint string) error { return errWindowsStoreTODO } + +// ListWindowsCerts will enumerate the Windows My store. +func ListWindowsCerts() ([]StoreCert, error) { return nil, errWindowsStoreTODO } + +// WindowsStoreSupported reports whether the Windows store is usable here. +func WindowsStoreSupported() bool { return true } diff --git a/backend/internal/tlsmgr/suffixes_other.go b/backend/internal/tlsmgr/suffixes_other.go new file mode 100644 index 0000000..3a17e10 --- /dev/null +++ b/backend/internal/tlsmgr/suffixes_other.go @@ -0,0 +1,48 @@ +//go:build !windows + +package tlsmgr + +import ( + "bufio" + "os" + "strings" +) + +// dnsSuffixes reads the "domain" and "search" directives from /etc/resolv.conf +// so the certificate covers the host's fully-qualified names. +func dnsSuffixes() []string { + f, err := os.Open("/etc/resolv.conf") + if err != nil { + return nil + } + defer f.Close() + + var out []string + seen := map[string]bool{} + add := func(s string) { + s = strings.ToLower(strings.Trim(strings.TrimSpace(s), ".")) + if s != "" && !seen[s] { + seen[s] = true + out = append(out, s) + } + } + + sc := bufio.NewScanner(f) + for sc.Scan() { + line := strings.TrimSpace(sc.Text()) + if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { + continue + } + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + switch fields[0] { + case "domain", "search": + for _, s := range fields[1:] { + add(s) + } + } + } + return out +} diff --git a/backend/internal/tlsmgr/suffixes_windows.go b/backend/internal/tlsmgr/suffixes_windows.go new file mode 100644 index 0000000..a425ce6 --- /dev/null +++ b/backend/internal/tlsmgr/suffixes_windows.go @@ -0,0 +1,41 @@ +//go:build windows + +package tlsmgr + +import ( + "strings" + + "golang.org/x/sys/windows/registry" +) + +// dnsSuffixes reads the primary DNS domain and the DNS suffix search list from +// the Windows TCP/IP registry, so the certificate covers every name by which +// the host may be addressed. +func dnsSuffixes() []string { + var out []string + seen := map[string]bool{} + add := func(s string) { + s = strings.ToLower(strings.Trim(strings.TrimSpace(s), ".")) + if s != "" && !seen[s] { + seen[s] = true + out = append(out, s) + } + } + + k, err := registry.OpenKey(registry.LOCAL_MACHINE, + `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`, registry.QUERY_VALUE) + if err == nil { + defer k.Close() + for _, name := range []string{"Domain", "NV Domain"} { + if v, _, e := k.GetStringValue(name); e == nil { + add(v) + } + } + if v, _, e := k.GetStringValue("SearchList"); e == nil { + for _, s := range strings.Split(v, ",") { + add(s) + } + } + } + return out +} -- 2.52.0 From 81753395bef6201ee851fbb57f0b28388b9e74cc Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:23:20 -0400 Subject: [PATCH 07/34] fix(tls): include per-adapter DNS suffixes in cert SANs Connection-specific DNS suffixes are set per adapter (and via DHCP) without a domain join, so read Domain/DhcpDomain from each Tcpip Interfaces subkey in addition to the global Domain/NV Domain/SearchList. Verified on a workgroup host: the cert now covers host. and the suffix for every adapter. Co-Authored-By: Claude Opus 4.8 --- backend/internal/tlsmgr/suffixes_windows.go | 36 ++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/backend/internal/tlsmgr/suffixes_windows.go b/backend/internal/tlsmgr/suffixes_windows.go index a425ce6..5bc9855 100644 --- a/backend/internal/tlsmgr/suffixes_windows.go +++ b/backend/internal/tlsmgr/suffixes_windows.go @@ -8,9 +8,10 @@ import ( "golang.org/x/sys/windows/registry" ) -// dnsSuffixes reads the primary DNS domain and the DNS suffix search list from -// the Windows TCP/IP registry, so the certificate covers every name by which -// the host may be addressed. +// dnsSuffixes reads the DNS suffixes by which the host may be addressed from the +// Windows TCP/IP registry: the primary domain and global suffix search list, +// plus every adapter's connection-specific suffix (Domain / DhcpDomain). Per- +// adapter suffixes can be set without joining a domain, so they are included. func dnsSuffixes() []string { var out []string seen := map[string]bool{} @@ -22,11 +23,11 @@ func dnsSuffixes() []string { } } - k, err := registry.OpenKey(registry.LOCAL_MACHINE, - `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters`, registry.QUERY_VALUE) - if err == nil { - defer k.Close() - for _, name := range []string{"Domain", "NV Domain"} { + const base = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters` + + // Global: primary domain + suffix search list. + if k, err := registry.OpenKey(registry.LOCAL_MACHINE, base, registry.QUERY_VALUE); err == nil { + for _, name := range []string{"Domain", "NV Domain", "DhcpDomain"} { if v, _, e := k.GetStringValue(name); e == nil { add(v) } @@ -36,6 +37,25 @@ func dnsSuffixes() []string { add(s) } } + k.Close() + } + + // Per-adapter connection-specific suffixes (statically set or DHCP-assigned). + if ifaces, err := registry.OpenKey(registry.LOCAL_MACHINE, base+`\Interfaces`, registry.ENUMERATE_SUB_KEYS); err == nil { + names, _ := ifaces.ReadSubKeyNames(-1) + ifaces.Close() + for _, n := range names { + ik, err := registry.OpenKey(registry.LOCAL_MACHINE, base+`\Interfaces\`+n, registry.QUERY_VALUE) + if err != nil { + continue + } + for _, name := range []string{"Domain", "DhcpDomain"} { + if v, _, e := ik.GetStringValue(name); e == nil { + add(v) + } + } + ik.Close() + } } return out } -- 2.52.0 From 81468b8af1f0794289cd599b09f1ba29e47692b5 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:30:48 -0400 Subject: [PATCH 08/34] feat(tls): real Windows My-store enumeration and CNG-backed serving Implement ListWindowsCerts (LocalMachine + CurrentUser My stores: thumbprint, subject, issuer, validity, SANs, has-private-key) and windows-store serving: find the cert by thumbprint, acquire its CNG key, and serve TLS via a crypto.Signer that signs through NCryptSignHash (RSA PKCS#1 v1.5 and PSS) so the private key is never exported. Legacy CSP / non-RSA keys report a clear error. Verified end to end: a CNG cert in LocalMachine\My serves a completed HTTPS handshake (openssl confirms the served subject). Co-Authored-By: Claude Opus 4.8 --- backend/internal/tlsmgr/store_windows.go | 286 ++++++++++++++++++++++- 1 file changed, 276 insertions(+), 10 deletions(-) diff --git a/backend/internal/tlsmgr/store_windows.go b/backend/internal/tlsmgr/store_windows.go index 36d3b88..331b579 100644 --- a/backend/internal/tlsmgr/store_windows.go +++ b/backend/internal/tlsmgr/store_windows.go @@ -1,21 +1,287 @@ //go:build windows +// Windows "My" certificate store integration: enumerate certificates for the UI +// to list, and serve a selected certificate (by thumbprint) using its private +// key directly from CNG via a crypto.Signer, so the key never has to be +// exported. RSA keys (PKCS#1 v1.5 and PSS) are supported; legacy CSP keys and +// non-RSA keys are reported as unusable. package tlsmgr -import "errors" +import ( + "crypto" + "crypto/rsa" + "crypto/sha1" + "crypto/tls" + "crypto/x509" + "encoding/hex" + "fmt" + "io" + "strings" + "sync" + "unsafe" -// TODO(tls): implement real Windows "My" store enumeration and a CNG/CAPI-backed -// crypto.Signer so a selected certificate can be served without exporting its -// private key. Until then ensureWindowsStore reports unavailable (the manager -// falls back to the self-managed certificate) and ListWindowsCerts returns a -// clear error so the UI can surface it. + "golang.org/x/sys/windows" +) -var errWindowsStoreTODO = errors.New("windows-store certificate selection is not implemented yet") +const ( + certStoreProvSystemW = 10 + certStoreReadonlyFlag = 0x00008000 + certSystemStoreCurUser = 0x00010000 + certSystemStoreLocalMac = 0x00020000 + certKeyProvInfoPropID = 2 -func (m *Manager) ensureWindowsStore(thumbprint string) error { return errWindowsStoreTODO } + cryptAcquireSilentFlag = 0x00000040 + cryptAcquireOnlyNCryptKeyFlg = 0x00040000 + certNCryptKeySpec = 0xFFFFFFFF -// ListWindowsCerts will enumerate the Windows My store. -func ListWindowsCerts() ([]StoreCert, error) { return nil, errWindowsStoreTODO } + bcryptPadPKCS1 = 0x00000002 + bcryptPadPSS = 0x00000008 +) + +var ( + crypt32 = windows.NewLazySystemDLL("crypt32.dll") + ncrypt = windows.NewLazySystemDLL("ncrypt.dll") + + procCryptAcquireCertificatePrivateKey = crypt32.NewProc("CryptAcquireCertificatePrivateKey") + procCertGetCertificateContextProperty = crypt32.NewProc("CertGetCertificateContextProperty") + procNCryptSignHash = ncrypt.NewProc("NCryptSignHash") + procNCryptFreeObject = ncrypt.NewProc("NCryptFreeObject") +) + +// prevKey holds the NCrypt key handle currently in use so it can be released +// when the manager reloads to a new certificate. +var ( + prevKeyMu sync.Mutex + prevKey windows.Handle +) // WindowsStoreSupported reports whether the Windows store is usable here. func WindowsStoreSupported() bool { return true } + +func openMyStore(location uint32) (windows.Handle, error) { + name, err := windows.UTF16PtrFromString("MY") + if err != nil { + return 0, err + } + return windows.CertOpenStore( + certStoreProvSystemW, 0, 0, + location|certStoreReadonlyFlag, + uintptr(unsafe.Pointer(name)), + ) +} + +func contextDER(ctx *windows.CertContext) []byte { + der := make([]byte, ctx.Length) + copy(der, unsafe.Slice(ctx.EncodedCert, ctx.Length)) + return der +} + +func hasPrivateKey(ctx *windows.CertContext) bool { + var cb uint32 + r, _, _ := procCertGetCertificateContextProperty.Call( + uintptr(unsafe.Pointer(ctx)), + certKeyProvInfoPropID, + 0, + uintptr(unsafe.Pointer(&cb)), + ) + return r != 0 +} + +// ListWindowsCerts enumerates the LocalMachine and CurrentUser "My" stores. +func ListWindowsCerts() ([]StoreCert, error) { + var out []StoreCert + seen := map[string]bool{} + for _, loc := range []uint32{certSystemStoreLocalMac, certSystemStoreCurUser} { + store, err := openMyStore(loc) + if err != nil { + continue + } + var prev *windows.CertContext + for { + ctx, err := windows.CertEnumCertificatesInStore(store, prev) + if ctx == nil || err != nil { + break + } + der := contextDER(ctx) + cert, perr := x509.ParseCertificate(der) + if perr == nil { + sum := sha1.Sum(der) + thumb := strings.ToUpper(hex.EncodeToString(sum[:])) + if !seen[thumb] { + seen[thumb] = true + out = append(out, StoreCert{ + Thumbprint: thumb, + Subject: cert.Subject.String(), + Issuer: cert.Issuer.String(), + NotBefore: cert.NotBefore, + NotAfter: cert.NotAfter, + HasPrivateKey: hasPrivateKey(ctx), + DNSNames: cert.DNSNames, + }) + } + } + prev = ctx + } + windows.CertCloseStore(store, 0) + } + return out, nil +} + +// ensureWindowsStore serves the certificate identified by thumbprint from the +// Windows store using a CNG-backed signer. +func (m *Manager) ensureWindowsStore(thumbprint string) error { + if thumbprint == "" { + return fmt.Errorf("no certificate thumbprint configured") + } + want := strings.ToLower(strings.ReplaceAll(thumbprint, " ", "")) + + for _, loc := range []uint32{certSystemStoreLocalMac, certSystemStoreCurUser} { + store, err := openMyStore(loc) + if err != nil { + continue + } + var prev *windows.CertContext + for { + ctx, err := windows.CertEnumCertificatesInStore(store, prev) + if ctx == nil || err != nil { + break + } + der := contextDER(ctx) + sum := sha1.Sum(der) + if strings.ToLower(hex.EncodeToString(sum[:])) != want { + prev = ctx + continue + } + // Match. Acquire the CNG key (caller-owned) then release the context + // and store; the key handle stays valid on its own. + cert, perr := x509.ParseCertificate(der) + if perr != nil { + windows.CertCloseStore(store, 0) + return fmt.Errorf("parsing store certificate: %w", perr) + } + key, kerr := acquireNCryptKey(ctx) + windows.CertCloseStore(store, 0) + if kerr != nil { + return kerr + } + signer := &ncryptSigner{handle: key, pub: cert.PublicKey} + m.setCurrent(&tls.Certificate{ + Certificate: [][]byte{der}, + PrivateKey: signer, + Leaf: cert, + }) + replacePrevKey(key) + return nil + } + windows.CertCloseStore(store, 0) + } + return fmt.Errorf("certificate %s not found (or without a usable private key) in the Windows My store", thumbprint) +} + +func replacePrevKey(h windows.Handle) { + prevKeyMu.Lock() + old := prevKey + prevKey = h + prevKeyMu.Unlock() + if old != 0 { + procNCryptFreeObject.Call(uintptr(old)) + } +} + +func acquireNCryptKey(ctx *windows.CertContext) (windows.Handle, error) { + var h windows.Handle + var keySpec uint32 + var callerFree int32 + r, _, err := procCryptAcquireCertificatePrivateKey.Call( + uintptr(unsafe.Pointer(ctx)), + cryptAcquireSilentFlag|cryptAcquireOnlyNCryptKeyFlg, + 0, + uintptr(unsafe.Pointer(&h)), + uintptr(unsafe.Pointer(&keySpec)), + uintptr(unsafe.Pointer(&callerFree)), + ) + if r == 0 { + return 0, fmt.Errorf("acquiring private key from store: %v", err) + } + if keySpec != certNCryptKeySpec { + return 0, fmt.Errorf("certificate uses a legacy CSP key, which is not supported (use a CNG/KSP certificate)") + } + return h, nil +} + +// ncryptSigner implements crypto.Signer over a CNG (NCrypt) RSA key handle. +type ncryptSigner struct { + handle windows.Handle + pub crypto.PublicKey +} + +func (s *ncryptSigner) Public() crypto.PublicKey { return s.pub } + +type bcryptPKCS1PaddingInfo struct{ pszAlgID *uint16 } +type bcryptPSSPaddingInfo struct { + pszAlgID *uint16 + cbSalt uint32 +} + +func (s *ncryptSigner) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + if _, ok := s.pub.(*rsa.PublicKey); !ok { + return nil, fmt.Errorf("windows-store signing supports RSA keys only") + } + algID, err := bcryptHashAlg(opts.HashFunc()) + if err != nil { + return nil, err + } + var padPtr unsafe.Pointer + var flags uint32 + if pss, ok := opts.(*rsa.PSSOptions); ok { + salt := pss.SaltLength + if salt == rsa.PSSSaltLengthAuto || salt == rsa.PSSSaltLengthEqualsHash { + salt = opts.HashFunc().Size() + } + info := bcryptPSSPaddingInfo{pszAlgID: algID, cbSalt: uint32(salt)} + padPtr = unsafe.Pointer(&info) + flags = bcryptPadPSS + } else { + info := bcryptPKCS1PaddingInfo{pszAlgID: algID} + padPtr = unsafe.Pointer(&info) + flags = bcryptPadPKCS1 + } + + var cb uint32 + r, _, _ := procNCryptSignHash.Call( + uintptr(s.handle), uintptr(padPtr), + uintptr(unsafe.Pointer(&digest[0])), uintptr(len(digest)), + 0, 0, uintptr(unsafe.Pointer(&cb)), uintptr(flags), + ) + if r != 0 { + return nil, fmt.Errorf("NCryptSignHash (size) failed: 0x%x", r) + } + sig := make([]byte, cb) + r, _, _ = procNCryptSignHash.Call( + uintptr(s.handle), uintptr(padPtr), + uintptr(unsafe.Pointer(&digest[0])), uintptr(len(digest)), + uintptr(unsafe.Pointer(&sig[0])), uintptr(cb), + uintptr(unsafe.Pointer(&cb)), uintptr(flags), + ) + if r != 0 { + return nil, fmt.Errorf("NCryptSignHash failed: 0x%x", r) + } + return sig[:cb], nil +} + +func bcryptHashAlg(h crypto.Hash) (*uint16, error) { + var name string + switch h { + case crypto.SHA256: + name = "SHA256" + case crypto.SHA384: + name = "SHA384" + case crypto.SHA512: + name = "SHA512" + case crypto.SHA1: + name = "SHA1" + default: + return nil, fmt.Errorf("unsupported hash %v for windows-store signing", h) + } + return windows.UTF16PtrFromString(name) +} -- 2.52.0 From 6b4cd325c9e3633bb27e2bf88db838e13bb1776d Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:51:52 -0400 Subject: [PATCH 09/34] feat(config): read installer-recorded listen address/port from the registry On Windows, resolve the listen address/port with precedence env > registry (HKLM\Software\Grace Solutions\OrchestrAD ListenAddress/ListenPort, written by the MSI wizard) > default. No-op off Windows. Co-Authored-By: Claude Opus 4.8 --- backend/internal/config/config.go | 23 +++++++++++++++++++-- backend/internal/config/registry_other.go | 6 ++++++ backend/internal/config/registry_windows.go | 21 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 backend/internal/config/registry_other.go create mode 100644 backend/internal/config/registry_windows.go diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index b2a9907..8db5266 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -85,8 +85,11 @@ func Load() (*Config, error) { DataPath: dataPath, SecretKey: secretKey, Server: ServerConfig{ - Host: getEnv("ORCHESTRAD_HOST", "0.0.0.0"), - Port: getEnvInt("ORCHESTRAD_PORT", 18090), + // Listen address/port precedence: environment variable, then the + // value the Windows installer recorded in the registry, then the + // built-in default. (On non-Windows registrySetting returns "".) + Host: getEnv("ORCHESTRAD_HOST", firstNonEmpty(registrySetting("ListenAddress"), "0.0.0.0")), + Port: getEnvInt("ORCHESTRAD_PORT", atoiOr(registrySetting("ListenPort"), 18090)), TrustedProxies: splitCSV(getEnv("ORCHESTRAD_TRUSTED_PROXIES", "")), }, Database: DatabaseConfig{ @@ -170,3 +173,19 @@ func getEnvInt(key string, defaultVal int) int { } return defaultVal } + +// firstNonEmpty returns v if it is non-empty, otherwise def. +func firstNonEmpty(v, def string) string { + if v != "" { + return v + } + return def +} + +// atoiOr parses v as an int, returning def when v is empty or unparseable. +func atoiOr(v string, def int) int { + if i, err := strconv.Atoi(v); err == nil { + return i + } + return def +} diff --git a/backend/internal/config/registry_other.go b/backend/internal/config/registry_other.go new file mode 100644 index 0000000..a0a0f1c --- /dev/null +++ b/backend/internal/config/registry_other.go @@ -0,0 +1,6 @@ +//go:build !windows + +package config + +// registrySetting is a no-op off Windows. +func registrySetting(string) string { return "" } diff --git a/backend/internal/config/registry_windows.go b/backend/internal/config/registry_windows.go new file mode 100644 index 0000000..3421bfe --- /dev/null +++ b/backend/internal/config/registry_windows.go @@ -0,0 +1,21 @@ +//go:build windows + +package config + +import "golang.org/x/sys/windows/registry" + +// registrySetting reads a value the MSI recorded under +// HKLM\Software\Grace Solutions\OrchestrAD (e.g. ListenAddress, ListenPort). +// Returns "" when the key or value is absent. +func registrySetting(name string) string { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Grace Solutions\OrchestrAD`, registry.QUERY_VALUE) + if err != nil { + return "" + } + defer k.Close() + v, _, err := k.GetStringValue(name) + if err != nil { + return "" + } + return v +} -- 2.52.0 From 0e671ff34cd8e17271ae3b4658d0184075fef8d2 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 11:51:52 -0400 Subject: [PATCH 10/34] feat(installer): wizard with install-dir + network dialogs and finish page Add a WiX UI wizard (WixToolset.UI.wixext): Welcome -> License (GPLv3) -> Install directory -> Network (listen address + port) -> Ready -> Finish. The install dir, listen address, and port are recorded in the registry and the service reads them; the finish page shows https://localhost:/ with the default admin/admin credentials and an optional 'open in browser' box. Schedule InitializeService after WriteRegistryValues so the first start binds the chosen port. CI msi job adds the UI extension. Verified: install to a custom dir + port binds that port (health OK), registry recorded, upgrade + uninstall clean. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/release.yml | 5 +- installer/OrchestrAD.wxs | 151 ++++---- installer/license.rtf | 677 +++++++++++++++++++++++++++++++++++ 3 files changed, 770 insertions(+), 63 deletions(-) create mode 100644 installer/license.rtf diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index cf6652a..f44f8f7 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -385,9 +385,12 @@ jobs: if (-not (Get-Command wix -ErrorAction SilentlyContinue)) { dotnet tool install --global wix --version 5.0.2 } + # The wizard UI (install-dir + network dialogs, finish page) needs the + # WiX UI extension, pinned to match the WiX 5 tool. + wix extension add -g WixToolset.UI.wixext/5.0.2 New-Item -ItemType Directory -Force dist | Out-Null $msi = "dist/OrchestrAD-$($env:VERSION)-x64.msi" - wix build -arch x64 installer/OrchestrAD.wxs ` + wix build -arch x64 -ext WixToolset.UI.wixext installer/OrchestrAD.wxs ` -d Version="$($env:MSI_VERSION)" ` -d BinDir="$PWD/msistage" ` -d IconPath="$PWD/resources/icons/orchestrad.ico" ` diff --git a/installer/OrchestrAD.wxs b/installer/OrchestrAD.wxs index 2c3b3f8..b1c5e82 100644 --- a/installer/OrchestrAD.wxs +++ b/installer/OrchestrAD.wxs @@ -1,27 +1,26 @@ - + - - - + - - - + + + + + - - - + + + @@ -83,35 +74,71 @@ - - + + + - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/license.rtf b/installer/license.rtf new file mode 100644 index 0000000..abb2c18 --- /dev/null +++ b/installer/license.rtf @@ -0,0 +1,677 @@ +{ tf1ansideff0{ onttbl{ 0 Consolas;}} s16 +GNU GENERAL PUBLIC LICENSEpar + Version 3, 29 June 2007par +par + Copyright (C) 2007 Free Software Foundation, Inc. par + Everyone is permitted to copy and distribute verbatim copiespar + of this license document, but changing it is not allowed.par +par + Preamblepar +par + The GNU General Public License is a free, copyleft license forpar +software and other kinds of works.par +par + The licenses for most software and other practical works are designedpar +to take away your freedom to share and change the works. By contrast,par +the GNU General Public License is intended to guarantee your freedom topar +share and change all versions of a program--to make sure it remains freepar +software for all its users. We, the Free Software Foundation, use thepar +GNU General Public License for most of our software; it applies also topar +any other work released this way by its authors. You can apply it topar +your programs, too.par +par + When we speak of free software, we are referring to freedom, notpar +price. Our General Public Licenses are designed to make sure that youpar +have the freedom to distribute copies of free software (and charge forpar +them if you wish), that you receive source code or can get it if youpar +want it, that you can change the software or use pieces of it in newpar +free programs, and that you know you can do these things.par +par + To protect your rights, we need to prevent others from denying youpar +these rights or asking you to surrender the rights. Therefore, you havepar +certain responsibilities if you distribute copies of the software, or ifpar +you modify it: responsibilities to respect the freedom of others.par +par + For example, if you distribute copies of such a program, whetherpar +gratis or for a fee, you must pass on to the recipients the samepar +freedoms that you received. You must make sure that they, too, receivepar +or can get the source code. And you must show them these terms so theypar +know their rights.par +par + Developers that use the GNU GPL protect your rights with two steps:par +(1) assert copyright on the software, and (2) offer you this Licensepar +giving you legal permission to copy, distribute and/or modify it.par +par + For the developers' and authors' protection, the GPL clearly explainspar +that there is no warranty for this free software. For both users' andpar +authors' sake, the GPL requires that modified versions be marked aspar +changed, so that their problems will not be attributed erroneously topar +authors of previous versions.par +par + Some devices are designed to deny users access to install or runpar +modified versions of the software inside them, although the manufacturerpar +can do so. This is fundamentally incompatible with the aim ofpar +protecting users' freedom to change the software. The systematicpar +pattern of such abuse occurs in the area of products for individuals topar +use, which is precisely where it is most unacceptable. Therefore, wepar +have designed this version of the GPL to prohibit the practice for thosepar +products. If such problems arise substantially in other domains, wepar +stand ready to extend this provision to those domains in future versionspar +of the GPL, as needed to protect the freedom of users.par +par + Finally, every program is threatened constantly by software patents.par +States should not allow patents to restrict development and use ofpar +software on general-purpose computers, but in those that do, we wish topar +avoid the special danger that patents applied to a free program couldpar +make it effectively proprietary. To prevent this, the GPL assures thatpar +patents cannot be used to render the program non-free.par +par + The precise terms and conditions for copying, distribution andpar +modification follow.par +par + TERMS AND CONDITIONSpar +par + 0. Definitions.par +par + "This License" refers to version 3 of the GNU General Public License.par +par + "Copyright" also means copyright-like laws that apply to other kinds ofpar +works, such as semiconductor masks.par +par + "The Program" refers to any copyrightable work licensed under thispar +License. Each licensee is addressed as "you". "Licensees" andpar +"recipients" may be individuals or organizations.par +par + To "modify" a work means to copy from or adapt all or part of the workpar +in a fashion requiring copyright permission, other than the making of anpar +exact copy. The resulting work is called a "modified version" of thepar +earlier work or a work "based on" the earlier work.par +par + A "covered work" means either the unmodified Program or a work basedpar +on the Program.par +par + To "propagate" a work means to do anything with it that, withoutpar +permission, would make you directly or secondarily liable forpar +infringement under applicable copyright law, except executing it on apar +computer or modifying a private copy. Propagation includes copying,par +distribution (with or without modification), making available to thepar +public, and in some countries other activities as well.par +par + To "convey" a work means any kind of propagation that enables otherpar +parties to make or receive copies. Mere interaction with a user throughpar +a computer network, with no transfer of a copy, is not conveying.par +par + An interactive user interface displays "Appropriate Legal Notices"par +to the extent that it includes a convenient and prominently visiblepar +feature that (1) displays an appropriate copyright notice, and (2)par +tells the user that there is no warranty for the work (except to thepar +extent that warranties are provided), that licensees may convey thepar +work under this License, and how to view a copy of this License. Ifpar +the interface presents a list of user commands or options, such as apar +menu, a prominent item in the list meets this criterion.par +par + 1. Source Code.par +par + The "source code" for a work means the preferred form of the workpar +for making modifications to it. "Object code" means any non-sourcepar +form of a work.par +par + A "Standard Interface" means an interface that either is an officialpar +standard defined by a recognized standards body, or, in the case ofpar +interfaces specified for a particular programming language, one thatpar +is widely used among developers working in that language.par +par + The "System Libraries" of an executable work include anything, otherpar +than the work as a whole, that (a) is included in the normal form ofpar +packaging a Major Component, but which is not part of that Majorpar +Component, and (b) serves only to enable use of the work with thatpar +Major Component, or to implement a Standard Interface for which anpar +implementation is available to the public in source code form. Apar +"Major Component", in this context, means a major essential componentpar +(kernel, window system, and so on) of the specific operating systempar +(if any) on which the executable work runs, or a compiler used topar +produce the work, or an object code interpreter used to run it.par +par + The "Corresponding Source" for a work in object code form means allpar +the source code needed to generate, install, and (for an executablepar +work) run the object code and to modify the work, including scripts topar +control those activities. However, it does not include the work'spar +System Libraries, or general-purpose tools or generally available freepar +programs which are used unmodified in performing those activities butpar +which are not part of the work. For example, Corresponding Sourcepar +includes interface definition files associated with source files forpar +the work, and the source code for shared libraries and dynamicallypar +linked subprograms that the work is specifically designed to require,par +such as by intimate data communication or control flow between thosepar +subprograms and other parts of the work.par +par + The Corresponding Source need not include anything that userspar +can regenerate automatically from other parts of the Correspondingpar +Source.par +par + The Corresponding Source for a work in source code form is thatpar +same work.par +par + 2. Basic Permissions.par +par + All rights granted under this License are granted for the term ofpar +copyright on the Program, and are irrevocable provided the statedpar +conditions are met. This License explicitly affirms your unlimitedpar +permission to run the unmodified Program. The output from running apar +covered work is covered by this License only if the output, given itspar +content, constitutes a covered work. This License acknowledges yourpar +rights of fair use or other equivalent, as provided by copyright law.par +par + You may make, run and propagate covered works that you do notpar +convey, without conditions so long as your license otherwise remainspar +in force. You may convey covered works to others for the sole purposepar +of having them make modifications exclusively for you, or provide youpar +with facilities for running those works, provided that you comply withpar +the terms of this License in conveying all material for which you dopar +not control copyright. Those thus making or running the covered workspar +for you must do so exclusively on your behalf, under your directionpar +and control, on terms that prohibit them from making any copies ofpar +your copyrighted material outside their relationship with you.par +par + Conveying under any other circumstances is permitted solely underpar +the conditions stated below. Sublicensing is not allowed; section 10par +makes it unnecessary.par +par + 3. Protecting Users' Legal Rights From Anti-Circumvention Law.par +par + No covered work shall be deemed part of an effective technologicalpar +measure under any applicable law fulfilling obligations under articlepar +11 of the WIPO copyright treaty adopted on 20 December 1996, orpar +similar laws prohibiting or restricting circumvention of suchpar +measures.par +par + When you convey a covered work, you waive any legal power to forbidpar +circumvention of technological measures to the extent such circumventionpar +is effected by exercising rights under this License with respect topar +the covered work, and you disclaim any intention to limit operation orpar +modification of the work as a means of enforcing, against the work'spar +users, your or third parties' legal rights to forbid circumvention ofpar +technological measures.par +par + 4. Conveying Verbatim Copies.par +par + You may convey verbatim copies of the Program's source code as youpar +receive it, in any medium, provided that you conspicuously andpar +appropriately publish on each copy an appropriate copyright notice;par +keep intact all notices stating that this License and anypar +non-permissive terms added in accord with section 7 apply to the code;par +keep intact all notices of the absence of any warranty; and give allpar +recipients a copy of this License along with the Program.par +par + You may charge any price or no price for each copy that you convey,par +and you may offer support or warranty protection for a fee.par +par + 5. Conveying Modified Source Versions.par +par + You may convey a work based on the Program, or the modifications topar +produce it from the Program, in the form of source code under thepar +terms of section 4, provided that you also meet all of these conditions:par +par + a) The work must carry prominent notices stating that you modifiedpar + it, and giving a relevant date.par +par + b) The work must carry prominent notices stating that it ispar + released under this License and any conditions added under sectionpar + 7. This requirement modifies the requirement in section 4 topar + "keep intact all notices".par +par + c) You must license the entire work, as a whole, under thispar + License to anyone who comes into possession of a copy. Thispar + License will therefore apply, along with any applicable section 7par + additional terms, to the whole of the work, and all its parts,par + regardless of how they are packaged. This License gives nopar + permission to license the work in any other way, but it does notpar + invalidate such permission if you have separately received it.par +par + d) If the work has interactive user interfaces, each must displaypar + Appropriate Legal Notices; however, if the Program has interactivepar + interfaces that do not display Appropriate Legal Notices, yourpar + work need not make them do so.par +par + A compilation of a covered work with other separate and independentpar +works, which are not by their nature extensions of the covered work,par +and which are not combined with it such as to form a larger program,par +in or on a volume of a storage or distribution medium, is called anpar +"aggregate" if the compilation and its resulting copyright are notpar +used to limit the access or legal rights of the compilation's userspar +beyond what the individual works permit. Inclusion of a covered workpar +in an aggregate does not cause this License to apply to the otherpar +parts of the aggregate.par +par + 6. Conveying Non-Source Forms.par +par + You may convey a covered work in object code form under the termspar +of sections 4 and 5, provided that you also convey thepar +machine-readable Corresponding Source under the terms of this License,par +in one of these ways:par +par + a) Convey the object code in, or embodied in, a physical productpar + (including a physical distribution medium), accompanied by thepar + Corresponding Source fixed on a durable physical mediumpar + customarily used for software interchange.par +par + b) Convey the object code in, or embodied in, a physical productpar + (including a physical distribution medium), accompanied by apar + written offer, valid for at least three years and valid for aspar + long as you offer spare parts or customer support for that productpar + model, to give anyone who possesses the object code either (1) apar + copy of the Corresponding Source for all the software in thepar + product that is covered by this License, on a durable physicalpar + medium customarily used for software interchange, for a price nopar + more than your reasonable cost of physically performing thispar + conveying of source, or (2) access to copy thepar + Corresponding Source from a network server at no charge.par +par + c) Convey individual copies of the object code with a copy of thepar + written offer to provide the Corresponding Source. Thispar + alternative is allowed only occasionally and noncommercially, andpar + only if you received the object code with such an offer, in accordpar + with subsection 6b.par +par + d) Convey the object code by offering access from a designatedpar + place (gratis or for a charge), and offer equivalent access to thepar + Corresponding Source in the same way through the same place at nopar + further charge. You need not require recipients to copy thepar + Corresponding Source along with the object code. If the place topar + copy the object code is a network server, the Corresponding Sourcepar + may be on a different server (operated by you or a third party)par + that supports equivalent copying facilities, provided you maintainpar + clear directions next to the object code saying where to find thepar + Corresponding Source. Regardless of what server hosts thepar + Corresponding Source, you remain obligated to ensure that it ispar + available for as long as needed to satisfy these requirements.par +par + e) Convey the object code using peer-to-peer transmission, providedpar + you inform other peers where the object code and Correspondingpar + Source of the work are being offered to the general public at nopar + charge under subsection 6d.par +par + A separable portion of the object code, whose source code is excludedpar +from the Corresponding Source as a System Library, need not bepar +included in conveying the object code work.par +par + A "User Product" is either (1) a "consumer product", which means anypar +tangible personal property which is normally used for personal, family,par +or household purposes, or (2) anything designed or sold for incorporationpar +into a dwelling. In determining whether a product is a consumer product,par +doubtful cases shall be resolved in favor of coverage. For a particularpar +product received by a particular user, "normally used" refers to apar +typical or common use of that class of product, regardless of the statuspar +of the particular user or of the way in which the particular userpar +actually uses, or expects or is expected to use, the product. A productpar +is a consumer product regardless of whether the product has substantialpar +commercial, industrial or non-consumer uses, unless such uses representpar +the only significant mode of use of the product.par +par + "Installation Information" for a User Product means any methods,par +procedures, authorization keys, or other information required to installpar +and execute modified versions of a covered work in that User Product frompar +a modified version of its Corresponding Source. The information mustpar +suffice to ensure that the continued functioning of the modified objectpar +code is in no case prevented or interfered with solely becausepar +modification has been made.par +par + If you convey an object code work under this section in, or with, orpar +specifically for use in, a User Product, and the conveying occurs aspar +part of a transaction in which the right of possession and use of thepar +User Product is transferred to the recipient in perpetuity or for apar +fixed term (regardless of how the transaction is characterized), thepar +Corresponding Source conveyed under this section must be accompaniedpar +by the Installation Information. But this requirement does not applypar +if neither you nor any third party retains the ability to installpar +modified object code on the User Product (for example, the work haspar +been installed in ROM).par +par + The requirement to provide Installation Information does not include apar +requirement to continue to provide support service, warranty, or updatespar +for a work that has been modified or installed by the recipient, or forpar +the User Product in which it has been modified or installed. Access to apar +network may be denied when the modification itself materially andpar +adversely affects the operation of the network or violates the rules andpar +protocols for communication across the network.par +par + Corresponding Source conveyed, and Installation Information provided,par +in accord with this section must be in a format that is publiclypar +documented (and with an implementation available to the public inpar +source code form), and must require no special password or key forpar +unpacking, reading or copying.par +par + 7. Additional Terms.par +par + "Additional permissions" are terms that supplement the terms of thispar +License by making exceptions from one or more of its conditions.par +Additional permissions that are applicable to the entire Program shallpar +be treated as though they were included in this License, to the extentpar +that they are valid under applicable law. If additional permissionspar +apply only to part of the Program, that part may be used separatelypar +under those permissions, but the entire Program remains governed bypar +this License without regard to the additional permissions.par +par + When you convey a copy of a covered work, you may at your optionpar +remove any additional permissions from that copy, or from any part ofpar +it. (Additional permissions may be written to require their ownpar +removal in certain cases when you modify the work.) You may placepar +additional permissions on material, added by you to a covered work,par +for which you have or can give appropriate copyright permission.par +par + Notwithstanding any other provision of this License, for material youpar +add to a covered work, you may (if authorized by the copyright holders ofpar +that material) supplement the terms of this License with terms:par +par + a) Disclaiming warranty or limiting liability differently from thepar + terms of sections 15 and 16 of this License; orpar +par + b) Requiring preservation of specified reasonable legal notices orpar + author attributions in that material or in the Appropriate Legalpar + Notices displayed by works containing it; orpar +par + c) Prohibiting misrepresentation of the origin of that material, orpar + requiring that modified versions of such material be marked inpar + reasonable ways as different from the original version; orpar +par + d) Limiting the use for publicity purposes of names of licensors orpar + authors of the material; orpar +par + e) Declining to grant rights under trademark law for use of somepar + trade names, trademarks, or service marks; orpar +par + f) Requiring indemnification of licensors and authors of thatpar + material by anyone who conveys the material (or modified versions ofpar + it) with contractual assumptions of liability to the recipient, forpar + any liability that these contractual assumptions directly impose onpar + those licensors and authors.par +par + All other non-permissive additional terms are considered "furtherpar +restrictions" within the meaning of section 10. If the Program as youpar +received it, or any part of it, contains a notice stating that it ispar +governed by this License along with a term that is a furtherpar +restriction, you may remove that term. If a license document containspar +a further restriction but permits relicensing or conveying under thispar +License, you may add to a covered work material governed by the termspar +of that license document, provided that the further restriction doespar +not survive such relicensing or conveying.par +par + If you add terms to a covered work in accord with this section, youpar +must place, in the relevant source files, a statement of thepar +additional terms that apply to those files, or a notice indicatingpar +where to find the applicable terms.par +par + Additional terms, permissive or non-permissive, may be stated in thepar +form of a separately written license, or stated as exceptions;par +the above requirements apply either way.par +par + 8. Termination.par +par + You may not propagate or modify a covered work except as expresslypar +provided under this License. Any attempt otherwise to propagate orpar +modify it is void, and will automatically terminate your rights underpar +this License (including any patent licenses granted under the thirdpar +paragraph of section 11).par +par + However, if you cease all violation of this License, then yourpar +license from a particular copyright holder is reinstated (a)par +provisionally, unless and until the copyright holder explicitly andpar +finally terminates your license, and (b) permanently, if the copyrightpar +holder fails to notify you of the violation by some reasonable meanspar +prior to 60 days after the cessation.par +par + Moreover, your license from a particular copyright holder ispar +reinstated permanently if the copyright holder notifies you of thepar +violation by some reasonable means, this is the first time you havepar +received notice of violation of this License (for any work) from thatpar +copyright holder, and you cure the violation prior to 30 days afterpar +your receipt of the notice.par +par + Termination of your rights under this section does not terminate thepar +licenses of parties who have received copies or rights from you underpar +this License. If your rights have been terminated and not permanentlypar +reinstated, you do not qualify to receive new licenses for the samepar +material under section 10.par +par + 9. Acceptance Not Required for Having Copies.par +par + You are not required to accept this License in order to receive orpar +run a copy of the Program. Ancillary propagation of a covered workpar +occurring solely as a consequence of using peer-to-peer transmissionpar +to receive a copy likewise does not require acceptance. However,par +nothing other than this License grants you permission to propagate orpar +modify any covered work. These actions infringe copyright if you dopar +not accept this License. Therefore, by modifying or propagating apar +covered work, you indicate your acceptance of this License to do so.par +par + 10. Automatic Licensing of Downstream Recipients.par +par + Each time you convey a covered work, the recipient automaticallypar +receives a license from the original licensors, to run, modify andpar +propagate that work, subject to this License. You are not responsiblepar +for enforcing compliance by third parties with this License.par +par + An "entity transaction" is a transaction transferring control of anpar +organization, or substantially all assets of one, or subdividing anpar +organization, or merging organizations. If propagation of a coveredpar +work results from an entity transaction, each party to thatpar +transaction who receives a copy of the work also receives whateverpar +licenses to the work the party's predecessor in interest had or couldpar +give under the previous paragraph, plus a right to possession of thepar +Corresponding Source of the work from the predecessor in interest, ifpar +the predecessor has it or can get it with reasonable efforts.par +par + You may not impose any further restrictions on the exercise of thepar +rights granted or affirmed under this License. For example, you maypar +not impose a license fee, royalty, or other charge for exercise ofpar +rights granted under this License, and you may not initiate litigationpar +(including a cross-claim or counterclaim in a lawsuit) alleging thatpar +any patent claim is infringed by making, using, selling, offering forpar +sale, or importing the Program or any portion of it.par +par + 11. Patents.par +par + A "contributor" is a copyright holder who authorizes use under thispar +License of the Program or a work on which the Program is based. Thepar +work thus licensed is called the contributor's "contributor version".par +par + A contributor's "essential patent claims" are all patent claimspar +owned or controlled by the contributor, whether already acquired orpar +hereafter acquired, that would be infringed by some manner, permittedpar +by this License, of making, using, or selling its contributor version,par +but do not include claims that would be infringed only as apar +consequence of further modification of the contributor version. Forpar +purposes of this definition, "control" includes the right to grantpar +patent sublicenses in a manner consistent with the requirements ofpar +this License.par +par + Each contributor grants you a non-exclusive, worldwide, royalty-freepar +patent license under the contributor's essential patent claims, topar +make, use, sell, offer for sale, import and otherwise run, modify andpar +propagate the contents of its contributor version.par +par + In the following three paragraphs, a "patent license" is any expresspar +agreement or commitment, however denominated, not to enforce a patentpar +(such as an express permission to practice a patent or covenant not topar +sue for patent infringement). To "grant" such a patent license to apar +party means to make such an agreement or commitment not to enforce apar +patent against the party.par +par + If you convey a covered work, knowingly relying on a patent license,par +and the Corresponding Source of the work is not available for anyonepar +to copy, free of charge and under the terms of this License, through apar +publicly available network server or other readily accessible means,par +then you must either (1) cause the Corresponding Source to be sopar +available, or (2) arrange to deprive yourself of the benefit of thepar +patent license for this particular work, or (3) arrange, in a mannerpar +consistent with the requirements of this License, to extend the patentpar +license to downstream recipients. "Knowingly relying" means you havepar +actual knowledge that, but for the patent license, your conveying thepar +covered work in a country, or your recipient's use of the covered workpar +in a country, would infringe one or more identifiable patents in thatpar +country that you have reason to believe are valid.par +par + If, pursuant to or in connection with a single transaction orpar +arrangement, you convey, or propagate by procuring conveyance of, apar +covered work, and grant a patent license to some of the partiespar +receiving the covered work authorizing them to use, propagate, modifypar +or convey a specific copy of the covered work, then the patent licensepar +you grant is automatically extended to all recipients of the coveredpar +work and works based on it.par +par + A patent license is "discriminatory" if it does not include withinpar +the scope of its coverage, prohibits the exercise of, or ispar +conditioned on the non-exercise of one or more of the rights that arepar +specifically granted under this License. You may not convey a coveredpar +work if you are a party to an arrangement with a third party that ispar +in the business of distributing software, under which you make paymentpar +to the third party based on the extent of your activity of conveyingpar +the work, and under which the third party grants, to any of thepar +parties who would receive the covered work from you, a discriminatorypar +patent license (a) in connection with copies of the covered workpar +conveyed by you (or copies made from those copies), or (b) primarilypar +for and in connection with specific products or compilations thatpar +contain the covered work, unless you entered into that arrangement,par +or that patent license was granted, prior to 28 March 2007.par +par + Nothing in this License shall be construed as excluding or limitingpar +any implied license or other defenses to infringement that maypar +otherwise be available to you under applicable patent law.par +par + 12. No Surrender of Others' Freedom.par +par + If conditions are imposed on you (whether by court order, agreement orpar +otherwise) that contradict the conditions of this License, they do notpar +excuse you from the conditions of this License. If you cannot convey apar +covered work so as to satisfy simultaneously your obligations under thispar +License and any other pertinent obligations, then as a consequence you maypar +not convey it at all. For example, if you agree to terms that obligate youpar +to collect a royalty for further conveying from those to whom you conveypar +the Program, the only way you could satisfy both those terms and thispar +License would be to refrain entirely from conveying the Program.par +par + 13. Use with the GNU Affero General Public License.par +par + Notwithstanding any other provision of this License, you havepar +permission to link or combine any covered work with a work licensedpar +under version 3 of the GNU Affero General Public License into a singlepar +combined work, and to convey the resulting work. The terms of thispar +License will continue to apply to the part which is the covered work,par +but the special requirements of the GNU Affero General Public License,par +section 13, concerning interaction through a network will apply to thepar +combination as such.par +par + 14. Revised Versions of this License.par +par + The Free Software Foundation may publish revised and/or new versions ofpar +the GNU General Public License from time to time. Such new versions willpar +be similar in spirit to the present version, but may differ in detail topar +address new problems or concerns.par +par + Each version is given a distinguishing version number. If thepar +Program specifies that a certain numbered version of the GNU Generalpar +Public License "or any later version" applies to it, you have thepar +option of following the terms and conditions either of that numberedpar +version or of any later version published by the Free Softwarepar +Foundation. If the Program does not specify a version number of thepar +GNU General Public License, you may choose any version ever publishedpar +by the Free Software Foundation.par +par + If the Program specifies that a proxy can decide which futurepar +versions of the GNU General Public License can be used, that proxy'spar +public statement of acceptance of a version permanently authorizes youpar +to choose that version for the Program.par +par + Later license versions may give you additional or differentpar +permissions. However, no additional obligations are imposed on anypar +author or copyright holder as a result of your choosing to follow apar +later version.par +par + 15. Disclaimer of Warranty.par +par + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BYpar +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHTpar +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTYpar +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,par +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULARpar +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAMpar +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OFpar +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.par +par + 16. Limitation of Liability.par +par + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITINGpar +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYSpar +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANYpar +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THEpar +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OFpar +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRDpar +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),par +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OFpar +SUCH DAMAGES.par +par + 17. Interpretation of Sections 15 and 16.par +par + If the disclaimer of warranty and limitation of liability providedpar +above cannot be given local legal effect according to their terms,par +reviewing courts shall apply local law that most closely approximatespar +an absolute waiver of all civil liability in connection with thepar +Program, unless a warranty or assumption of liability accompanies apar +copy of the Program in return for a fee.par +par + END OF TERMS AND CONDITIONSpar +par + How to Apply These Terms to Your New Programspar +par + If you develop a new program, and you want it to be of the greatestpar +possible use to the public, the best way to achieve this is to make itpar +free software which everyone can redistribute and change under these terms.par +par + To do so, attach the following notices to the program. It is safestpar +to attach them to the start of each source file to most effectivelypar +state the exclusion of warranty; and each file should have at leastpar +the "copyright" line and a pointer to where the full notice is found.par +par + par + Copyright (C) par +par + This program is free software: you can redistribute it and/or modifypar + it under the terms of the GNU General Public License as published bypar + the Free Software Foundation, either version 3 of the License, orpar + (at your option) any later version.par +par + This program is distributed in the hope that it will be useful,par + but WITHOUT ANY WARRANTY; without even the implied warranty ofpar + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See thepar + GNU General Public License for more details.par +par + You should have received a copy of the GNU General Public Licensepar + along with this program. If not, see .par +par +Also add information on how to contact you by electronic and paper mail.par +par + If the program does terminal interaction, make it output a shortpar +notice like this when it starts in an interactive mode:par +par + Copyright (C) par + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.par + This is free software, and you are welcome to redistribute itpar + under certain conditions; type `show c' for details.par +par +The hypothetical commands `show w' and `show c' should show the appropriatepar +parts of the General Public License. Of course, your program's commandspar +might be different; for a GUI interface, you would use an "about box".par +par + You should also get your employer (if you work as a programmer) or school,par +if any, to sign a "copyright disclaimer" for the program, if necessary.par +For more information on this, and how to apply and follow the GNU GPL, seepar +.par +par + The GNU General Public License does not permit incorporating your programpar +into proprietary programs. If your program is a subroutine library, youpar +may consider it more useful to permit linking proprietary applications withpar +the library. If this is what you want to do, use the GNU Lesser Generalpar +Public License instead of this License. But first, please readpar +.par + +} \ No newline at end of file -- 2.52.0 From 06b1c860d495765189bc05b8663eeea227a147f8 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 12:01:48 -0400 Subject: [PATCH 11/34] feat(auth): OIDC/SSO login with UI-configurable, env-overriding settings Add backend OIDC: /api/v1/auth/oidc/status|login|callback (authorization code + PKCE, state+nonce cookies, ID-token verification via go-oidc) and admin /auth/oidc/config (GET/PUT, validates the issuer via discovery on enable). Config resolves app_settings (UI) > env > default, so SSO can be configured from the UI while env still seeds it. LoginOIDC provisions/links federated users by the stable (issuer, subject) pair, refusing to reuse a username held by a different account. Verified: discovery + real authorize redirect + PKCE cookies; config API with DB-over-env precedence; provisioning unit-tested. Co-Authored-By: Claude Opus 4.8 --- backend/go.mod | 3 + backend/go.sum | 6 + backend/internal/api/oidc_handlers.go | 359 ++++++++++++++++++++++++++ backend/internal/auth/auth.go | 95 ++++++- backend/internal/auth/oidc_test.go | 77 ++++++ backend/internal/repository/users.go | 38 +++ backend/internal/server/server.go | 8 + 7 files changed, 582 insertions(+), 4 deletions(-) create mode 100644 backend/internal/api/oidc_handlers.go create mode 100644 backend/internal/auth/oidc_test.go diff --git a/backend/go.mod b/backend/go.mod index a2c9193..c5e8487 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -3,6 +3,7 @@ module github.com/Grace-Solutions/OrchestrAD go 1.25.0 require ( + github.com/coreos/go-oidc/v3 v3.21.0 github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/cors v1.2.1 github.com/go-ldap/ldap/v3 v3.4.13 @@ -11,6 +12,7 @@ require ( github.com/kardianos/service v1.3.0 github.com/robfig/cron/v3 v3.0.1 golang.org/x/crypto v0.48.0 + golang.org/x/oauth2 v0.36.0 golang.org/x/sys v0.47.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 modernc.org/sqlite v1.58.0 @@ -21,6 +23,7 @@ require ( github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/mattn/go-isatty v0.0.24 // indirect diff --git a/backend/go.sum b/backend/go.sum index eb9322b..52dfed8 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -2,6 +2,8 @@ github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+ github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/coreos/go-oidc/v3 v3.21.0 h1:wZo4Q9Pum8dYEj0eMUPrqR+kvuGkeUplbLpNCkBqoWM= +github.com/coreos/go-oidc/v3 v3.21.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,6 +15,8 @@ github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= +github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ= github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0= github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU= @@ -68,6 +72,8 @@ golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= diff --git a/backend/internal/api/oidc_handlers.go b/backend/internal/api/oidc_handlers.go new file mode 100644 index 0000000..92b89d7 --- /dev/null +++ b/backend/internal/api/oidc_handlers.go @@ -0,0 +1,359 @@ +// Package api - OIDC / SSO authentication and configuration handlers. +// +// OIDC configuration is resolved with app_settings (set via the UI) taking +// precedence over environment variables, then defaults (see +// services.SettingsService.Resolve*). This lets an administrator configure SSO +// entirely from the UI while env vars can still seed a working setup. +package api + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/audit" + "github.com/Grace-Solutions/OrchestrAD/internal/auth" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/coreos/go-oidc/v3/oidc" + "golang.org/x/oauth2" +) + +// OIDCHandler handles SSO login/callback and admin configuration. +type OIDCHandler struct { + authService *auth.Service + settings *services.SettingsService + auditService *audit.Service + logger *logging.Logger +} + +// NewOIDCHandler creates an OIDCHandler. +func NewOIDCHandler(authService *auth.Service, settings *services.SettingsService, auditService *audit.Service, logger *logging.Logger) *OIDCHandler { + return &OIDCHandler{authService: authService, settings: settings, auditService: auditService, logger: logger} +} + +type oidcConfig struct { + Enabled bool + Issuer string + ClientID string + ClientSecret string + RedirectURL string + Scopes string + UsernameClaim string + EmailClaim string + NameClaim string + DefaultRole string +} + +func (h *OIDCHandler) resolve() oidcConfig { + r := h.settings + return oidcConfig{ + Enabled: r.ResolveBool("oidc.enabled", "ORCHESTRAD_OIDC_ENABLED", false), + Issuer: r.ResolveString("oidc.issuer", "ORCHESTRAD_OIDC_ISSUER", ""), + ClientID: r.ResolveString("oidc.client_id", "ORCHESTRAD_OIDC_CLIENT_ID", ""), + ClientSecret: r.ResolveString("oidc.client_secret", "ORCHESTRAD_OIDC_CLIENT_SECRET", ""), + RedirectURL: r.ResolveString("oidc.redirect_url", "ORCHESTRAD_OIDC_REDIRECT_URL", ""), + Scopes: r.ResolveString("oidc.scopes", "ORCHESTRAD_OIDC_SCOPES", "openid profile email"), + UsernameClaim: r.ResolveString("oidc.username_claim", "ORCHESTRAD_OIDC_USERNAME_CLAIM", "preferred_username"), + EmailClaim: r.ResolveString("oidc.email_claim", "ORCHESTRAD_OIDC_EMAIL_CLAIM", "email"), + NameClaim: r.ResolveString("oidc.name_claim", "ORCHESTRAD_OIDC_NAME_CLAIM", "name"), + DefaultRole: r.ResolveString("oidc.default_role", "ORCHESTRAD_OIDC_DEFAULT_ROLE", ""), + } +} + +// redirectURI returns the callback URL: the configured one if set, else derived +// from the request so it works without explicit configuration. It must be +// identical between the login and callback requests. +func (h *OIDCHandler) redirectURI(r *http.Request, cfg oidcConfig) string { + if cfg.RedirectURL != "" { + return cfg.RedirectURL + } + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + if p := r.Header.Get("X-Forwarded-Proto"); p != "" { + scheme = p + } + return fmt.Sprintf("%s://%s/api/v1/auth/oidc/callback", scheme, r.Host) +} + +func (h *OIDCHandler) build(ctx context.Context, cfg oidcConfig, redirectURL string) (*oidc.Provider, oauth2.Config, error) { + provider, err := oidc.NewProvider(ctx, cfg.Issuer) + if err != nil { + return nil, oauth2.Config{}, err + } + oc := oauth2.Config{ + ClientID: cfg.ClientID, + ClientSecret: cfg.ClientSecret, + Endpoint: provider.Endpoint(), + RedirectURL: redirectURL, + Scopes: strings.Fields(cfg.Scopes), + } + return provider, oc, nil +} + +// Status handles GET /api/v1/auth/oidc/status (public) so the login page can +// decide whether to show the SSO button. +func (h *OIDCHandler) Status(w http.ResponseWriter, r *http.Request) { + cfg := h.resolve() + enabled := cfg.Enabled && cfg.Issuer != "" && cfg.ClientID != "" + WriteJSON(w, http.StatusOK, map[string]any{ + "enabled": enabled, + "loginUrl": "/api/v1/auth/oidc/login", + }) +} + +// Login handles GET /api/v1/auth/oidc/login (public): starts the auth-code + +// PKCE flow and redirects to the provider. +func (h *OIDCHandler) Login(w http.ResponseWriter, r *http.Request) { + cfg := h.resolve() + if !cfg.Enabled || cfg.Issuer == "" || cfg.ClientID == "" { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, "SSO is not enabled") + return + } + redirectURL := h.redirectURI(r, cfg) + _, oc, err := h.build(r.Context(), cfg, redirectURL) + if err != nil { + h.logger.Error("OIDC", "provider discovery failed: %v", err) + WriteError(w, http.StatusBadGateway, ErrCodeInternalError, "SSO provider is unreachable") + return + } + + state := randToken() + nonce := randToken() + verifier := oauth2.GenerateVerifier() + secure := r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" + h.setFlowCookie(w, "oidc_state", state, secure) + h.setFlowCookie(w, "oidc_nonce", nonce, secure) + h.setFlowCookie(w, "oidc_verifier", verifier, secure) + + authURL := oc.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(verifier)) + http.Redirect(w, r, authURL, http.StatusFound) +} + +// Callback handles GET /api/v1/auth/oidc/callback (public). +func (h *OIDCHandler) Callback(w http.ResponseWriter, r *http.Request) { + cfg := h.resolve() + if !cfg.Enabled { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, "SSO is not enabled") + return + } + if e := r.URL.Query().Get("error"); e != "" { + h.failRedirect(w, r, e) + return + } + state, _ := r.Cookie("oidc_state") + nonce, _ := r.Cookie("oidc_nonce") + verifier, _ := r.Cookie("oidc_verifier") + if state == nil || nonce == nil || verifier == nil || r.URL.Query().Get("state") != state.Value { + h.failRedirect(w, r, "invalid_state") + return + } + h.clearFlowCookies(w, r.TLS != nil) + + redirectURL := h.redirectURI(r, cfg) + provider, oc, err := h.build(r.Context(), cfg, redirectURL) + if err != nil { + h.failRedirect(w, r, "provider_error") + return + } + oauth2Token, err := oc.Exchange(r.Context(), r.URL.Query().Get("code"), oauth2.VerifierOption(verifier.Value)) + if err != nil { + h.logger.Warn("OIDC", "token exchange failed: %v", err) + h.failRedirect(w, r, "exchange_failed") + return + } + rawID, ok := oauth2Token.Extra("id_token").(string) + if !ok { + h.failRedirect(w, r, "no_id_token") + return + } + idToken, err := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID}).Verify(r.Context(), rawID) + if err != nil { + h.logger.Warn("OIDC", "id_token verification failed: %v", err) + h.failRedirect(w, r, "invalid_id_token") + return + } + if idToken.Nonce != nonce.Value { + h.failRedirect(w, r, "invalid_nonce") + return + } + + var claims map[string]any + _ = idToken.Claims(&claims) + identity := auth.OIDCIdentity{ + ProviderID: cfg.Issuer, + Subject: idToken.Subject, + Username: claimString(claims, cfg.UsernameClaim, idToken.Subject), + Email: claimString(claims, cfg.EmailClaim, ""), + DisplayName: claimString(claims, cfg.NameClaim, ""), + DefaultRole: cfg.DefaultRole, + } + + result, err := h.authService.LoginOIDC(identity) + if err != nil { + h.logger.Warn("OIDC", "provisioning failed for subject %s: %v", idToken.Subject, err) + emitAudit(h.auditService, r, audit.EventLogin, "User", "", "OIDCLogin", false, map[string]any{"subject": idToken.Subject}, err.Error()) + h.failRedirect(w, r, "provisioning_failed") + return + } + emitAudit(h.auditService, r, audit.EventLogin, "User", result.User.ID, "OIDCLogin", true, map[string]any{"username": result.User.Username}, "") + + // Hand the session token to the SPA via the URL fragment (not the query, so + // it is not logged by intermediaries), then let the app store it. + frag := url.Values{} + frag.Set("oidc_token", result.SessionToken) + frag.Set("expires_at", result.ExpiresAt.Format(time.RFC3339)) + http.Redirect(w, r, "/login#"+frag.Encode(), http.StatusFound) +} + +func (h *OIDCHandler) failRedirect(w http.ResponseWriter, r *http.Request, reason string) { + http.Redirect(w, r, "/login#oidc_error="+url.QueryEscape(reason), http.StatusFound) +} + +// --- Admin configuration --- + +type oidcConfigResponse struct { + Enabled bool `json:"enabled"` + Issuer string `json:"issuer"` + ClientID string `json:"clientId"` + ClientSecret string `json:"clientSecret"` // redacted + RedirectURL string `json:"redirectUrl"` + Scopes string `json:"scopes"` + UsernameClaim string `json:"usernameClaim"` + EmailClaim string `json:"emailClaim"` + NameClaim string `json:"nameClaim"` + DefaultRole string `json:"defaultRole"` +} + +// GetConfig handles GET /api/v1/auth/oidc/config (admin). +func (h *OIDCHandler) GetConfig(w http.ResponseWriter, r *http.Request) { + c := h.resolve() + secret := "" + if c.ClientSecret != "" { + secret = "********" + } + WriteJSON(w, http.StatusOK, oidcConfigResponse{ + Enabled: c.Enabled, Issuer: c.Issuer, ClientID: c.ClientID, ClientSecret: secret, + RedirectURL: c.RedirectURL, Scopes: c.Scopes, UsernameClaim: c.UsernameClaim, + EmailClaim: c.EmailClaim, NameClaim: c.NameClaim, DefaultRole: c.DefaultRole, + }) +} + +type oidcConfigRequest struct { + Enabled *bool `json:"enabled"` + Issuer *string `json:"issuer"` + ClientID *string `json:"clientId"` + ClientSecret *string `json:"clientSecret"` + RedirectURL *string `json:"redirectUrl"` + Scopes *string `json:"scopes"` + UsernameClaim *string `json:"usernameClaim"` + EmailClaim *string `json:"emailClaim"` + NameClaim *string `json:"nameClaim"` + DefaultRole *string `json:"defaultRole"` +} + +// PutConfig handles PUT /api/v1/auth/oidc/config (admin). Only provided fields +// are changed. When enabling, the issuer is validated via OIDC discovery. +func (h *OIDCHandler) PutConfig(w http.ResponseWriter, r *http.Request) { + var req oidcConfigRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + set := func(key, val string, sensitive bool) error { + return h.settings.Upsert(services.Setting{Key: key, Value: val, ValueType: "string", IsSensitive: sensitive}) + } + if req.Issuer != nil { + _ = set("oidc.issuer", strings.TrimSpace(*req.Issuer), false) + } + if req.ClientID != nil { + _ = set("oidc.client_id", strings.TrimSpace(*req.ClientID), false) + } + if req.ClientSecret != nil && *req.ClientSecret != "" && *req.ClientSecret != "********" { + _ = set("oidc.client_secret", *req.ClientSecret, true) + } + if req.RedirectURL != nil { + _ = set("oidc.redirect_url", strings.TrimSpace(*req.RedirectURL), false) + } + if req.Scopes != nil { + _ = set("oidc.scopes", strings.TrimSpace(*req.Scopes), false) + } + if req.UsernameClaim != nil { + _ = set("oidc.username_claim", strings.TrimSpace(*req.UsernameClaim), false) + } + if req.EmailClaim != nil { + _ = set("oidc.email_claim", strings.TrimSpace(*req.EmailClaim), false) + } + if req.NameClaim != nil { + _ = set("oidc.name_claim", strings.TrimSpace(*req.NameClaim), false) + } + if req.DefaultRole != nil { + _ = set("oidc.default_role", strings.TrimSpace(*req.DefaultRole), false) + } + + // Validate discovery before allowing enable. + cfg := h.resolve() + if req.Enabled != nil && *req.Enabled { + if cfg.Issuer == "" || cfg.ClientID == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "issuer and clientId are required to enable SSO") + return + } + if _, err := oidc.NewProvider(r.Context(), cfg.Issuer); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "OIDC discovery failed for the issuer: "+err.Error()) + return + } + } + if req.Enabled != nil { + _ = h.settings.Upsert(services.Setting{Key: "oidc.enabled", Value: boolStr(*req.Enabled), ValueType: "bool"}) + } + emitAudit(h.auditService, r, audit.EventConfigChange, "OIDC", "config", "Update", true, map[string]any{"enabled": req.Enabled != nil && *req.Enabled}, "") + + h.GetConfig(w, r) +} + +// --- helpers --- + +func (h *OIDCHandler) setFlowCookie(w http.ResponseWriter, name, value string, secure bool) { + http.SetCookie(w, &http.Cookie{ + Name: name, Value: value, Path: "/api/v1/auth/oidc", + HttpOnly: true, Secure: secure, SameSite: http.SameSiteLaxMode, + MaxAge: 600, + }) +} + +func (h *OIDCHandler) clearFlowCookies(w http.ResponseWriter, secure bool) { + for _, n := range []string{"oidc_state", "oidc_nonce", "oidc_verifier"} { + http.SetCookie(w, &http.Cookie{Name: n, Value: "", Path: "/api/v1/auth/oidc", HttpOnly: true, Secure: secure, MaxAge: -1}) + } +} + +func randToken() string { + b := make([]byte, 32) + _, _ = rand.Read(b) + return base64.RawURLEncoding.EncodeToString(b) +} + +func claimString(claims map[string]any, key, fallback string) string { + if key != "" { + if v, ok := claims[key]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + } + return fallback +} + +func boolStr(b bool) string { + if b { + return "true" + } + return "false" +} diff --git a/backend/internal/auth/auth.go b/backend/internal/auth/auth.go index c226ec1..4db5d7c 100644 --- a/backend/internal/auth/auth.go +++ b/backend/internal/auth/auth.go @@ -20,6 +20,7 @@ var ( ErrSessionRevoked = errors.New("session has been revoked") ErrPasswordTooShort = errors.New("new password is too short") ErrPasswordUnchanged = errors.New("new password must differ from the current password") + ErrUsernameTaken = errors.New("a different account already uses this username") ) // MinPasswordLength is the enforced minimum length for passwords set through @@ -72,7 +73,13 @@ func (s *Service) Login(username, password string) (*LoginResult, error) { return nil, ErrInvalidCredentials } - // Create session + return s.createSession(user) +} + +// createSession issues a new session for an already-authenticated user, updates +// last-login, loads roles, and returns the session token. Shared by local and +// OIDC login. +func (s *Service) createSession(user *models.User) (*LoginResult, error) { sessionToken, err := crypto.GenerateRandomKey(32) if err != nil { return nil, err @@ -91,10 +98,7 @@ func (s *Service) Login(username, password string) (*LoginResult, error) { return nil, err } - // Update last login s.userRepo.UpdateLastLogin(user.ID) - - // Load user roles user.Roles, _ = s.GetUserRoles(user.ID) return &LoginResult{ @@ -104,6 +108,89 @@ func (s *Service) Login(username, password string) (*LoginResult, error) { }, nil } +// OIDCIdentity carries the claims extracted from a validated ID token. +type OIDCIdentity struct { + ProviderID string + Subject string + Username string + Email string + DisplayName string + DefaultRole string // role name to grant a newly provisioned user (optional) +} + +// LoginOIDC signs in a federated user. It links by the stable (provider, +// subject) pair; a first-time subject provisions a new active OIDC user (no +// local password). To avoid silent account takeover it refuses to reuse a +// username already held by a different (e.g. local) account. +func (s *Service) LoginOIDC(id OIDCIdentity) (*LoginResult, error) { + if id.Subject == "" || id.Username == "" { + return nil, ErrInvalidCredentials + } + + user, err := s.userRepo.GetByOIDCSubject(id.ProviderID, id.Subject) + if err != nil { + return nil, err + } + if user != nil { + if !user.IsActive { + return nil, ErrUserDisabled + } + return s.createSession(user) + } + + // First login for this subject: the username must not already belong to a + // different account. + if existing, err := s.userRepo.GetByUsername(id.Username); err != nil { + return nil, err + } else if existing != nil { + return nil, ErrUsernameTaken + } + + newUser := &models.User{ + Username: id.Username, + Email: strOrNil(id.Email), + DisplayName: strOrNil(id.DisplayName), + IsActive: true, + IsOIDCUser: true, + OIDCProviderID: strOrNil(id.ProviderID), + OIDCSubject: strOrNil(id.Subject), + } + if err := s.userRepo.Create(newUser); err != nil { + return nil, err + } + if id.DefaultRole != "" { + if err := s.assignRoleByName(newUser.ID, id.DefaultRole); err != nil { + // Non-fatal: the user is created but without the default role. + _ = err + } + } + return s.createSession(newUser) +} + +// assignRoleByName grants the named role to a user, if the role exists. +func (s *Service) assignRoleByName(userID, roleName string) error { + var roleID string + err := s.db.QueryRow(`SELECT id FROM roles WHERE name = ?`, roleName).Scan(&roleID) + if err == sql.ErrNoRows { + return nil + } + if err != nil { + return err + } + _, err = s.db.Exec(` + INSERT OR IGNORE INTO user_roles (user_id, role_id, created_utc) + VALUES (?, ?, ?) + `, userID, roleID, time.Now().UTC().Format(time.RFC3339)) + return err +} + +func strOrNil(s string) *string { + if s == "" { + return nil + } + return &s +} + // ValidateSession validates a session token and returns the user func (s *Service) ValidateSession(token string) (*models.User, error) { tokenHash := crypto.HashAPIKey(token) diff --git a/backend/internal/auth/oidc_test.go b/backend/internal/auth/oidc_test.go new file mode 100644 index 0000000..951465f --- /dev/null +++ b/backend/internal/auth/oidc_test.go @@ -0,0 +1,77 @@ +package auth + +import ( + "database/sql" + "path/filepath" + "testing" + + "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/db" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/repository" +) + +func migratedDB(t *testing.T) *sql.DB { + t.Helper() + database, err := db.New(config.DatabaseConfig{ + Path: filepath.Join(t.TempDir(), "oidc_test.db"), + MaxOpenConns: 1, + MaxIdleConns: 1, + WALMode: true, + ForeignKeys: true, + }, logging.Default()) + if err != nil { + t.Fatalf("db.New: %v", err) + } + t.Cleanup(func() { database.Close() }) + if err := database.Migrate(); err != nil { + t.Fatalf("migrate: %v", err) + } + return database.Conn() +} + +func TestLoginOIDCProvisionsAndLinksBySubject(t *testing.T) { + svc := NewService(migratedDB(t)) + id := OIDCIdentity{ProviderID: "https://idp", Subject: "sub-1", Username: "alice", Email: "alice@example.com", DisplayName: "Alice"} + + first, err := svc.LoginOIDC(id) + if err != nil { + t.Fatalf("first LoginOIDC: %v", err) + } + if first.User.Username != "alice" || !first.User.IsOIDCUser || first.User.PasswordHash != nil { + t.Fatalf("provisioned user looks wrong: %+v", first.User) + } + if first.SessionToken == "" { + t.Fatal("no session token issued") + } + + // Same subject, even with a changed username, resolves to the same user. + id2 := id + id2.Username = "alice-renamed" + second, err := svc.LoginOIDC(id2) + if err != nil { + t.Fatalf("second LoginOIDC: %v", err) + } + if second.User.ID != first.User.ID { + t.Errorf("subject not linked: got user %s, want %s", second.User.ID, first.User.ID) + } +} + +func TestLoginOIDCRefusesUsernameCollision(t *testing.T) { + conn := migratedDB(t) + svc := NewService(conn) + + // A pre-existing local account owns the username. + hash := "x" + if err := repository.NewUserRepository(conn).Create(&models.User{ + Username: "bob", PasswordHash: &hash, IsActive: true, + }); err != nil { + t.Fatalf("seed local user: %v", err) + } + + _, err := svc.LoginOIDC(OIDCIdentity{ProviderID: "https://idp", Subject: "sub-bob", Username: "bob"}) + if err != ErrUsernameTaken { + t.Fatalf("expected ErrUsernameTaken, got %v", err) + } +} diff --git a/backend/internal/repository/users.go b/backend/internal/repository/users.go index 457c070..0436909 100644 --- a/backend/internal/repository/users.go +++ b/backend/internal/repository/users.go @@ -116,6 +116,44 @@ func (r *UserRepository) GetByUsername(username string) (*models.User, error) { return user, nil } +// GetByOIDCSubject retrieves a non-deleted user by their OIDC provider id and +// subject, or nil when none matches. This is the stable identity link for +// federated (SSO) users. +func (r *UserRepository) GetByOIDCSubject(providerID, subject string) (*models.User, error) { + user := &models.User{} + var isActive, isOIDCUser, passwordResetRequired int + var lastLogin, deleted sql.NullString + var createdStr, updatedStr string + + err := r.db.QueryRow(` + SELECT id, username, email, password_hash, display_name, + is_active, is_oidc_user, oidc_provider_id, oidc_subject, + password_reset_required, + last_login_utc, created_utc, updated_utc, deleted_utc + FROM users WHERE oidc_provider_id = ? AND oidc_subject = ? AND deleted_utc IS NULL + `, providerID, subject).Scan( + &user.ID, &user.Username, &user.Email, &user.PasswordHash, &user.DisplayName, + &isActive, &isOIDCUser, &user.OIDCProviderID, &user.OIDCSubject, + &passwordResetRequired, + &lastLogin, &createdStr, &updatedStr, &deleted, + ) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + + user.IsActive = intToBool(isActive) + user.IsOIDCUser = intToBool(isOIDCUser) + user.PasswordResetRequired = intToBool(passwordResetRequired) + user.LastLoginUTC = parseNullTime(lastLogin) + user.CreatedUTC = parseTimeOrZero(createdStr) + user.UpdatedUTC = parseTimeOrZero(updatedStr) + user.DeletedUTC = parseNullTime(deleted) + return user, nil +} + // List returns a page of non-deleted users ordered by username, along with // the total count of matching rows for pagination metadata. func (r *UserRepository) List(offset, limit int) ([]models.User, int, error) { diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 7b80185..69eb69f 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -135,12 +135,20 @@ func (s *Server) setupRoutes() { // Auth endpoints. Login / logout / csrf are public by design; /me // requires a valid session so clients can resolve the current user. authHandler := api.NewAuthHandler(s.deps.AuthService, s.deps.AuditService, s.logger) + oidcHandler := api.NewOIDCHandler(s.deps.AuthService, s.deps.SettingsService, s.deps.AuditService, s.logger) r.Route("/auth", func(r chi.Router) { r.Post("/login", authHandler.Login) r.Post("/logout", authHandler.Logout) r.With(api.AuthMiddleware(s.deps.AuthService)).Get("/me", authHandler.Me) r.With(api.AuthMiddleware(s.deps.AuthService)).Post("/change-password", authHandler.ChangePassword) r.Get("/csrf", authHandler.CSRF) + + // SSO (OIDC). status/login/callback are public; config is admin-only. + r.Get("/oidc/status", oidcHandler.Status) + r.Get("/oidc/login", oidcHandler.Login) + r.Get("/oidc/callback", oidcHandler.Callback) + r.With(api.AuthMiddleware(s.deps.AuthService)).Get("/oidc/config", oidcHandler.GetConfig) + r.With(api.AuthMiddleware(s.deps.AuthService)).Put("/oidc/config", oidcHandler.PutConfig) }) // Everything below this group requires a valid session token. API -- 2.52.0 From b0decfe325ad2d9170c614a2b698b65b73665fdc Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 12:09:45 -0400 Subject: [PATCH 12/34] test(ad): opt-in integration tests exercising rules against a real AD Add build-tagged (adtest) + env-gated integration tests that run the rule engine against a live Active Directory over plain LDAP: create a unique test OU with users/groups, execute a rule, and assert the AD outcome, then tree-delete the OU on cleanup. Covers add-to-group-by-condition (matching users added, others not), create-missing-group, and move-to-OU. Excluded from normal builds and CI; run with -tags adtest and ORCHESTRAD_AD_TEST_* env. Verified green against the test domain; no objects left behind. Co-Authored-By: Claude Opus 4.8 --- backend/internal/adtest/ad_test.go | 324 +++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 backend/internal/adtest/ad_test.go diff --git a/backend/internal/adtest/ad_test.go b/backend/internal/adtest/ad_test.go new file mode 100644 index 0000000..d77b9f5 --- /dev/null +++ b/backend/internal/adtest/ad_test.go @@ -0,0 +1,324 @@ +//go:build adtest + +// Package adtest holds opt-in integration tests that exercise the rule engine +// against a real Active Directory. They are excluded from normal builds by the +// `adtest` build tag and additionally skip unless ORCHESTRAD_AD_TEST_HOST is set. +// +// Run against a test AD (plain LDAP 389), e.g.: +// +// ORCHESTRAD_AD_TEST_HOST=172.16.32.65 \ +// ORCHESTRAD_AD_TEST_BIND='OrchestrAD@gracesolutions.lab' \ +// ORCHESTRAD_AD_TEST_PASSWORD='OrchestrAD' \ +// ORCHESTRAD_AD_TEST_BASEDN='DC=gracesolutions,DC=lab' \ +// go test -tags adtest ./internal/adtest/ -v +// +// Each test creates a uniquely named OU under the base DN, does its work there, +// and tree-deletes it on cleanup, so nothing is left behind. +package adtest + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + "testing" + "time" + + appldap "github.com/Grace-Solutions/OrchestrAD/internal/directory/ldap" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/rules/engine" + "github.com/Grace-Solutions/OrchestrAD/internal/types" + ldapv3 "github.com/go-ldap/ldap/v3" +) + +// treeDeleteOID is the AD control for recursive (subtree) delete. +const treeDeleteOID = "1.2.840.113556.1.4.805" + +// securityGlobalGroupType is the groupType flag for a global security group. +const securityGlobalGroupType = "-2147483646" + +type adEnv struct { + host, bind, password, baseDN string + port int +} + +func loadEnv(t *testing.T) adEnv { + t.Helper() + host := os.Getenv("ORCHESTRAD_AD_TEST_HOST") + if host == "" { + t.Skip("ORCHESTRAD_AD_TEST_HOST not set; skipping AD integration tests") + } + port := 389 + if p := os.Getenv("ORCHESTRAD_AD_TEST_PORT"); p != "" { + port, _ = strconv.Atoi(p) + } + return adEnv{ + host: host, + port: port, + bind: os.Getenv("ORCHESTRAD_AD_TEST_BIND"), + password: os.Getenv("ORCHESTRAD_AD_TEST_PASSWORD"), + baseDN: os.Getenv("ORCHESTRAD_AD_TEST_BASEDN"), + } +} + +// rawConn opens a plain go-ldap connection for fixture setup/teardown/verify. +func (e adEnv) rawConn(t *testing.T) *ldapv3.Conn { + t.Helper() + conn, err := ldapv3.DialURL(fmt.Sprintf("ldap://%s:%d", e.host, e.port)) + if err != nil { + t.Fatalf("dial: %v", err) + } + if err := conn.Bind(e.bind, e.password); err != nil { + conn.Close() + t.Fatalf("bind: %v", err) + } + t.Cleanup(func() { conn.Close() }) + return conn +} + +// appClient returns the application's LDAP client (used by the engine). +func (e adEnv) appClient(t *testing.T) *appldap.Client { + t.Helper() + c := appldap.NewClient(&appldap.Config{ + Hosts: []string{e.host}, + Port: e.port, + UseTLS: false, + BindDN: e.bind, + BindPassword: e.password, + RootDN: e.baseDN, + Timeout: 15 * time.Second, + }) + if err := c.Connect(); err != nil { + t.Fatalf("app client connect: %v", err) + } + if err := c.Bind(); err != nil { + t.Fatalf("app client bind: %v", err) + } + t.Cleanup(c.Close) + return c +} + +// makeOU creates a unique test OU and registers a recursive-delete cleanup. +func (e adEnv) makeOU(t *testing.T, conn *ldapv3.Conn, label string) string { + t.Helper() + name := fmt.Sprintf("OrchestrAD-%s-%d", label, time.Now().UnixNano()) + dn := fmt.Sprintf("OU=%s,%s", name, e.baseDN) + add := ldapv3.NewAddRequest(dn, nil) + add.Attribute("objectClass", []string{"organizationalUnit"}) + if err := conn.Add(add); err != nil { + t.Fatalf("create OU %s: %v", dn, err) + } + t.Cleanup(func() { + del := ldapv3.NewDelRequest(dn, []ldapv3.Control{ldapv3.NewControlString(treeDeleteOID, true, "")}) + if err := conn.Del(del); err != nil { + t.Logf("cleanup tree-delete %s: %v", dn, err) + } + }) + return dn +} + +func addUser(t *testing.T, conn *ldapv3.Conn, dn string, attrs map[string][]string) { + t.Helper() + add := ldapv3.NewAddRequest(dn, nil) + add.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "user"}) + // Disabled account (no password set; plain LDAP cannot set unicodePwd). + add.Attribute("userAccountControl", []string{"514"}) + for k, v := range attrs { + add.Attribute(k, v) + } + if err := conn.Add(add); err != nil { + t.Fatalf("create user %s: %v", dn, err) + } +} + +func addGroup(t *testing.T, conn *ldapv3.Conn, dn, sam string) { + t.Helper() + add := ldapv3.NewAddRequest(dn, nil) + add.Attribute("objectClass", []string{"group"}) + add.Attribute("sAMAccountName", []string{sam}) + add.Attribute("groupType", []string{securityGlobalGroupType}) + if err := conn.Add(add); err != nil { + t.Fatalf("create group %s: %v", dn, err) + } +} + +func groupMembers(t *testing.T, conn *ldapv3.Conn, groupDN string) []string { + t.Helper() + res, err := conn.Search(ldapv3.NewSearchRequest( + groupDN, ldapv3.ScopeBaseObject, ldapv3.NeverDerefAliases, 0, 0, false, + "(objectClass=*)", []string{"member"}, nil, + )) + if err != nil { + t.Fatalf("read group members %s: %v", groupDN, err) + } + if len(res.Entries) == 0 { + return nil + } + return res.Entries[0].GetAttributeValues("member") +} + +func exists(t *testing.T, conn *ldapv3.Conn, dn string) bool { + t.Helper() + res, err := conn.Search(ldapv3.NewSearchRequest( + dn, ldapv3.ScopeBaseObject, ldapv3.NeverDerefAliases, 0, 0, false, + "(objectClass=*)", []string{"distinguishedName"}, nil, + )) + if err != nil { + return false + } + return len(res.Entries) == 1 +} + +func containsDN(list []string, dn string) bool { + for _, e := range list { + if strings.EqualFold(e, dn) { + return true + } + } + return false +} + +func conn(baseDN string) *models.ADConnection { + return &models.ADConnection{RootDN: baseDN, DefaultSearchScope: "Subtree"} +} + +func actionJSON(t *testing.T, cfg models.ActionConfig) string { + t.Helper() + b, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("marshal action config: %v", err) + } + return string(b) +} + +// ruleUsersWhere builds a User rule with one equals-condition and the given actions. +func ruleUsersWhere(baseDN, attr, value string, actions ...models.RuleAction) *models.Rule { + v := value + return &models.Rule{ + ID: "test-rule", + Name: "Integration Test Rule", + ObjectType: string(types.ObjectTypeUser), + BaseDNOverride: &baseDN, + GroupJoinOperator: string(types.JoinOperatorAND), + ExecutionMode: string(types.ExecutionModeApply), + ConditionGroups: []models.RuleConditionGroup{{ + IsEnabled: true, + JoinOperator: string(types.JoinOperatorAND), + Conditions: []models.RuleCondition{{ + IsEnabled: true, + AttributeName: attr, + Operator: string(types.OperatorEquals), + ComparisonValue: &v, + }}, + }}, + Actions: actions, + } +} + +func newEngine() *engine.Engine { + return engine.NewEngine(logging.Default()) +} + +// TestAddUsersToGroupByCondition: users with department=Engineering are added to +// a target group; a Sales user is not. +func TestAddUsersToGroupByCondition(t *testing.T) { + e := loadEnv(t) + raw := e.rawConn(t) + ou := e.makeOU(t, raw, "addgroup") + + alice := "CN=oad-alice," + ou + bob := "CN=oad-bob," + ou + carol := "CN=oad-carol," + ou + addUser(t, raw, alice, map[string][]string{"sAMAccountName": {"oad-alice"}, "department": {"Engineering"}}) + addUser(t, raw, bob, map[string][]string{"sAMAccountName": {"oad-bob"}, "department": {"Sales"}}) + addUser(t, raw, carol, map[string][]string{"sAMAccountName": {"oad-carol"}, "department": {"Engineering"}}) + + group := "CN=oad-engineers," + ou + addGroup(t, raw, group, "oad-engineers") + + rule := ruleUsersWhere(ou, "department", "Engineering", models.RuleAction{ + ID: "a1", ActionType: string(types.ActionAddToGroup), IsEnabled: true, + ConfigurationJSON: actionJSON(t, models.ActionConfig{TargetGroupDN: group}), + }) + + res := newEngine().Execute(context.Background(), rule, conn(e.baseDN), e.appClient(t)) + if res.Status != types.RunStatusCompleted { + t.Fatalf("run status = %s, want Completed; errors: %+v", res.Status, res.Errors) + } + if res.ObjectsMatched != 2 { + t.Errorf("ObjectsMatched = %d, want 2 (alice, carol)", res.ObjectsMatched) + } + + members := groupMembers(t, raw, group) + if !containsDN(members, alice) || !containsDN(members, carol) { + t.Errorf("group missing expected members; got %v", members) + } + if containsDN(members, bob) { + t.Errorf("Sales user bob should not be a member; got %v", members) + } +} + +// TestAddToGroupCreatesMissingGroup: AddToGroup with createIfMissing provisions +// the group before adding the member. +func TestAddToGroupCreatesMissingGroup(t *testing.T) { + e := loadEnv(t) + raw := e.rawConn(t) + ou := e.makeOU(t, raw, "creategroup") + + dave := "CN=oad-dave," + ou + addUser(t, raw, dave, map[string][]string{"sAMAccountName": {"oad-dave"}, "department": {"IT"}}) + + group := "CN=oad-it-created," + ou // does not exist yet + if exists(t, raw, group) { + t.Fatalf("precondition: group should not exist") + } + + rule := ruleUsersWhere(ou, "department", "IT", models.RuleAction{ + ID: "a1", ActionType: string(types.ActionAddToGroup), IsEnabled: true, + ConfigurationJSON: actionJSON(t, models.ActionConfig{ + TargetGroupDN: group, CreateIfMissing: true, GroupType: "Security", GroupScope: "Global", + }), + }) + + res := newEngine().Execute(context.Background(), rule, conn(e.baseDN), e.appClient(t)) + if res.Status != types.RunStatusCompleted { + t.Fatalf("run status = %s, want Completed; %+v", res.Status, res.Errors) + } + if !exists(t, raw, group) { + t.Fatalf("group was not created") + } + if members := groupMembers(t, raw, group); !containsDN(members, dave) { + t.Errorf("created group missing member dave; got %v", members) + } +} + +// TestMoveToOU: a matching user is moved into a target OU (created on demand). +func TestMoveToOU(t *testing.T) { + e := loadEnv(t) + raw := e.rawConn(t) + ou := e.makeOU(t, raw, "move") + + erin := "CN=oad-erin," + ou + addUser(t, raw, erin, map[string][]string{"sAMAccountName": {"oad-erin"}, "department": {"Relocate"}}) + + targetOU := "OU=oad-moved," + ou + rule := ruleUsersWhere(ou, "department", "Relocate", models.RuleAction{ + ID: "a1", ActionType: string(types.ActionMoveToOu), IsEnabled: true, + ConfigurationJSON: actionJSON(t, models.ActionConfig{TargetOU: targetOU, CreateOUIfMissing: true}), + }) + + res := newEngine().Execute(context.Background(), rule, conn(e.baseDN), e.appClient(t)) + if res.Status != types.RunStatusCompleted { + t.Fatalf("run status = %s, want Completed; %+v", res.Status, res.Errors) + } + movedDN := "CN=oad-erin," + targetOU + if !exists(t, raw, movedDN) { + t.Errorf("user was not moved to %s", movedDN) + } + if exists(t, raw, erin) { + t.Errorf("user still present at old DN %s", erin) + } +} -- 2.52.0 From c840ed054cc7e3cb9546a121b501aad7c9ad03b7 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 12:43:09 -0400 Subject: [PATCH 13/34] feat(rules): expose operator-friendly canonical names for matched objects Add ldap.CanonicalName: prefer the directory's canonicalName attribute (now requested in searches) and otherwise build domain.com/OU/OU/CN from the DN. Populate MatchedObject.CanonicalName and return it in the rule preview API so operators see canonical names instead of raw DNs. Unit-tested. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/rules_handlers.go | 14 +++-- backend/internal/directory/ldap/canonical.go | 60 +++++++++++++++++++ .../internal/directory/ldap/canonical_test.go | 31 ++++++++++ backend/internal/rules/engine/engine.go | 11 +++- 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 backend/internal/directory/ldap/canonical.go create mode 100644 backend/internal/directory/ldap/canonical_test.go diff --git a/backend/internal/api/rules_handlers.go b/backend/internal/api/rules_handlers.go index a11710f..79db15a 100644 --- a/backend/internal/api/rules_handlers.go +++ b/backend/internal/api/rules_handlers.go @@ -38,9 +38,10 @@ type PreviewResponse struct { } type previewMatchedObject struct { - DN string `json:"dn"` - ObjectType string `json:"objectType"` - Attributes map[string][]string `json:"attributes,omitempty"` + DN string `json:"dn"` + CanonicalName string `json:"canonicalName,omitempty"` + ObjectType string `json:"objectType"` + Attributes map[string][]string `json:"attributes,omitempty"` } type previewPlannedAction struct { @@ -120,9 +121,10 @@ func toPreviewResponse(p *engine.PreviewResult) PreviewResponse { } for _, m := range p.MatchedObjects { resp.MatchedObjects = append(resp.MatchedObjects, previewMatchedObject{ - DN: m.DN, - ObjectType: m.ObjectType, - Attributes: m.Attributes, + DN: m.DN, + CanonicalName: m.CanonicalName, + ObjectType: m.ObjectType, + Attributes: m.Attributes, }) } for _, a := range p.PlannedActions { diff --git a/backend/internal/directory/ldap/canonical.go b/backend/internal/directory/ldap/canonical.go new file mode 100644 index 0000000..5371014 --- /dev/null +++ b/backend/internal/directory/ldap/canonical.go @@ -0,0 +1,60 @@ +package ldap + +import ( + "strings" + + ldap "github.com/go-ldap/ldap/v3" +) + +// CanonicalName returns an operator-friendly canonical name for an object in +// the form domain.com/OU/OU/CN. It prefers the directory-provided canonicalName +// (an AD constructed attribute) when present, and otherwise builds one from the +// distinguished name. +func CanonicalName(dn, provided string) string { + if p := strings.TrimSpace(provided); p != "" { + return p + } + return canonicalFromDN(dn) +} + +// canonicalFromDN converts a distinguished name to a canonical name: +// the DC components become the dotted domain, and the remaining RDNs (OU/CN) +// are reversed to root-first and joined with "/". For example +// +// CN=Jane,OU=Users,OU=HQ,DC=corp,DC=example,DC=com +// +// becomes corp.example.com/HQ/Users/Jane. If the DN cannot be parsed it is +// returned unchanged. +func canonicalFromDN(dn string) string { + parsed, err := ldap.ParseDN(dn) + if err != nil { + return dn + } + var domain []string + var path []string + for _, rdn := range parsed.RDNs { + for _, a := range rdn.Attributes { + if strings.EqualFold(a.Type, "DC") { + domain = append(domain, a.Value) + } else { + path = append(path, a.Value) + } + } + } + // RDNs are leaf-first; canonical path is root-first. + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + + dom := strings.Join(domain, ".") + switch { + case dom == "" && len(path) == 0: + return dn + case len(path) == 0: + return dom + case dom == "": + return strings.Join(path, "/") + default: + return dom + "/" + strings.Join(path, "/") + } +} diff --git a/backend/internal/directory/ldap/canonical_test.go b/backend/internal/directory/ldap/canonical_test.go new file mode 100644 index 0000000..4e9a8ac --- /dev/null +++ b/backend/internal/directory/ldap/canonical_test.go @@ -0,0 +1,31 @@ +package ldap + +import "testing" + +func TestCanonicalFromDN(t *testing.T) { + cases := []struct{ dn, want string }{ + {"CN=OrchestrAD,OU=Users,OU=GSL,DC=gracesolutions,DC=lab", "gracesolutions.lab/GSL/Users/OrchestrAD"}, + {"CN=Jane Doe,OU=Users,OU=HQ,DC=corp,DC=example,DC=com", "corp.example.com/HQ/Users/Jane Doe"}, + {"DC=gracesolutions,DC=lab", "gracesolutions.lab"}, + {"OU=Engineering,DC=corp,DC=local", "corp.local/Engineering"}, + } + for _, c := range cases { + if got := canonicalFromDN(c.dn); got != c.want { + t.Errorf("canonicalFromDN(%q) = %q, want %q", c.dn, got, c.want) + } + } +} + +func TestCanonicalNamePrefersProvided(t *testing.T) { + dn := "CN=OrchestrAD,OU=Users,DC=gracesolutions,DC=lab" + if got := CanonicalName(dn, "gracesolutions.lab/Users/OrchestrAD"); got != "gracesolutions.lab/Users/OrchestrAD" { + t.Errorf("provided canonicalName should win, got %q", got) + } + // Falls back to constructing from the DN when not provided. + if got := CanonicalName(dn, ""); got != "gracesolutions.lab/Users/OrchestrAD" { + t.Errorf("constructed = %q", got) + } + if got := CanonicalName(dn, " "); got != "gracesolutions.lab/Users/OrchestrAD" { + t.Errorf("blank provided should fall back, got %q", got) + } +} diff --git a/backend/internal/rules/engine/engine.go b/backend/internal/rules/engine/engine.go index 85d88f4..8344718 100644 --- a/backend/internal/rules/engine/engine.go +++ b/backend/internal/rules/engine/engine.go @@ -77,9 +77,12 @@ type PreviewResult struct { // MatchedObject represents an AD object that matched rule conditions type MatchedObject struct { - DN string - ObjectType string - Attributes map[string][]string + DN string + // CanonicalName is the operator-friendly name (domain.com/OU/CN), from the + // directory's canonicalName attribute when present, else built from the DN. + CanonicalName string + ObjectType string + Attributes map[string][]string } // PlannedAction represents an action that would be executed @@ -125,6 +128,7 @@ func (e *Engine) Preview(ctx context.Context, rule *models.Rule, conn *models.AD for _, attr := range entry.Attributes { matched.Attributes[attr.Name] = attr.Values } + matched.CanonicalName = ldap.CanonicalName(entry.DN, entry.GetAttributeValue("canonicalName")) result.MatchedObjects = append(result.MatchedObjects, matched) } @@ -342,6 +346,7 @@ func collectAttributes(rule *models.Rule) []string { "mail": true, "memberOf": true, "member": true, + "canonicalName": true, } for _, g := range rule.ConditionGroups { for _, c := range g.Conditions { -- 2.52.0 From 7be92da2e6015ec233d2383bcc1bd1b871009362 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 12:47:26 -0400 Subject: [PATCH 14/34] feat(rules): canonical->DN targets and idempotent OU-path creation Add CanonicalToOUDN/NormalizeOUTarget so an OU target can be given as a canonical path (domain.com/OU/OU) or a DN, and Client.EnsureOUPath which idempotently creates every OU down the path (parents before children). MoveToOu and group creation now normalize the target and ensure the full OU path instead of only the leaf. Unit-tested (conversion, escaped split) and verified against the test AD: a canonical nested target creates each OU and moves the object; re-running is a no-op. Co-Authored-By: Claude Opus 4.8 --- backend/internal/adtest/ad_test.go | 42 +++++++++ backend/internal/directory/ldap/canonical.go | 87 +++++++++++++++++++ .../internal/directory/ldap/canonical_test.go | 42 +++++++++ backend/internal/directory/ldap/operations.go | 30 +++++++ backend/internal/rules/engine/actions.go | 23 ++--- 5 files changed, 214 insertions(+), 10 deletions(-) diff --git a/backend/internal/adtest/ad_test.go b/backend/internal/adtest/ad_test.go index d77b9f5..5dfe7eb 100644 --- a/backend/internal/adtest/ad_test.go +++ b/backend/internal/adtest/ad_test.go @@ -322,3 +322,45 @@ func TestMoveToOU(t *testing.T) { t.Errorf("user still present at old DN %s", erin) } } + +// TestMoveToNestedOUByCanonical: a canonical target path (domain/OU/OU/OU) is +// converted to a DN and every missing OU is created idempotently, then the +// object is moved into the leaf OU. Re-running the rule is a no-op. +func TestMoveToNestedOUByCanonical(t *testing.T) { + e := loadEnv(t) + raw := e.rawConn(t) + ou := e.makeOU(t, raw, "nested") + + frank := "CN=oad-frank," + ou + addUser(t, raw, frank, map[string][]string{"sAMAccountName": {"oad-frank"}, "department": {"Nest"}}) + + // Canonical target two levels below the (existing) test OU. + ouCanonical := appldap.CanonicalName(ou, "") + targetCanonical := ouCanonical + "/Level1/Level2" + + rule := ruleUsersWhere(ou, "department", "Nest", models.RuleAction{ + ID: "a1", ActionType: string(types.ActionMoveToOu), IsEnabled: true, + ConfigurationJSON: actionJSON(t, models.ActionConfig{TargetOU: targetCanonical, CreateOUIfMissing: true}), + }) + + res := newEngine().Execute(context.Background(), rule, conn(e.baseDN), e.appClient(t)) + if res.Status != types.RunStatusCompleted { + t.Fatalf("run status = %s, want Completed; %+v", res.Status, res.Errors) + } + + level1 := "OU=Level1," + ou + level2 := "OU=Level2," + level1 + if !exists(t, raw, level1) || !exists(t, raw, level2) { + t.Fatalf("nested OU path not created (level1=%v level2=%v)", exists(t, raw, level1), exists(t, raw, level2)) + } + movedDN := "CN=oad-frank," + level2 + if !exists(t, raw, movedDN) { + t.Errorf("user not moved to %s", movedDN) + } + + // Idempotent: running again does not error (OUs exist, user already there). + res2 := newEngine().Execute(context.Background(), rule, conn(e.baseDN), e.appClient(t)) + if res2.Status != types.RunStatusCompleted { + t.Errorf("re-run status = %s, want Completed; %+v", res2.Status, res2.Errors) + } +} diff --git a/backend/internal/directory/ldap/canonical.go b/backend/internal/directory/ldap/canonical.go index 5371014..f34c7a9 100644 --- a/backend/internal/directory/ldap/canonical.go +++ b/backend/internal/directory/ldap/canonical.go @@ -58,3 +58,90 @@ func canonicalFromDN(dn string) string { return dom + "/" + strings.Join(path, "/") } } + +// LooksCanonical reports whether s is a canonical name (domain.com/OU/OU) rather +// than a distinguished name. A DN contains "=" in its components; a canonical +// name uses "/" separators and no "=". +func LooksCanonical(s string) bool { + s = strings.TrimSpace(s) + return s != "" && strings.Contains(s, "/") && !strings.Contains(s, "=") +} + +// CanonicalToOUDN converts a canonical OU path (domain.com/OU/OU) into a +// distinguished name, treating every path segment as an organizationalUnit +// (the common case for a target OU). For example +// +// corp.example.com/HQ/Users +// +// becomes OU=Users,OU=HQ,DC=corp,DC=example,DC=com. +func CanonicalToOUDN(canonical string) string { + canonical = strings.Trim(strings.TrimSpace(canonical), "/") + if canonical == "" { + return "" + } + parts := strings.Split(canonical, "/") + domain := parts[0] + path := parts[1:] // root-first + + var comps []string + for i := len(path) - 1; i >= 0; i-- { // reverse to leaf-first + comps = append(comps, "OU="+escapeDNValue(path[i])) + } + for _, d := range strings.Split(domain, ".") { + if d != "" { + comps = append(comps, "DC="+escapeDNValue(d)) + } + } + return strings.Join(comps, ",") +} + +// NormalizeOUTarget accepts either a DN or a canonical OU path and returns a DN. +func NormalizeOUTarget(input string) string { + if LooksCanonical(input) { + return CanonicalToOUDN(input) + } + return strings.TrimSpace(input) +} + +// escapeDNValue escapes the characters that are special inside a DN attribute +// value (RFC 4514), enough for OU/DC names entered by operators. +func escapeDNValue(v string) string { + r := strings.NewReplacer( + `\`, `\\`, + `,`, `\,`, + `+`, `\+`, + `"`, `\"`, + `<`, `\<`, + `>`, `\>`, + `;`, `\;`, + `=`, `\=`, + ) + return r.Replace(v) +} + +// splitDNComponents splits a DN into its top-level RDN components, honoring +// backslash-escaped commas. +func splitDNComponents(dn string) []string { + var out []string + var cur strings.Builder + escaped := false + for _, ch := range dn { + switch { + case escaped: + cur.WriteRune(ch) + escaped = false + case ch == '\\': + cur.WriteRune(ch) + escaped = true + case ch == ',': + out = append(out, cur.String()) + cur.Reset() + default: + cur.WriteRune(ch) + } + } + if cur.Len() > 0 { + out = append(out, cur.String()) + } + return out +} diff --git a/backend/internal/directory/ldap/canonical_test.go b/backend/internal/directory/ldap/canonical_test.go index 4e9a8ac..d9e8061 100644 --- a/backend/internal/directory/ldap/canonical_test.go +++ b/backend/internal/directory/ldap/canonical_test.go @@ -29,3 +29,45 @@ func TestCanonicalNamePrefersProvided(t *testing.T) { t.Errorf("blank provided should fall back, got %q", got) } } + +func TestCanonicalToOUDN(t *testing.T) { + cases := []struct{ canonical, want string }{ + {"corp.example.com/HQ/Users", "OU=Users,OU=HQ,DC=corp,DC=example,DC=com"}, + {"gracesolutions.lab/GSL", "OU=GSL,DC=gracesolutions,DC=lab"}, + {"gracesolutions.lab/GSL/Sub/Deep", "OU=Deep,OU=Sub,OU=GSL,DC=gracesolutions,DC=lab"}, + {"gracesolutions.lab", "DC=gracesolutions,DC=lab"}, + } + for _, c := range cases { + if got := CanonicalToOUDN(c.canonical); got != c.want { + t.Errorf("CanonicalToOUDN(%q) = %q, want %q", c.canonical, got, c.want) + } + } +} + +func TestNormalizeOUTarget(t *testing.T) { + // canonical -> DN + if got := NormalizeOUTarget("corp.example.com/HQ"); got != "OU=HQ,DC=corp,DC=example,DC=com" { + t.Errorf("canonical normalize = %q", got) + } + // a DN passes through unchanged + dn := "OU=HQ,DC=corp,DC=example,DC=com" + if got := NormalizeOUTarget(dn); got != dn { + t.Errorf("DN should pass through, got %q", got) + } + if !LooksCanonical("corp.example.com/HQ") || LooksCanonical("OU=HQ,DC=corp,DC=com") { + t.Error("LooksCanonical misclassified an input") + } +} + +func TestSplitDNComponentsEscaped(t *testing.T) { + got := splitDNComponents(`OU=Eng\,Ops,OU=HQ,DC=corp,DC=com`) + want := []string{`OU=Eng\,Ops`, "OU=HQ", "DC=corp", "DC=com"} + if len(got) != len(want) { + t.Fatalf("split = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("component %d = %q, want %q", i, got[i], want[i]) + } + } +} diff --git a/backend/internal/directory/ldap/operations.go b/backend/internal/directory/ldap/operations.go index 52d3d4e..e43f260 100644 --- a/backend/internal/directory/ldap/operations.go +++ b/backend/internal/directory/ldap/operations.go @@ -135,6 +135,36 @@ func (c *Client) CreateOU(ouDN string) error { return c.conn.Add(addRequest) } +// EnsureOUPath idempotently ensures every organizationalUnit along ouDN exists, +// creating missing OUs from the domain root down to the leaf. The non-OU suffix +// (the DC domain components and any CN containers) is assumed to already exist. +// Safe to call repeatedly. +func (c *Client) EnsureOUPath(ouDN string) error { + if c.conn == nil { + return fmt.Errorf("not connected") + } + comps := splitDNComponents(ouDN) + + // OU components, root-most (highest index) first, so parents are created + // before their children. + for i := len(comps) - 1; i >= 0; i-- { + if !strings.HasPrefix(strings.ToUpper(strings.TrimSpace(comps[i])), "OU=") { + continue + } + dn := strings.Join(comps[i:], ",") + exists, err := c.Exists(dn) + if err != nil { + return fmt.Errorf("checking OU %s: %w", dn, err) + } + if !exists { + if err := c.CreateOU(dn); err != nil { + return fmt.Errorf("creating OU %s: %w", dn, err) + } + } + } + return nil +} + // Exists checks if an object exists func (c *Client) Exists(dn string) (bool, error) { entry, err := c.SearchOne(dn, ldap.ScopeBaseObject, "(objectClass=*)", []string{"dn"}) diff --git a/backend/internal/rules/engine/actions.go b/backend/internal/rules/engine/actions.go index da7b065..2facdb5 100644 --- a/backend/internal/rules/engine/actions.go +++ b/backend/internal/rules/engine/actions.go @@ -77,6 +77,10 @@ func (e *actionExecutor) doAddToGroup(out *actionOutcome, cfg models.ActionConfi return } if !exists { + if err := e.client.EnsureOUPath(parentDN(groupDN)); err != nil { + out.fail(fmt.Errorf("ensuring group OU path: %w", err)) + return + } if err := e.client.CreateGroup(groupDN, cfg.GroupType, cfg.GroupScope); err != nil { out.fail(fmt.Errorf("creating group: %w", err)) return @@ -113,6 +117,10 @@ func (e *actionExecutor) doEnsureGroupExists(out *actionOutcome, cfg models.Acti return } + if err := e.client.EnsureOUPath(parentDN(groupDN)); err != nil { + out.fail(fmt.Errorf("ensuring group OU path: %w", err)) + return + } if err := e.client.CreateGroup(groupDN, cfg.GroupType, cfg.GroupScope); err != nil { out.fail(fmt.Errorf("creating group: %w", err)) return @@ -128,24 +136,19 @@ func (e *actionExecutor) doMoveToOU(out *actionOutcome, cfg models.ActionConfig, out.fail(fmt.Errorf("expanding target OU: %w", err)) return } + // Accept either a DN or a canonical path (domain.com/OU/OU). + targetOU = ldap.NormalizeOUTarget(targetOU) if targetOU == "" { out.fail(fmt.Errorf("targetOu is required")) return } if cfg.CreateOUIfMissing { - exists, err := e.client.Exists(targetOU) - if err != nil { - out.fail(fmt.Errorf("checking OU existence: %w", err)) + // Idempotently create every OU down the path, not just the leaf. + if err := e.client.EnsureOUPath(targetOU); err != nil { + out.fail(fmt.Errorf("ensuring OU path: %w", err)) return } - if !exists { - if err := e.client.CreateOU(targetOU); err != nil { - out.fail(fmt.Errorf("creating OU: %w", err)) - return - } - out.Details["ouCreated"] = true - } } // If object is already in the target OU, nothing to do. -- 2.52.0 From 5674ca7849dc10f44c107a1336a557c17f74c28a Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 12:59:14 -0400 Subject: [PATCH 15/34] feat(ui): dark-by-default, OrchestrAD branding, de-jarred login, SSO button Default the theme to dark (toggle still available). Rebrand the logo to the OrchestrAD icon + wordmark. Redesign the login into a calm, centered card on a soft themed backdrop (removed the 3D character illustration and decorative circles). Add a 'Sign in with SSO' button shown when OIDC is enabled, and adopt the OIDC callback token from the URL fragment (hydrating the user via /auth/me). Replace the hardcoded 'Mike/Admin' sidebar card with the real signed-in user and a working sign-out; de-jar the search label. Co-Authored-By: Claude Opus 4.8 --- frontend/public/images/logos/orchestrad.svg | 38 ++++ .../src/app/(app)/layout/shared/logo/Logo.tsx | 71 +++---- .../(app)/layout/vertical/header/Search.tsx | 2 +- .../(app)/layout/vertical/sidebar/Sidebar.tsx | 39 ++-- frontend/src/app/context/AuthContext.tsx | 20 +- frontend/src/app/context/config.ts | 2 +- frontend/src/app/login/AuthLogin.tsx | 69 ++++++- frontend/src/app/login/page.tsx | 185 ++++-------------- 8 files changed, 216 insertions(+), 210 deletions(-) create mode 100644 frontend/public/images/logos/orchestrad.svg diff --git a/frontend/public/images/logos/orchestrad.svg b/frontend/public/images/logos/orchestrad.svg new file mode 100644 index 0000000..5403910 --- /dev/null +++ b/frontend/public/images/logos/orchestrad.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/app/(app)/layout/shared/logo/Logo.tsx b/frontend/src/app/(app)/layout/shared/logo/Logo.tsx index 4fd0357..0ab07ad 100644 --- a/frontend/src/app/(app)/layout/shared/logo/Logo.tsx +++ b/frontend/src/app/(app)/layout/shared/logo/Logo.tsx @@ -1,67 +1,46 @@ 'use client' import Link from "next/link"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; import { styled } from '@mui/material/styles'; - -import Image from "next/image"; import { useContext } from "react"; import { CustomizerContext } from "@/app/context/customizerContext"; import config from "@/app/context/config"; +// OrchestrAD wordmark: the app icon plus the product name. Collapses to just the +// icon in the mini sidebar. Works in both light and dark themes. export default function Logo() { - const { isCollapse, isSidebarHover, activeDir, activeMode } = useContext(CustomizerContext); + const { isCollapse, isSidebarHover } = useContext(CustomizerContext); const TopbarHeight = config.topbarHeight; + const compact = isCollapse === "mini-sidebar" && !isSidebarHover; const LinkStyled = styled(Link)(() => ({ height: TopbarHeight, - width: isCollapse == "mini-sidebar" && !isSidebarHover ? "40px" : "180px", + width: compact ? "40px" : "180px", overflow: "hidden", - display: "block", + display: "flex", + alignItems: "center", + gap: "10px", + textDecoration: "none", })); - if (activeDir === "ltr") { - return ( - - {activeMode === "dark" ? ( - logo - ) : ( - logo - )} - - ); - } - return ( - {activeMode === "dark" ? ( - logo - ) : ( - logo - )} + + {!compact ? ( + + OrchestrAD + + ) : null} ); } diff --git a/frontend/src/app/(app)/layout/vertical/header/Search.tsx b/frontend/src/app/(app)/layout/vertical/header/Search.tsx index 6cee562..b7cf943 100644 --- a/frontend/src/app/(app)/layout/vertical/header/Search.tsx +++ b/frontend/src/app/(app)/layout/vertical/header/Search.tsx @@ -70,7 +70,7 @@ const Search = () => { borderRadius: "25px", }} > - Try to searching + Search theme.breakpoints.down("lg")); const { @@ -26,6 +35,7 @@ export default function Sidebar() { setIsMobileSidebar, isCardShadow } = useContext(CustomizerContext); + const { session, logout } = useContext(AuthContext); const MiniSidebarWidth = config.miniSidebarWidth; const SidebarWidth = config.sidebarWidth; @@ -117,29 +127,28 @@ export default function Sidebar() { {isCollapse == "mini-sidebar" ? null : ( - + - - - - Mike - Admin + + + {initials(session?.user?.displayName || session?.user?.username)} + + + + {session?.user?.displayName || session?.user?.username || "Signed in"} + + + {session?.user?.roles?.[0] || "User"} + - - + logout()} aria-label="Sign out"> + diff --git a/frontend/src/app/context/AuthContext.tsx b/frontend/src/app/context/AuthContext.tsx index aaf8688..35e8aed 100644 --- a/frontend/src/app/context/AuthContext.tsx +++ b/frontend/src/app/context/AuthContext.tsx @@ -28,6 +28,7 @@ interface AuthContextValue { ready: boolean; session: StoredSession | null; login: (username: string, password: string) => Promise; + adoptSession: (token: string, expiresAt: string) => Promise; logout: () => Promise; updateSessionUser: (patch: Partial) => void; } @@ -38,6 +39,9 @@ export const AuthContext = createContext({ login: async () => { throw new Error("AuthContext not mounted"); }, + adoptSession: async () => { + throw new Error("AuthContext not mounted"); + }, logout: async () => {}, updateSessionUser: () => {}, }); @@ -126,6 +130,18 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { return result; }, []); + // adoptSession accepts a session token minted elsewhere (the OIDC/SSO + // callback hands it back via the URL fragment), stores it so the API client + // authenticates, hydrates the user via /auth/me, and returns it. + const adoptSession = useCallback(async (token: string, expiresAt: string) => { + writeStoredSession({ token, expiresAt, user: {} as UserInfo }); + const user = await AuthApi.me(); + const stored: StoredSession = { token, expiresAt, user }; + writeStoredSession(stored); + setSession(stored); + return user; + }, []); + const updateSessionUser = useCallback((patch: Partial) => { setSession((prev) => { if (!prev) return prev; @@ -139,8 +155,8 @@ export const AuthProvider = ({ children }: { children: React.ReactNode }) => { }, []); const value = useMemo( - () => ({ ready, session, login, logout, updateSessionUser }), - [ready, session, login, logout, updateSessionUser], + () => ({ ready, session, login, adoptSession, logout, updateSessionUser }), + [ready, session, login, adoptSession, logout, updateSessionUser], ); return {children}; diff --git a/frontend/src/app/context/config.ts b/frontend/src/app/context/config.ts index 0ec4779..2b1f94f 100644 --- a/frontend/src/app/context/config.ts +++ b/frontend/src/app/context/config.ts @@ -1,6 +1,6 @@ const config = { activeDir: "ltr", // This can be ltr or rtl - activeMode: "light", // This can be light or dark + activeMode: "dark", // This can be light or dark (OrchestrAD defaults to dark) activeTheme: "BLUE_THEME", // BLUE_THEME, GREEN_THEME, AQUA_THEME, PURPLE_THEME, ORANGE_THEME activeLayout: "vertical", // This can be vertical or horizontal isLayout: "boxed", // This can be full or boxed diff --git a/frontend/src/app/login/AuthLogin.tsx b/frontend/src/app/login/AuthLogin.tsx index 7320c8b..89de7b6 100644 --- a/frontend/src/app/login/AuthLogin.tsx +++ b/frontend/src/app/login/AuthLogin.tsx @@ -3,10 +3,11 @@ import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import CircularProgress from '@mui/material/CircularProgress'; +import Divider from '@mui/material/Divider'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import { useRouter, useSearchParams } from "next/navigation"; -import { useContext, useState, ReactNode } from "react"; +import { useContext, useEffect, useState, ReactNode } from "react"; import CustomTextField from "@/app/components/forms/theme-elements/CustomTextField"; import CustomFormLabel from "@/app/components/forms/theme-elements/CustomFormLabel"; import { AuthContext, isApiError } from "@/app/context/AuthContext"; @@ -21,11 +22,52 @@ interface loginType { const AuthLogin = ({ title, subtitle, subtext }: loginType) => { const router = useRouter(); const params = useSearchParams(); - const { login } = useContext(AuthContext); + const { login, adoptSession } = useContext(AuthContext); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); + const [ssoEnabled, setSsoEnabled] = useState(false); + + // Is SSO configured? Controls whether the "Sign in with SSO" button shows. + useEffect(() => { + let active = true; + fetch("/api/v1/auth/oidc/status") + .then((r) => (r.ok ? r.json() : null)) + .then((body) => { + if (active) setSsoEnabled(Boolean(body?.data?.enabled ?? body?.enabled)); + }) + .catch(() => {}); + return () => { + active = false; + }; + }, []); + + // The OIDC callback redirects back to /login with the session token in the URL + // fragment. Adopt it, then continue into the app. + useEffect(() => { + if (typeof window === "undefined" || !window.location.hash) return; + const frag = new URLSearchParams(window.location.hash.replace(/^#/, "")); + const oidcError = frag.get("oidc_error"); + if (oidcError) { + history.replaceState(null, "", window.location.pathname + window.location.search); + setError(`SSO sign-in failed (${oidcError}).`); + return; + } + const token = frag.get("oidc_token"); + const expiresAt = frag.get("expires_at"); + if (!token || !expiresAt) return; + history.replaceState(null, "", window.location.pathname + window.location.search); + setSubmitting(true); + adoptSession(token, expiresAt) + .then((user) => { + router.replace(user.passwordResetRequired ? "/change-password" : safeRedirectTarget(params?.get("redirect"))); + }) + .catch(() => { + setSubmitting(false); + setError("SSO sign-in failed while establishing the session."); + }); + }, [adoptSession, router, params]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -110,6 +152,29 @@ const AuthLogin = ({ title, subtitle, subtext }: loginType) => { {submitting ? "Signing in" : "Sign In"} + + {ssoEnabled ? ( + + + + or + + + + + ) : null} + {subtitle} ); diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx index 118b0ba..6fae570 100644 --- a/frontend/src/app/login/page.tsx +++ b/frontend/src/app/login/page.tsx @@ -1,157 +1,56 @@ "use client"; -import Avatar from '@mui/material/Avatar'; -import Box from '@mui/material/Box'; -import Grid from '@mui/material/Grid'; -import { Theme } from '@mui/material/styles'; -import Typography from '@mui/material/Typography'; -import useMediaQuery from '@mui/material/useMediaQuery'; +import Box from "@mui/material/Box"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; import PageContainer from "@/app/components/container/PageContainer"; import Logo from "@/app/(app)/layout/shared/logo/Logo"; import AuthLogin from "./AuthLogin"; -import { CustomizerContext } from "@/app/context/customizerContext"; -import { useContext } from "react"; +// A deliberately calm, professional sign-in: a single centered card on a soft +// themed backdrop, no decorative illustrations. Works in light and dark. export default function Login() { - const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up("lg")); - const { isBorderRadius } = useContext(CustomizerContext); - - return ( - - + theme.palette.mode === "dark" + ? `radial-gradient(1200px 600px at 50% -10%, ${theme.palette.primary.dark}22, transparent 60%), ${theme.palette.background.default}` + : `radial-gradient(1200px 600px at 50% -10%, ${theme.palette.primary.light}33, transparent 60%), ${theme.palette.background.default}`, + }} + > + theme.palette.grey[200], - overflow: "hidden", - borderRadius: isBorderRadius / 18, - display: "flex", - alignItems: "center", - justifyContent: "center", + width: "100%", + maxWidth: 420, + p: { xs: 3, sm: 5 }, + borderRadius: 3, + border: (theme) => `1px solid ${theme.palette.divider}`, + boxShadow: (theme) => + theme.palette.mode === "dark" + ? "0 24px 60px -20px rgba(0,0,0,0.6)" + : "0 24px 60px -24px rgba(37,83,185,0.25)", }} > - - theme.palette.mode === "light" ? "white" : "#111c2d", - maxWidth: { - xs: "340px", - sm: "500px", - lg: "1320px", - }, - }} - > - - - - {lgUp ? ( - - - - ) : ( - "" - )} - - - - Active Directory automation platform - - } - /> - - - - - - - + + + + Sign in to OrchestrAD + + + Active Directory rule automation + + + + + ); -- 2.52.0 From f0c81dfa51055e4b9b916a1f7fd418b08bc569a1 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 13:06:30 -0400 Subject: [PATCH 16/34] feat(ui): Security page (OIDC + TLS config) and canonical name in preview Add a Security admin page wiring the OIDC and TLS backends: an SSO/OIDC form (issuer, client id/secret, redirect, scopes, claim mappings, default role) and a TLS card showing the live certificate and a source picker - self-managed, bring-your-own (PEM upload), or a Windows-store certificate (enumerate + select). Add OidcApi/TlsApi clients + types, a Security nav entry, and a Canonical Name column to the rule preview. Co-Authored-By: Claude Opus 4.8 --- .../layout/vertical/sidebar/MenuItems.ts | 7 + .../src/app/(app)/rules/RulePreviewDialog.tsx | 2 + frontend/src/app/(app)/security/page.tsx | 378 ++++++++++++++++++ frontend/src/lib/api/resources.ts | 19 + frontend/src/lib/api/types.ts | 42 ++ 5 files changed, 448 insertions(+) create mode 100644 frontend/src/app/(app)/security/page.tsx diff --git a/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts b/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts index d8ae706..70d1c07 100644 --- a/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts +++ b/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts @@ -81,6 +81,13 @@ const Menuitems: NavGroup[] = [ href: "/audit", bgcolor: "warning", }, + { + id: uniqueId(), + title: "Security", + icon: "shield-check-line-duotone", + href: "/security", + bgcolor: "primary", + }, { id: uniqueId(), title: "Settings", diff --git a/frontend/src/app/(app)/rules/RulePreviewDialog.tsx b/frontend/src/app/(app)/rules/RulePreviewDialog.tsx index 1b42390..fa8fb4e 100644 --- a/frontend/src/app/(app)/rules/RulePreviewDialog.tsx +++ b/frontend/src/app/(app)/rules/RulePreviewDialog.tsx @@ -111,6 +111,7 @@ export default function RulePreviewDialog({ rule, onClose }: Props) { + Canonical Name DN Type @@ -118,6 +119,7 @@ export default function RulePreviewDialog({ rule, onClose }: Props) { {result.matchedObjects.slice(0, 100).map((m, i) => ( + {m.canonicalName || "β€”"} {m.dn} {m.objectType} diff --git a/frontend/src/app/(app)/security/page.tsx b/frontend/src/app/(app)/security/page.tsx new file mode 100644 index 0000000..6632e11 --- /dev/null +++ b/frontend/src/app/(app)/security/page.tsx @@ -0,0 +1,378 @@ +"use client"; + +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Chip from "@mui/material/Chip"; +import CircularProgress from "@mui/material/CircularProgress"; +import Divider from "@mui/material/Divider"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Grid from "@mui/material/Grid"; +import Radio from "@mui/material/Radio"; +import RadioGroup from "@mui/material/RadioGroup"; +import Stack from "@mui/material/Stack"; +import Switch from "@mui/material/Switch"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useCallback, useEffect, useState } from "react"; + +import PageContainer from "@/app/components/container/PageContainer"; +import DashboardCard from "@/app/components/shared/DashboardCard"; +import { ApiError } from "@/lib/api/client"; +import { OidcApi, TlsApi } from "@/lib/api/resources"; +import type { OidcConfig, TlsStatus, WindowsStoreCert } from "@/lib/api/types"; +import { formatDateTime } from "@/lib/format"; + +export default function SecurityPage() { + return ( + + + + + + + ); +} + +/* ------------------------------- OIDC / SSO ------------------------------- */ + +const emptyOidc: OidcConfig = { + enabled: false, + issuer: "", + clientId: "", + clientSecret: "", + redirectUrl: "", + scopes: "openid profile email", + usernameClaim: "preferred_username", + emailClaim: "email", + nameClaim: "name", + defaultRole: "", +}; + +function OidcCard() { + const [cfg, setCfg] = useState(emptyOidc); + const [secretDirty, setSecretDirty] = useState(false); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [saved, setSaved] = useState(false); + + const load = useCallback(async () => { + setLoading(true); + setError(null); + try { + setCfg(await OidcApi.getConfig()); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to load SSO configuration"); + } finally { + setLoading(false); + } + }, []); + useEffect(() => { + load(); + }, [load]); + + const set = (patch: Partial) => { + setSaved(false); + setCfg((c) => ({ ...c, ...patch })); + }; + + const save = async () => { + setSaving(true); + setError(null); + setSaved(false); + try { + const body: Partial = { + enabled: cfg.enabled, + issuer: cfg.issuer, + clientId: cfg.clientId, + redirectUrl: cfg.redirectUrl, + scopes: cfg.scopes, + usernameClaim: cfg.usernameClaim, + emailClaim: cfg.emailClaim, + nameClaim: cfg.nameClaim, + defaultRole: cfg.defaultRole, + }; + // Only send the secret when the operator actually typed a new one. + if (secretDirty) body.clientSecret = cfg.clientSecret; + const next = await OidcApi.putConfig(body); + setCfg(next); + setSecretDirty(false); + setSaved(true); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to save SSO configuration"); + } finally { + setSaving(false); + } + }; + + return ( + + {loading ? ( + + + + ) : ( + + + Configure an OpenID Connect provider (Entra ID, Okta, Keycloak, …). Values set here + override the corresponding environment variables. + + {error ? {error} : null} + {saved ? SSO configuration saved. : null} + + set({ enabled: e.target.checked })} />} + label="Enable SSO" + /> + + + set({ issuer: e.target.value })} /> + + + set({ defaultRole: e.target.value })} /> + + + set({ clientId: e.target.value })} /> + + + { setSecretDirty(true); set({ clientSecret: e.target.value }); }} /> + + + set({ redirectUrl: e.target.value })} /> + + + set({ scopes: e.target.value })} /> + + + set({ usernameClaim: e.target.value })} /> + + + set({ emailClaim: e.target.value })} /> + + + set({ nameClaim: e.target.value })} /> + + + + + + + )} + + ); +} + +/* ---------------------------------- TLS ---------------------------------- */ + +function TlsCard() { + const [status, setStatus] = useState(null); + const [mode, setMode] = useState("auto"); + const [loading, setLoading] = useState(true); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [msg, setMsg] = useState(null); + + // Bring-your-own inputs. + const [certPem, setCertPem] = useState(""); + const [keyPem, setKeyPem] = useState(""); + const [chainPem, setChainPem] = useState(""); + + // Windows store. + const [winCerts, setWinCerts] = useState(null); + const [winErr, setWinErr] = useState(null); + const [thumb, setThumb] = useState(""); + + const load = useCallback(async () => { + setLoading(true); + setError(null); + try { + const s = await TlsApi.status(); + setStatus(s); + if (s.certificate?.mode) setMode(s.certificate.mode); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to load TLS status"); + } finally { + setLoading(false); + } + }, []); + useEffect(() => { + load(); + }, [load]); + + const apply = async (fn: () => Promise, okMsg: string) => { + setBusy(true); + setError(null); + setMsg(null); + try { + await fn(); + setMsg(okMsg); + await load(); + } catch (err) { + setError(err instanceof ApiError ? err.message : "TLS operation failed"); + } finally { + setBusy(false); + } + }; + + const loadWinCerts = async () => { + setWinErr(null); + setWinCerts(null); + try { + setWinCerts(await TlsApi.windowsCerts()); + } catch (err) { + setWinErr(err instanceof ApiError ? err.message : "Could not enumerate the Windows store"); + } + }; + + return ( + + {loading ? ( + + + + ) : !status?.enabled ? ( + + TLS is disabled β€” the server is serving plain HTTP (TLS is terminated upstream). Enable + TLS to manage a certificate here. + + ) : ( + + {status.certificate ? ( + + + + {status.certificate.fallback ? ( + + ) : null} + + + + Subject: {status.certificate.subject || "β€”"} + + + Issuer: {status.certificate.issuer || "β€”"} + + + ) : null} + + {error ? {error} : null} + {msg ? {msg} : null} + + + Certificate source + setMode(e.target.value)}> + } label="Self-managed CA (auto-generated & renewed)" /> + } label="Bring your own certificate (upload)" /> + } + label="Windows certificate store" + disabled={!status.windowsStoreSupported} /> + + + {mode === "auto" ? ( + + + + ) : null} + + {mode === "provided" ? ( + + setCertPem(e.target.value)} + slotProps={{ htmlInput: { style: { fontFamily: "monospace", fontSize: 12 } } }} /> + setKeyPem(e.target.value)} + slotProps={{ htmlInput: { style: { fontFamily: "monospace", fontSize: 12 } } }} /> + setChainPem(e.target.value)} + slotProps={{ htmlInput: { style: { fontFamily: "monospace", fontSize: 12 } } }} /> + + + + + ) : null} + + {mode === "windows-store" ? ( + + + + + {winErr ? {winErr} : null} + {winCerts && winCerts.length > 0 ? ( + +
    + + + + Subject + Issuer + Expires + Key + + + + {winCerts.map((c) => ( + setThumb(c.thumbprint)} sx={{ cursor: "pointer" }}> + + + + {c.subject} + {c.issuer} + {formatDateTime(c.notAfter)} + + {c.hasPrivateKey ? + : } + + + ))} + +
    + + ) : winCerts ? ( + No certificates found. + ) : null} + + + + + ) : null} + + )} + + ); +} diff --git a/frontend/src/lib/api/resources.ts b/frontend/src/lib/api/resources.ts index 6cc9d6b..57c1f0b 100644 --- a/frontend/src/lib/api/resources.ts +++ b/frontend/src/lib/api/resources.ts @@ -22,6 +22,10 @@ import { Schedule, User, UserInfo, + OidcConfig, + TlsStatus, + TlsInfo, + WindowsStoreCert, } from "./types"; interface PageParams { @@ -181,3 +185,18 @@ export const ConfigApi = { import: (payload: ConfigExport, dryRun: boolean) => api.post("/api/v1/config/import", { payload, dryRun }).then((r) => r.data), }; + +export const OidcApi = { + getConfig: () => api.get("/api/v1/auth/oidc/config").then((r) => r.data), + putConfig: (body: Partial) => + api.put("/api/v1/auth/oidc/config", body).then((r) => r.data), +}; + +export const TlsApi = { + status: () => api.get("/api/v1/tls/status").then((r) => r.data), + setMode: (mode: string, windowsThumbprint?: string) => + api.post("/api/v1/tls/mode", { mode, windowsThumbprint }).then((r) => r.data), + uploadCertificate: (body: { certificatePem: string; privateKeyPem: string; chainPem?: string }) => + api.post("/api/v1/tls/certificate", body).then((r) => r.data), + windowsCerts: () => api.get("/api/v1/tls/windows-store").then((r) => r.data), +}; diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 0407f19..b093072 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -290,6 +290,7 @@ export interface ConnectionTestResult { export interface RulePreviewMatchedObject { dn: string; + canonicalName?: string; objectType: string; attributes?: Record; } @@ -433,3 +434,44 @@ export interface ImportReport { entities: ImportEntityReport[]; errors?: string[]; } + +// --- OIDC / SSO configuration --- +export interface OidcConfig { + enabled: boolean; + issuer: string; + clientId: string; + clientSecret: string; // redacted to "********" when set + redirectUrl: string; + scopes: string; + usernameClaim: string; + emailClaim: string; + nameClaim: string; + defaultRole: string; +} + +// --- TLS configuration --- +export interface TlsInfo { + mode: string; + fallback: boolean; + subject: string; + issuer: string; + dnsNames: string[] | null; + notBefore: string; + notAfter: string; +} + +export interface TlsStatus { + enabled: boolean; + windowsStoreSupported: boolean; + certificate?: TlsInfo; +} + +export interface WindowsStoreCert { + thumbprint: string; + subject: string; + issuer: string; + notBefore: string; + notAfter: string; + hasPrivateKey: boolean; + dnsNames: string[] | null; +} -- 2.52.0 From dd183941dc7a3ae86d6723ebb5f5100d57505fca Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 13:55:11 -0400 Subject: [PATCH 17/34] fix(repository): parse TEXT timestamps as strings for modernc sqlite The modernc.org/sqlite driver returns TEXT timestamp columns as strings rather than time.Time (unlike the CGO mattn driver), so scanning directly into *time.Time failed with "unsupported Scan" and surfaced as 500 errors on the credentials and connections list endpoints. Scan created/updated/started timestamps into strings and convert with the existing parseTimeOrZero/parseNullTime helpers across credentials, connections, schedules, rules, and rule_runs repositories. Add a regression test that round-trips a credential and connection through a migrated DB and asserts the timestamps come back parsed. Co-Authored-By: Claude Opus 4.8 --- backend/internal/repository/connections.go | 10 ++- backend/internal/repository/credentials.go | 10 ++- backend/internal/repository/rule_runs.go | 25 ++++-- backend/internal/repository/rules.go | 25 ++++-- backend/internal/repository/schedules.go | 10 ++- .../internal/repository/timestamps_test.go | 76 +++++++++++++++++++ 6 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 backend/internal/repository/timestamps_test.go diff --git a/backend/internal/repository/connections.go b/backend/internal/repository/connections.go index 2782793..23563a6 100644 --- a/backend/internal/repository/connections.go +++ b/backend/internal/repository/connections.go @@ -52,6 +52,7 @@ func (r *ConnectionRepository) GetByID(id string) (*models.ADConnection, error) conn := &models.ADConnection{} var isEnabled, useTLS, useStartTLS, allowInvalidCerts, pagingEnabled int var lastTested, deleted sql.NullString + var createdStr, updatedStr string err := r.db.QueryRow(` SELECT id, name, description, is_enabled, hosts, port, @@ -67,7 +68,7 @@ func (r *ConnectionRepository) GetByID(id string) (*models.ADConnection, error) &conn.RootDN, &conn.BindDN, &conn.CredentialID, &conn.DefaultSearchScope, &conn.TimeoutSeconds, &pagingEnabled, &conn.PageSize, &lastTested, &conn.LastTestResult, - &conn.CreatedUTC, &conn.UpdatedUTC, &deleted, + &createdStr, &updatedStr, &deleted, ) if err == sql.ErrNoRows { return nil, nil @@ -82,6 +83,8 @@ func (r *ConnectionRepository) GetByID(id string) (*models.ADConnection, error) conn.AllowInvalidCerts = intToBool(allowInvalidCerts) conn.PagingEnabled = intToBool(pagingEnabled) conn.LastTestedUTC = parseNullTime(lastTested) + conn.CreatedUTC = parseTimeOrZero(createdStr) + conn.UpdatedUTC = parseTimeOrZero(updatedStr) conn.DeletedUTC = parseNullTime(deleted) return conn, nil } @@ -113,18 +116,21 @@ func (r *ConnectionRepository) List(offset, limit int) ([]models.ADConnection, i var conn models.ADConnection var isEnabled, useTLS int var lastTested sql.NullString + var createdStr, updatedStr string if err := rows.Scan( &conn.ID, &conn.Name, &conn.Description, &isEnabled, &conn.Hosts, &conn.Port, &useTLS, &conn.RootDN, &conn.CredentialID, &conn.DefaultSearchScope, &lastTested, &conn.LastTestResult, - &conn.CreatedUTC, &conn.UpdatedUTC, + &createdStr, &updatedStr, ); err != nil { return nil, 0, err } conn.IsEnabled = intToBool(isEnabled) conn.UseTLS = intToBool(useTLS) conn.LastTestedUTC = parseNullTime(lastTested) + conn.CreatedUTC = parseTimeOrZero(createdStr) + conn.UpdatedUTC = parseTimeOrZero(updatedStr) conns = append(conns, conn) } diff --git a/backend/internal/repository/credentials.go b/backend/internal/repository/credentials.go index 53ca449..3495c76 100644 --- a/backend/internal/repository/credentials.go +++ b/backend/internal/repository/credentials.go @@ -46,6 +46,7 @@ func (r *CredentialRepository) GetByID(id string) (*models.Credential, error) { cred := &models.Credential{} var isEnabled int var lastTested, deleted sql.NullString + var createdStr, updatedStr string err := r.db.QueryRow(` SELECT id, name, description, credential_type, username, @@ -56,7 +57,7 @@ func (r *CredentialRepository) GetByID(id string) (*models.Credential, error) { &cred.ID, &cred.Name, &cred.Description, &cred.CredentialType, &cred.Username, &cred.EncryptedSecret, &isEnabled, &lastTested, &cred.LastTestResult, - &cred.CreatedUTC, &cred.UpdatedUTC, &deleted, + &createdStr, &updatedStr, &deleted, ) if err == sql.ErrNoRows { return nil, nil @@ -67,6 +68,8 @@ func (r *CredentialRepository) GetByID(id string) (*models.Credential, error) { cred.IsEnabled = intToBool(isEnabled) cred.LastTestedUTC = parseNullTime(lastTested) + cred.CreatedUTC = parseTimeOrZero(createdStr) + cred.UpdatedUTC = parseTimeOrZero(updatedStr) cred.DeletedUTC = parseNullTime(deleted) return cred, nil } @@ -98,15 +101,18 @@ func (r *CredentialRepository) List(offset, limit int) ([]models.Credential, int var cred models.Credential var isEnabled int var lastTested sql.NullString + var createdStr, updatedStr string if err := rows.Scan( &cred.ID, &cred.Name, &cred.Description, &cred.CredentialType, &cred.Username, &isEnabled, &lastTested, &cred.LastTestResult, - &cred.CreatedUTC, &cred.UpdatedUTC, + &createdStr, &updatedStr, ); err != nil { return nil, 0, err } cred.IsEnabled = intToBool(isEnabled) cred.LastTestedUTC = parseNullTime(lastTested) + cred.CreatedUTC = parseTimeOrZero(createdStr) + cred.UpdatedUTC = parseTimeOrZero(updatedStr) creds = append(creds, cred) } diff --git a/backend/internal/repository/rule_runs.go b/backend/internal/repository/rule_runs.go index 09904f3..ff8f053 100644 --- a/backend/internal/repository/rule_runs.go +++ b/backend/internal/repository/rule_runs.go @@ -80,6 +80,7 @@ func (r *RuleRunRepository) Update(run *models.RuleRun) error { func (r *RuleRunRepository) GetByID(id string) (*models.RuleRun, error) { run := &models.RuleRun{} var completed sql.NullString + var startedStr, createdStr string err := r.db.QueryRow(` SELECT id, rule_id, status, started_utc, completed_utc, duration_ms, @@ -87,9 +88,9 @@ func (r *RuleRunRepository) GetByID(id string) (*models.RuleRun, error) { error_message, execution_mode, triggered_by, created_utc FROM rule_runs WHERE id = ? `, id).Scan( - &run.ID, &run.RuleID, &run.Status, &run.StartedUTC, &completed, &run.DurationMS, + &run.ID, &run.RuleID, &run.Status, &startedStr, &completed, &run.DurationMS, &run.ObjectsMatched, &run.ObjectsProcessed, &run.ActionsExecuted, &run.ActionsFailed, - &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &run.CreatedUTC, + &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &createdStr, ) if err == sql.ErrNoRows { return nil, nil @@ -98,6 +99,8 @@ func (r *RuleRunRepository) GetByID(id string) (*models.RuleRun, error) { return nil, err } + run.StartedUTC = parseTimeOrZero(startedStr) + run.CreatedUTC = parseTimeOrZero(createdStr) run.CompletedUTC = parseNullTime(completed) return run, nil } @@ -128,13 +131,16 @@ func (r *RuleRunRepository) ListByRule(ruleID string, offset, limit int) ([]mode for rows.Next() { var run models.RuleRun var completed sql.NullString + var startedStr, createdStr string if err := rows.Scan( - &run.ID, &run.RuleID, &run.Status, &run.StartedUTC, &completed, &run.DurationMS, + &run.ID, &run.RuleID, &run.Status, &startedStr, &completed, &run.DurationMS, &run.ObjectsMatched, &run.ObjectsProcessed, &run.ActionsExecuted, &run.ActionsFailed, - &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &run.CreatedUTC, + &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &createdStr, ); err != nil { return nil, 0, err } + run.StartedUTC = parseTimeOrZero(startedStr) + run.CreatedUTC = parseTimeOrZero(createdStr) run.CompletedUTC = parseNullTime(completed) runs = append(runs, run) } @@ -167,13 +173,16 @@ func (r *RuleRunRepository) List(offset, limit int) ([]models.RuleRun, int, erro for rows.Next() { var run models.RuleRun var completed sql.NullString + var startedStr, createdStr string if err := rows.Scan( - &run.ID, &run.RuleID, &run.Status, &run.StartedUTC, &completed, &run.DurationMS, + &run.ID, &run.RuleID, &run.Status, &startedStr, &completed, &run.DurationMS, &run.ObjectsMatched, &run.ObjectsProcessed, &run.ActionsExecuted, &run.ActionsFailed, - &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &run.CreatedUTC, + &run.ErrorMessage, &run.ExecutionMode, &run.TriggeredBy, &createdStr, ); err != nil { return nil, 0, err } + run.StartedUTC = parseTimeOrZero(startedStr) + run.CreatedUTC = parseTimeOrZero(createdStr) run.CompletedUTC = parseNullTime(completed) runs = append(runs, run) } @@ -220,12 +229,14 @@ func (r *RuleRunRepository) ListActionsByRun(runID string) ([]models.RuleRunActi var actions []models.RuleRunAction for rows.Next() { var a models.RuleRunAction + var createdStr string if err := rows.Scan( &a.ID, &a.RuleRunID, &a.RuleActionID, &a.ObjectDN, &a.ActionType, - &a.Status, &a.DetailsJSON, &a.ErrorMessage, &a.DurationMS, &a.CreatedUTC, + &a.Status, &a.DetailsJSON, &a.ErrorMessage, &a.DurationMS, &createdStr, ); err != nil { return nil, err } + a.CreatedUTC = parseTimeOrZero(createdStr) actions = append(actions, a) } diff --git a/backend/internal/repository/rules.go b/backend/internal/repository/rules.go index f9fb4cc..716c142 100644 --- a/backend/internal/repository/rules.go +++ b/backend/internal/repository/rules.go @@ -50,6 +50,7 @@ func (r *RuleRepository) GetByID(id string) (*models.Rule, error) { rule := &models.Rule{} var isEnabled, stopOnError int var lastRun, deleted sql.NullString + var createdStr, updatedStr string err := r.db.QueryRow(` SELECT id, name, description, is_enabled, ad_connection_id, @@ -64,7 +65,7 @@ func (r *RuleRepository) GetByID(id string) (*models.Rule, error) { &rule.SearchScopeOverride, &rule.ScheduleID, &rule.ExecutionMode, &rule.GroupJoinOperator, &rule.MaxParallelism, &stopOnError, &lastRun, &rule.LastRunResult, - &rule.CreatedUTC, &rule.UpdatedUTC, &deleted, + &createdStr, &updatedStr, &deleted, ) if err == sql.ErrNoRows { return nil, nil @@ -76,6 +77,8 @@ func (r *RuleRepository) GetByID(id string) (*models.Rule, error) { rule.IsEnabled = intToBool(isEnabled) rule.StopOnError = intToBool(stopOnError) rule.LastRunUTC = parseNullTime(lastRun) + rule.CreatedUTC = parseTimeOrZero(createdStr) + rule.UpdatedUTC = parseTimeOrZero(updatedStr) rule.DeletedUTC = parseNullTime(deleted) // Load condition groups @@ -110,14 +113,17 @@ func (r *RuleRepository) getConditionGroups(ruleID string) ([]models.RuleConditi for rows.Next() { var g models.RuleConditionGroup var isEnabled, negate int + var createdStr, updatedStr string if err := rows.Scan( &g.ID, &g.RuleID, &g.Name, &isEnabled, &g.JoinOperator, - &g.SortOrder, &negate, &g.CreatedUTC, &g.UpdatedUTC, + &g.SortOrder, &negate, &createdStr, &updatedStr, ); err != nil { return nil, err } g.IsEnabled = intToBool(isEnabled) g.Negate = intToBool(negate) + g.CreatedUTC = parseTimeOrZero(createdStr) + g.UpdatedUTC = parseTimeOrZero(updatedStr) // Load conditions for this group g.Conditions, err = r.getConditions(g.ID) @@ -148,16 +154,19 @@ func (r *RuleRepository) getConditions(groupID string) ([]models.RuleCondition, for rows.Next() { var c models.RuleCondition var isEnabled, negate, caseSensitive int + var createdStr, updatedStr string if err := rows.Scan( &c.ID, &c.ConditionGroupID, &isEnabled, &c.AttributeName, &c.Operator, &c.ValueType, &c.ComparisonValue, &c.CustomLdapExpression, - &negate, &c.SortOrder, &caseSensitive, &c.CreatedUTC, &c.UpdatedUTC, + &negate, &c.SortOrder, &caseSensitive, &createdStr, &updatedStr, ); err != nil { return nil, err } c.IsEnabled = intToBool(isEnabled) c.Negate = intToBool(negate) c.CaseSensitive = intToBool(caseSensitive) + c.CreatedUTC = parseTimeOrZero(createdStr) + c.UpdatedUTC = parseTimeOrZero(updatedStr) conditions = append(conditions, c) } @@ -192,19 +201,22 @@ func (r *RuleRepository) List(offset, limit int) ([]models.Rule, int, error) { var rule models.Rule var isEnabled, stopOnError int var lastRun sql.NullString + var createdStr, updatedStr string if err := rows.Scan( &rule.ID, &rule.Name, &rule.Description, &isEnabled, &rule.ADConnectionID, &rule.ObjectType, &rule.BaseDNOverride, &rule.SearchScopeOverride, &rule.ScheduleID, &rule.ExecutionMode, &rule.GroupJoinOperator, &rule.MaxParallelism, &stopOnError, &lastRun, &rule.LastRunResult, - &rule.CreatedUTC, &rule.UpdatedUTC, + &createdStr, &updatedStr, ); err != nil { return nil, 0, err } rule.IsEnabled = intToBool(isEnabled) rule.StopOnError = intToBool(stopOnError) rule.LastRunUTC = parseNullTime(lastRun) + rule.CreatedUTC = parseTimeOrZero(createdStr) + rule.UpdatedUTC = parseTimeOrZero(updatedStr) rules = append(rules, rule) } @@ -288,13 +300,16 @@ func (r *RuleRepository) getActions(ruleID string) ([]models.RuleAction, error) for rows.Next() { var a models.RuleAction var isEnabled int + var createdStr, updatedStr string if err := rows.Scan( &a.ID, &a.RuleID, &a.ActionType, &isEnabled, &a.SortOrder, - &a.ConfigurationJSON, &a.RollbackMode, &a.CreatedUTC, &a.UpdatedUTC, + &a.ConfigurationJSON, &a.RollbackMode, &createdStr, &updatedStr, ); err != nil { return nil, err } a.IsEnabled = intToBool(isEnabled) + a.CreatedUTC = parseTimeOrZero(createdStr) + a.UpdatedUTC = parseTimeOrZero(updatedStr) actions = append(actions, a) } diff --git a/backend/internal/repository/schedules.go b/backend/internal/repository/schedules.go index 3c08adf..0099526 100644 --- a/backend/internal/repository/schedules.go +++ b/backend/internal/repository/schedules.go @@ -48,6 +48,7 @@ func (r *ScheduleRepository) GetByID(id string) (*models.Schedule, error) { sched := &models.Schedule{} var isEnabled int var nextRun, deleted sql.NullString + var createdStr, updatedStr string err := r.db.QueryRow(` SELECT id, name, description, is_enabled, schedule_kind, @@ -58,7 +59,7 @@ func (r *ScheduleRepository) GetByID(id string) (*models.Schedule, error) { &sched.ID, &sched.Name, &sched.Description, &isEnabled, &sched.ScheduleKind, &sched.EasyIntervalValue, &sched.EasyIntervalUnit, &sched.CronExpression, &sched.TimezoneMode, &nextRun, - &sched.CreatedUTC, &sched.UpdatedUTC, &deleted, + &createdStr, &updatedStr, &deleted, ) if err == sql.ErrNoRows { return nil, nil @@ -69,6 +70,8 @@ func (r *ScheduleRepository) GetByID(id string) (*models.Schedule, error) { sched.IsEnabled = intToBool(isEnabled) sched.NextRunUTC = parseNullTime(nextRun) + sched.CreatedUTC = parseTimeOrZero(createdStr) + sched.UpdatedUTC = parseTimeOrZero(updatedStr) sched.DeletedUTC = parseNullTime(deleted) return sched, nil } @@ -100,16 +103,19 @@ func (r *ScheduleRepository) List(offset, limit int) ([]models.Schedule, int, er var sched models.Schedule var isEnabled int var nextRun sql.NullString + var createdStr, updatedStr string if err := rows.Scan( &sched.ID, &sched.Name, &sched.Description, &isEnabled, &sched.ScheduleKind, &sched.EasyIntervalValue, &sched.EasyIntervalUnit, &sched.CronExpression, &sched.TimezoneMode, &nextRun, - &sched.CreatedUTC, &sched.UpdatedUTC, + &createdStr, &updatedStr, ); err != nil { return nil, 0, err } sched.IsEnabled = intToBool(isEnabled) sched.NextRunUTC = parseNullTime(nextRun) + sched.CreatedUTC = parseTimeOrZero(createdStr) + sched.UpdatedUTC = parseTimeOrZero(updatedStr) scheds = append(scheds, sched) } diff --git a/backend/internal/repository/timestamps_test.go b/backend/internal/repository/timestamps_test.go new file mode 100644 index 0000000..81190af --- /dev/null +++ b/backend/internal/repository/timestamps_test.go @@ -0,0 +1,76 @@ +package repository + +import ( + "database/sql" + "path/filepath" + "testing" + + "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/db" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" +) + +// migratedDB spins up a temp-file database with the full schema applied. +func migratedDB(t *testing.T) *sql.DB { + t.Helper() + database, err := db.New(config.DatabaseConfig{ + Path: filepath.Join(t.TempDir(), "repo_test.db"), + MaxOpenConns: 1, + MaxIdleConns: 1, + WALMode: true, + ForeignKeys: true, + }, logging.Default()) + if err != nil { + t.Fatalf("db.New: %v", err) + } + t.Cleanup(func() { database.Close() }) + if err := database.Migrate(); err != nil { + t.Fatalf("migrate: %v", err) + } + return database.Conn() +} + +// TestListTimestampsParse guards the modernc.org/sqlite switch: unlike the CGO +// mattn driver, modernc returns TEXT timestamp columns as strings, so a Scan +// straight into time.Time fails ("unsupported Scan, storing driver.Value type +// string into type *time.Time"). This exercises the credential and connection +// List paths and asserts the timestamps come back parsed. +func TestListTimestampsParse(t *testing.T) { + conn := migratedDB(t) + + credRepo := NewCredentialRepository(conn) + cred := &models.Credential{Name: "t-cred", CredentialType: "UsernamePassword", IsEnabled: true} + if err := credRepo.Create(cred); err != nil { + t.Fatalf("create credential: %v", err) + } + creds, _, err := credRepo.List(0, 50) + if err != nil { + t.Fatalf("list credentials: %v", err) + } + if len(creds) != 1 { + t.Fatalf("expected 1 credential, got %d", len(creds)) + } + if creds[0].CreatedUTC.IsZero() || creds[0].UpdatedUTC.IsZero() { + t.Errorf("credential timestamps not parsed: created=%v updated=%v", creds[0].CreatedUTC, creds[0].UpdatedUTC) + } + + connRepo := NewConnectionRepository(conn) + adc := &models.ADConnection{ + Name: "t-conn", Hosts: "dc1", Port: 389, RootDN: "DC=x,DC=y", + DefaultSearchScope: "Subtree", TimeoutSeconds: 15, PageSize: 1000, IsEnabled: true, + } + if err := connRepo.Create(adc); err != nil { + t.Fatalf("create connection: %v", err) + } + conns, _, err := connRepo.List(0, 50) + if err != nil { + t.Fatalf("list connections: %v", err) + } + if len(conns) != 1 { + t.Fatalf("expected 1 connection, got %d", len(conns)) + } + if conns[0].CreatedUTC.IsZero() || conns[0].UpdatedUTC.IsZero() { + t.Errorf("connection timestamps not parsed: created=%v updated=%v", conns[0].CreatedUTC, conns[0].UpdatedUTC) + } +} -- 2.52.0 From 6cb8e67c0c1eee7a47b9620361fb94af08ab6fb0 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 14:08:44 -0400 Subject: [PATCH 18/34] feat(ui): remove language selector from header Drop the flag/language dropdown from both the vertical and horizontal headers and delete the now-unused Language component. OrchestrAD ships a single language, so the selector was dead UI chrome. Co-Authored-By: Claude Opus 4.8 --- .../(app)/layout/horizontal/header/Header.tsx | 2 - .../(app)/layout/vertical/header/Header.tsx | 2 - .../(app)/layout/vertical/header/Language.tsx | 98 ------------------- 3 files changed, 102 deletions(-) delete mode 100644 frontend/src/app/(app)/layout/vertical/header/Language.tsx diff --git a/frontend/src/app/(app)/layout/horizontal/header/Header.tsx b/frontend/src/app/(app)/layout/horizontal/header/Header.tsx index f21d51d..e182c6b 100644 --- a/frontend/src/app/(app)/layout/horizontal/header/Header.tsx +++ b/frontend/src/app/(app)/layout/horizontal/header/Header.tsx @@ -12,7 +12,6 @@ import { IconMenu2 } from "@tabler/icons-react"; import Notifications from "../../vertical/header/Notification"; import Profile from "../../vertical/header/Profile"; import Search from "../../vertical/header/Search"; -import Language from "../../vertical/header/Language"; import Logo from "../../shared/logo/Logo"; import config from "@/app/context/config"; @@ -77,7 +76,6 @@ export default function Header() { {/* Search Dropdown */} {/* ------------------------------------------- */} - {activeMode === 'light' ? ( setActiveMode("dark")} /> diff --git a/frontend/src/app/(app)/layout/vertical/header/Header.tsx b/frontend/src/app/(app)/layout/vertical/header/Header.tsx index 3c7b310..d63a893 100644 --- a/frontend/src/app/(app)/layout/vertical/header/Header.tsx +++ b/frontend/src/app/(app)/layout/vertical/header/Header.tsx @@ -11,7 +11,6 @@ import { Icon } from "@iconify/react"; import Notifications from "./Notification"; import Profile from "./Profile"; import Search from "./Search"; -import Language from "./Language"; import { shadows } from "@/utils/theme/Shadows"; @@ -79,7 +78,6 @@ const Header = () => { {smUp ? : ""} - {activeMode === 'light' ? ( setActiveMode("dark")} /> diff --git a/frontend/src/app/(app)/layout/vertical/header/Language.tsx b/frontend/src/app/(app)/layout/vertical/header/Language.tsx deleted file mode 100644 index 766cbcb..0000000 --- a/frontend/src/app/(app)/layout/vertical/header/Language.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useContext } from 'react'; -import Avatar from '@mui/material/Avatar'; -import Button from '@mui/material/Button'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import Typography from '@mui/material/Typography'; -import { CustomizerContext } from '@/app/context/customizerContext'; - -import { Stack } from '@mui/system'; -import { useTranslation } from 'react-i18next'; -import { useEffect } from 'react'; - - -const Languages = [ - { - flagname: 'English (UK)', - icon: "/images/flag/icon-flag-en.svg", - value: 'en', - }, - { - flagname: 'δΈ­ε›½δΊΊ (Chinese)', - icon: "/images/flag/icon-flag-cn.svg", - value: 'ch', - }, - { - flagname: 'franΓ§ais (French)', - icon: "/images/flag/icon-flag-fr.svg", - value: 'fr', - }, - - { - flagname: 'عربي (Arabic)', - icon: "/images/flag/icon-flag-sa.svg", - value: 'ar', - }, -]; - -const Language = () => { - const [anchorEl, setAnchorEl] = React.useState(null); - const { isLanguage, setIsLanguage } = useContext(CustomizerContext); - - const open = Boolean(anchorEl); - - const currentLang = - Languages.find((_lang) => _lang.value === isLanguage) || Languages[1]; - const { i18n } = useTranslation(); - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - const handleClose = () => { - setAnchorEl(null); - }; - useEffect(() => { - i18n.changeLanguage(isLanguage); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - <> - - - {Languages.map((option, index) => ( - setIsLanguage(option.value)} - > - - - {option.flagname} - - - ))} - - - ); -}; - -export default Language; -- 2.52.0 From e74cb454c822e378aaf6ca0b1826beb14c258aea Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 14:09:04 -0400 Subject: [PATCH 19/34] feat(activity): add action-ledger intelligence and drill-in feed Turn the rule_run_actions ledger into operator intelligence. A new ActivityService rolls the ledger up by time window (24h / 7d / all-time) into syncs, operations and removals with success/failure counts and the number of distinct objects affected, plus all-time totals per action type and a most-active-rules ranking. A companion filtered, paginated feed answers the operational questions directly: what action ran, what happened, to which object, triggered by whom, and when. Backend: - ActivityService.Summary() and ListActions(filter) over the joined rule_run_actions / rule_runs / rules tables, categorising each action type as Sync (AddToGroup/AddGroupToGroup), Removal (RemoveFromGroupIfNoLongerMatched) or Operation (everything else). - GET /api/v1/activity/summary and GET /api/v1/activity (category, actionType, status, ruleId, search, pagination). - Table-driven test seeding a run with mixed action types/statuses and asserting window roll-ups, distinct-object counts, top rules, and the category/status feed filters. Frontend: - New Activity page: window summary cards, by-action-type and most-active-rules tables, and a drill-in feed with category/result/ object filters and a detail dialog that pretty-prints the action's details JSON and error. - Wired into the vertical sidebar and horizontal navbar under Automation. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/activity_handlers.go | 53 +++ backend/internal/cli/cli.go | 1 + backend/internal/server/server.go | 8 + backend/internal/services/activity_service.go | 350 ++++++++++++++++++ .../services/activity_service_test.go | 131 +++++++ .../(app)/activity/ActivityDetailDialog.tsx | 104 ++++++ frontend/src/app/(app)/activity/categories.ts | 30 ++ frontend/src/app/(app)/activity/page.tsx | 285 ++++++++++++++ .../layout/horizontal/navbar/Menudata.ts | 1 + .../layout/vertical/sidebar/MenuItems.ts | 7 + frontend/src/lib/api/resources.ts | 8 + frontend/src/lib/api/types.ts | 58 +++ 12 files changed, 1036 insertions(+) create mode 100644 backend/internal/api/activity_handlers.go create mode 100644 backend/internal/services/activity_service.go create mode 100644 backend/internal/services/activity_service_test.go create mode 100644 frontend/src/app/(app)/activity/ActivityDetailDialog.tsx create mode 100644 frontend/src/app/(app)/activity/categories.ts create mode 100644 frontend/src/app/(app)/activity/page.tsx diff --git a/backend/internal/api/activity_handlers.go b/backend/internal/api/activity_handlers.go new file mode 100644 index 0000000..a1f52e2 --- /dev/null +++ b/backend/internal/api/activity_handlers.go @@ -0,0 +1,53 @@ +// Package api - activity intelligence handlers +package api + +import ( + "net/http" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/services" +) + +// ActivityHandler serves aggregated action intelligence and the drill-in feed. +type ActivityHandler struct { + service *services.ActivityService + logger *logging.Logger +} + +// NewActivityHandler creates a new ActivityHandler. +func NewActivityHandler(service *services.ActivityService, logger *logging.Logger) *ActivityHandler { + return &ActivityHandler{service: service, logger: logger} +} + +// Summary handles GET /api/v1/activity/summary +func (h *ActivityHandler) Summary(w http.ResponseWriter, r *http.Request) { + summary, err := h.service.Summary() + if err != nil { + h.logger.Error("ActivityHandler", "Summary failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to compute activity summary") + return + } + WriteJSON(w, http.StatusOK, summary) +} + +// List handles GET /api/v1/activity β€” the filtered, paginated action feed. +func (h *ActivityHandler) List(w http.ResponseWriter, r *http.Request) { + p := ParsePagination(r) + q := r.URL.Query() + filter := services.ActivityFilter{ + Category: q.Get("category"), + ActionType: q.Get("actionType"), + Status: q.Get("status"), + RuleID: q.Get("ruleId"), + Search: q.Get("search"), + Offset: (p.Page - 1) * p.PageSize, + Limit: p.PageSize, + } + records, total, err := h.service.ListActions(filter) + if err != nil { + h.logger.Error("ActivityHandler", "List failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to list activity") + return + } + WriteList(w, records, p.Page, p.PageSize, total) +} diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index 3e4fd57..1230e9d 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -147,6 +147,7 @@ func runServer(ctx context.Context) error { BackupService: services.NewBackupService(database, filepath.Join(cfg.DataPath, "backups"), 10, logger), SettingsService: settingsService, DashboardService: services.NewDashboardService(database.Conn(), logger), + ActivityService: services.NewActivityService(database.Conn(), logger), ConfigService: services.NewConfigService(database.Conn(), logger), TLS: tlsManager, } diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 69eb69f..fdaf230 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -37,6 +37,7 @@ type Dependencies struct { BackupService *services.BackupService SettingsService *services.SettingsService DashboardService *services.DashboardService + ActivityService *services.ActivityService ConfigService *services.ConfigService AuditService *audit.Service RuleRepo *repository.RuleRepository @@ -269,6 +270,13 @@ func (s *Server) setupRoutes() { r.Get("/summary", dashboardHandler.Summary) }) + // Activity intelligence (action ledger roll-ups + drill-in feed) + activityHandler := api.NewActivityHandler(s.deps.ActivityService, s.logger) + r.Route("/activity", func(r chi.Router) { + r.Get("/", activityHandler.List) + r.Get("/summary", activityHandler.Summary) + }) + // Configuration export / import configHandler := api.NewConfigHandler(s.deps.ConfigService, s.deps.AuditService, s.logger) r.Route("/config", func(r chi.Router) { diff --git a/backend/internal/services/activity_service.go b/backend/internal/services/activity_service.go new file mode 100644 index 0000000..e8a4908 --- /dev/null +++ b/backend/internal/services/activity_service.go @@ -0,0 +1,350 @@ +// Package services - activity intelligence aggregation service. +// +// The activity service turns the raw rule_run_actions ledger into operator- +// friendly intelligence: how many syncs, operations and removals happened, to +// how many objects, and a drill-in feed answering what action ran, what +// happened, to which object, triggered by whom, and when. +package services + +import ( + "database/sql" + "strings" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" +) + +// Activity categories group the individual AD action types into the three +// buckets operators reason about. +const ( + CategorySync = "Sync" // membership additions (AddToGroup / AddGroupToGroup) + CategoryOperation = "Operation" // structural changes (MoveToOu / EnsureGroupExists) + CategoryRemoval = "Removal" // membership removals (RemoveFromGroupIfNoLongerMatched) +) + +// syncActionTypes / removalActionTypes drive both the SQL categorisation and +// the Go-side helpers so the two never drift apart. +var ( + syncActionTypes = []string{"AddToGroup", "AddGroupToGroup"} + removalActionTypes = []string{"RemoveFromGroupIfNoLongerMatched"} +) + +// categorize maps an action type to its activity category. +func categorize(actionType string) string { + for _, a := range syncActionTypes { + if a == actionType { + return CategorySync + } + } + for _, a := range removalActionTypes { + if a == actionType { + return CategoryRemoval + } + } + return CategoryOperation +} + +// ActivityWindow summarises action activity over a time window. +type ActivityWindow struct { + Key string `json:"key"` + Label string `json:"label"` + Total int `json:"total"` + Success int `json:"success"` + Failed int `json:"failed"` + Syncs int `json:"syncs"` + Operations int `json:"operations"` + Removals int `json:"removals"` + ObjectsAffected int `json:"objectsAffected"` +} + +// ActionTypeCount is an all-time roll-up for a single action type. +type ActionTypeCount struct { + ActionType string `json:"actionType"` + Category string `json:"category"` + Total int `json:"total"` + Success int `json:"success"` + Failed int `json:"failed"` +} + +// RuleActivityCount ranks rules by how much activity they generated. +type RuleActivityCount struct { + RuleID string `json:"ruleId"` + RuleName string `json:"ruleName"` + Total int `json:"total"` + Failed int `json:"failed"` +} + +// ActivitySummary is the composite response for the intelligence page header. +type ActivitySummary struct { + Windows []ActivityWindow `json:"windows"` + ByActionType []ActionTypeCount `json:"byActionType"` + TopRules []RuleActivityCount `json:"topRules"` + GeneratedUTC string `json:"generatedUtc"` +} + +// ActivityRecord is one row of the drill-in feed. +type ActivityRecord struct { + ID string `json:"id"` + RuleRunID string `json:"ruleRunId"` + RuleID string `json:"ruleId"` + RuleName string `json:"ruleName"` + ObjectDN string `json:"objectDn"` + ActionType string `json:"actionType"` + Category string `json:"category"` + Status string `json:"status"` + DetailsJSON *string `json:"detailsJson,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty"` + DurationMS *int `json:"durationMs,omitempty"` + TriggeredBy *string `json:"triggeredBy,omitempty"` + CreatedUTC string `json:"createdUtc"` +} + +// ActivityFilter narrows the drill-in feed. +type ActivityFilter struct { + Category string + ActionType string + Status string + RuleID string + Search string + Offset int + Limit int +} + +// ActivityService aggregates read-only intelligence from the action ledger. +type ActivityService struct { + db *sql.DB + logger *logging.Logger +} + +// NewActivityService creates a new ActivityService. +func NewActivityService(db *sql.DB, logger *logging.Logger) *ActivityService { + return &ActivityService{db: db, logger: logger} +} + +// caseSum builds a "COALESCE(SUM(CASE WHEN THEN 1 ELSE 0 END),0)" fragment. +func caseSum(cond string) string { + return "COALESCE(SUM(CASE WHEN " + cond + " THEN 1 ELSE 0 END),0)" +} + +// inList renders a quoted SQL IN(...) list from action-type constants. The +// values are compile-time constants, never user input, so inlining is safe. +func inList(values []string) string { + quoted := make([]string, len(values)) + for i, v := range values { + quoted[i] = "'" + v + "'" + } + return "(" + strings.Join(quoted, ",") + ")" +} + +// Summary returns the composite intelligence payload. +func (s *ActivityService) Summary() (*ActivitySummary, error) { + now := time.Now().UTC() + out := &ActivitySummary{GeneratedUTC: now.Format(time.RFC3339)} + + windows := []struct { + key, label string + since *time.Time + }{ + {"24h", "Last 24 hours", ptrTime(now.Add(-24 * time.Hour))}, + {"7d", "Last 7 days", ptrTime(now.Add(-7 * 24 * time.Hour))}, + {"all", "All time", nil}, + } + for _, w := range windows { + win, err := s.loadWindow(w.key, w.label, w.since) + if err != nil { + return nil, err + } + out.Windows = append(out.Windows, win) + } + + byType, err := s.loadByActionType() + if err != nil { + return nil, err + } + out.ByActionType = byType + + topRules, err := s.loadTopRules(8) + if err != nil { + return nil, err + } + out.TopRules = topRules + + return out, nil +} + +func (s *ActivityService) loadWindow(key, label string, since *time.Time) (ActivityWindow, error) { + win := ActivityWindow{Key: key, Label: label} + query := ` + SELECT COUNT(*), + ` + caseSum("ra.status = 'Succeeded'") + `, + ` + caseSum("ra.status = 'Failed'") + `, + ` + caseSum("ra.action_type IN "+inList(syncActionTypes)) + `, + ` + caseSum("ra.action_type IN "+inList(removalActionTypes)) + `, + COUNT(DISTINCT ra.object_dn) + FROM rule_run_actions ra` + var args []any + if since != nil { + query += ` WHERE ra.created_utc >= ?` + args = append(args, since.Format(time.RFC3339)) + } + var syncs, removals int + if err := s.db.QueryRow(query, args...).Scan( + &win.Total, &win.Success, &win.Failed, &syncs, &removals, &win.ObjectsAffected, + ); err != nil { + return win, err + } + win.Syncs = syncs + win.Removals = removals + win.Operations = win.Total - syncs - removals + return win, nil +} + +func (s *ActivityService) loadByActionType() ([]ActionTypeCount, error) { + rows, err := s.db.Query(` + SELECT ra.action_type, COUNT(*), + ` + caseSum("ra.status = 'Succeeded'") + `, + ` + caseSum("ra.status = 'Failed'") + ` + FROM rule_run_actions ra + GROUP BY ra.action_type + ORDER BY COUNT(*) DESC`) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]ActionTypeCount, 0) + for rows.Next() { + var c ActionTypeCount + if err := rows.Scan(&c.ActionType, &c.Total, &c.Success, &c.Failed); err != nil { + return nil, err + } + c.Category = categorize(c.ActionType) + out = append(out, c) + } + return out, rows.Err() +} + +func (s *ActivityService) loadTopRules(limit int) ([]RuleActivityCount, error) { + rows, err := s.db.Query(` + SELECT r.id, r.name, COUNT(*), + `+caseSum("ra.status = 'Failed'")+` + FROM rule_run_actions ra + JOIN rule_runs rr ON rr.id = ra.rule_run_id + JOIN rules r ON r.id = rr.rule_id + GROUP BY r.id, r.name + ORDER BY COUNT(*) DESC + LIMIT ?`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]RuleActivityCount, 0) + for rows.Next() { + var c RuleActivityCount + if err := rows.Scan(&c.RuleID, &c.RuleName, &c.Total, &c.Failed); err != nil { + return nil, err + } + out = append(out, c) + } + return out, rows.Err() +} + +// ListActions returns a filtered, paginated slice of the action feed plus the +// total matching count. +func (s *ActivityService) ListActions(f ActivityFilter) ([]ActivityRecord, int, error) { + where := []string{"1=1"} + var args []any + + switch f.Category { + case CategorySync: + where = append(where, "ra.action_type IN "+inList(syncActionTypes)) + case CategoryRemoval: + where = append(where, "ra.action_type IN "+inList(removalActionTypes)) + case CategoryOperation: + where = append(where, "ra.action_type NOT IN "+inList(append(append([]string{}, syncActionTypes...), removalActionTypes...))) + } + if f.ActionType != "" { + where = append(where, "ra.action_type = ?") + args = append(args, f.ActionType) + } + if f.Status != "" { + where = append(where, "ra.status = ?") + args = append(args, f.Status) + } + if f.RuleID != "" { + where = append(where, "rr.rule_id = ?") + args = append(args, f.RuleID) + } + if f.Search != "" { + where = append(where, "ra.object_dn LIKE ?") + args = append(args, "%"+f.Search+"%") + } + clause := strings.Join(where, " AND ") + + var total int + countQuery := ` + SELECT COUNT(*) + FROM rule_run_actions ra + JOIN rule_runs rr ON rr.id = ra.rule_run_id + JOIN rules r ON r.id = rr.rule_id + WHERE ` + clause + if err := s.db.QueryRow(countQuery, args...).Scan(&total); err != nil { + return nil, 0, err + } + + limit := f.Limit + if limit <= 0 { + limit = 50 + } + listArgs := append(append([]any{}, args...), limit, f.Offset) + rows, err := s.db.Query(` + SELECT ra.id, ra.rule_run_id, rr.rule_id, r.name, ra.object_dn, + ra.action_type, ra.status, ra.details_json, ra.error_message, + ra.duration_ms, rr.triggered_by, ra.created_utc + FROM rule_run_actions ra + JOIN rule_runs rr ON rr.id = ra.rule_run_id + JOIN rules r ON r.id = rr.rule_id + WHERE `+clause+` + ORDER BY ra.created_utc DESC + LIMIT ? OFFSET ?`, listArgs...) + if err != nil { + return nil, 0, err + } + defer rows.Close() + + out := make([]ActivityRecord, 0, limit) + for rows.Next() { + var rec ActivityRecord + var details, errMsg, triggeredBy sql.NullString + var duration sql.NullInt64 + if err := rows.Scan( + &rec.ID, &rec.RuleRunID, &rec.RuleID, &rec.RuleName, &rec.ObjectDN, + &rec.ActionType, &rec.Status, &details, &errMsg, + &duration, &triggeredBy, &rec.CreatedUTC, + ); err != nil { + return nil, 0, err + } + rec.Category = categorize(rec.ActionType) + if details.Valid { + v := details.String + rec.DetailsJSON = &v + } + if errMsg.Valid { + v := errMsg.String + rec.ErrorMessage = &v + } + if duration.Valid { + v := int(duration.Int64) + rec.DurationMS = &v + } + if triggeredBy.Valid { + v := triggeredBy.String + rec.TriggeredBy = &v + } + out = append(out, rec) + } + return out, total, rows.Err() +} + +func ptrTime(t time.Time) *time.Time { return &t } diff --git a/backend/internal/services/activity_service_test.go b/backend/internal/services/activity_service_test.go new file mode 100644 index 0000000..cc826de --- /dev/null +++ b/backend/internal/services/activity_service_test.go @@ -0,0 +1,131 @@ +package services + +import ( + "path/filepath" + "testing" + + "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/db" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/repository" +) + +func TestActivityServiceSummaryAndFeed(t *testing.T) { + database, err := db.New(config.DatabaseConfig{ + Path: filepath.Join(t.TempDir(), "activity_test.db"), + MaxOpenConns: 1, + MaxIdleConns: 1, + WALMode: true, + ForeignKeys: true, + }, logging.Default()) + if err != nil { + t.Fatalf("db.New: %v", err) + } + defer database.Close() + if err := database.Migrate(); err != nil { + t.Fatalf("migrate: %v", err) + } + conn := database.Conn() + + // A connection satisfies the rules.ad_connection_id FK. + connRepo := repository.NewConnectionRepository(conn) + adc := &models.ADConnection{ + Name: "act-conn", Hosts: "dc1", Port: 389, RootDN: "DC=y", + DefaultSearchScope: "Subtree", TimeoutSeconds: 15, PageSize: 1000, IsEnabled: true, + } + if err := connRepo.Create(adc); err != nil { + t.Fatalf("create connection: %v", err) + } + + ruleRepo := repository.NewRuleRepository(conn) + rule := &models.Rule{Name: "act-rule", ObjectType: "User", ADConnectionID: adc.ID, ExecutionMode: "Automatic", GroupJoinOperator: "AND"} + if err := ruleRepo.Create(rule); err != nil { + t.Fatalf("create rule: %v", err) + } + + // A rule_action satisfies the rule_run_actions.rule_action_id FK. + actionID := "act-action-1" + if _, err := conn.Exec(` + INSERT INTO rule_actions (id, rule_id, action_type, is_enabled, sort_order, configuration_json, rollback_mode, created_utc, updated_utc) + VALUES (?, ?, 'AddToGroup', 1, 0, '{}', 'None', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')`, + actionID, rule.ID); err != nil { + t.Fatalf("insert rule_action: %v", err) + } + + runRepo := repository.NewRuleRunRepository(conn) + who := "tester" + run := &models.RuleRun{RuleID: rule.ID, Status: "Success", ExecutionMode: "Automatic", TriggeredBy: &who} + if err := runRepo.Create(run); err != nil { + t.Fatalf("create run: %v", err) + } + + // Two syncs (one failed), one operation, one removal. + actions := []struct { + actionType, status, dn string + }{ + {"AddToGroup", "Succeeded", "CN=Alice,OU=x,DC=y"}, + {"AddToGroup", "Failed", "CN=Bob,OU=x,DC=y"}, + {"MoveToOu", "Succeeded", "CN=Alice,OU=x,DC=y"}, + {"RemoveFromGroupIfNoLongerMatched", "Succeeded", "CN=Carol,OU=x,DC=y"}, + } + for _, a := range actions { + if err := runRepo.CreateAction(&models.RuleRunAction{ + RuleRunID: run.ID, RuleActionID: actionID, ObjectDN: a.dn, ActionType: a.actionType, Status: a.status, + }); err != nil { + t.Fatalf("create action: %v", err) + } + } + + svc := NewActivityService(conn, logging.Default()) + + sum, err := svc.Summary() + if err != nil { + t.Fatalf("summary: %v", err) + } + all := findWindow(t, sum.Windows, "all") + if all.Total != 4 || all.Syncs != 2 || all.Operations != 1 || all.Removals != 1 { + t.Errorf("window totals wrong: %+v", all) + } + if all.Failed != 1 || all.Success != 3 { + t.Errorf("window success/failed wrong: %+v", all) + } + if all.ObjectsAffected != 3 { // Alice, Bob, Carol + t.Errorf("objectsAffected = %d, want 3", all.ObjectsAffected) + } + if len(sum.TopRules) != 1 || sum.TopRules[0].Total != 4 || sum.TopRules[0].Failed != 1 { + t.Errorf("topRules wrong: %+v", sum.TopRules) + } + + // Removal category filter should return exactly the one removal action. + recs, total, err := svc.ListActions(ActivityFilter{Category: CategoryRemoval, Limit: 50}) + if err != nil { + t.Fatalf("list removals: %v", err) + } + if total != 1 || len(recs) != 1 || recs[0].ActionType != "RemoveFromGroupIfNoLongerMatched" { + t.Errorf("removal filter wrong: total=%d recs=%+v", total, recs) + } + if recs[0].TriggeredBy == nil || *recs[0].TriggeredBy != who { + t.Errorf("triggeredBy not threaded through: %+v", recs[0].TriggeredBy) + } + + // Failed status filter should surface the single failed sync. + failed, ftotal, err := svc.ListActions(ActivityFilter{Status: "Failed", Limit: 50}) + if err != nil { + t.Fatalf("list failed: %v", err) + } + if ftotal != 1 || len(failed) != 1 || failed[0].Category != CategorySync { + t.Errorf("failed filter wrong: total=%d recs=%+v", ftotal, failed) + } +} + +func findWindow(t *testing.T, windows []ActivityWindow, key string) ActivityWindow { + t.Helper() + for _, w := range windows { + if w.Key == key { + return w + } + } + t.Fatalf("window %q not found", key) + return ActivityWindow{} +} diff --git a/frontend/src/app/(app)/activity/ActivityDetailDialog.tsx b/frontend/src/app/(app)/activity/ActivityDetailDialog.tsx new file mode 100644 index 0000000..c85520d --- /dev/null +++ b/frontend/src/app/(app)/activity/ActivityDetailDialog.tsx @@ -0,0 +1,104 @@ +"use client"; + +import Chip from "@mui/material/Chip"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Divider from "@mui/material/Divider"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; + +import type { ActivityRecord } from "@/lib/api/types"; +import { formatDateTime, formatDurationMs, runStatusColor } from "@/lib/format"; +import { categoryColor } from "./categories"; + +function prettyJson(raw?: string): string | null { + if (!raw) return null; + try { + return JSON.stringify(JSON.parse(raw), null, 2); + } catch { + return raw; + } +} + +function Field({ label, value }: { label: string; value: React.ReactNode }) { + return ( + + + {label} + + + {value} + + + ); +} + +export default function ActivityDetailDialog({ + record, + onClose, +}: { + record: ActivityRecord | null; + onClose: () => void; +}) { + const details = prettyJson(record?.detailsJson); + return ( + + {record && ( + <> + + + {record.actionType} + + + + + + + + + + + + + + + {record.errorMessage && ( + <> + + Error + + {record.errorMessage} + + + )} + + {details && ( + <> + + Details + + {details} + + + )} + + + + )} + + ); +} diff --git a/frontend/src/app/(app)/activity/categories.ts b/frontend/src/app/(app)/activity/categories.ts new file mode 100644 index 0000000..de2cfd0 --- /dev/null +++ b/frontend/src/app/(app)/activity/categories.ts @@ -0,0 +1,30 @@ +// Shared category presentation helpers for the activity intelligence page. +import type { ChipColor } from "@/lib/format"; + +export const CATEGORIES = ["Sync", "Operation", "Removal"] as const; + +export function categoryColor(category: string): ChipColor { + switch (category) { + case "Sync": + return "info"; + case "Removal": + return "warning"; + case "Operation": + return "secondary"; + default: + return "default"; + } +} + +export function categoryLabel(category: string): string { + switch (category) { + case "Sync": + return "Syncs"; + case "Operation": + return "Operations"; + case "Removal": + return "Removals"; + default: + return category; + } +} diff --git a/frontend/src/app/(app)/activity/page.tsx b/frontend/src/app/(app)/activity/page.tsx new file mode 100644 index 0000000..1db2f36 --- /dev/null +++ b/frontend/src/app/(app)/activity/page.tsx @@ -0,0 +1,285 @@ +"use client"; + +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Chip from "@mui/material/Chip"; +import CircularProgress from "@mui/material/CircularProgress"; +import Grid from "@mui/material/Grid"; +import IconButton from "@mui/material/IconButton"; +import MenuItem from "@mui/material/MenuItem"; +import Stack from "@mui/material/Stack"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; +import RefreshOutlinedIcon from "@mui/icons-material/RefreshOutlined"; +import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; +import { useCallback, useEffect, useState } from "react"; + +import PageContainer from "@/app/components/container/PageContainer"; +import DashboardCard from "@/app/components/shared/DashboardCard"; +import { ApiError } from "@/lib/api/client"; +import { ActivityApi } from "@/lib/api/resources"; +import type { ActivityRecord, ActivitySummary, ActivityWindow } from "@/lib/api/types"; +import { formatDateTime, runStatusColor } from "@/lib/format"; + +import ActivityDetailDialog from "./ActivityDetailDialog"; +import { CATEGORIES, categoryColor, categoryLabel } from "./categories"; + +const STATUS_OPTIONS = ["", "Succeeded", "Failed"]; + +function WindowCard({ w }: { w: ActivityWindow }) { + const stat = (label: string, value: number, color?: string) => ( + + {value} + {label} + + ); + return ( + + + {stat("Total", w.total)} + {stat("Syncs", w.syncs, "info.main")} + {stat("Operations", w.operations, "secondary.main")} + {stat("Removals", w.removals, "warning.main")} + {stat("Failed", w.failed, "error.main")} + + + ); +} + +export default function ActivityPage() { + const [summary, setSummary] = useState(null); + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(25); + const [category, setCategory] = useState(""); + const [status, setStatus] = useState(""); + const [search, setSearch] = useState(""); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [detail, setDetail] = useState(null); + + const loadSummary = useCallback(() => { + ActivityApi.summary().then(setSummary).catch(() => setSummary(null)); + }, []); + + const loadFeed = useCallback(async () => { + setLoading(true); + setError(null); + try { + const res = await ActivityApi.list({ + page: page + 1, + pageSize, + category: category || undefined, + status: status || undefined, + search: search || undefined, + }); + setItems(res.items); + setTotal(Number(res.meta?.totalCount ?? res.items.length)); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to load activity"); + } finally { + setLoading(false); + } + }, [page, pageSize, category, status, search]); + + useEffect(() => { loadSummary(); }, [loadSummary]); + useEffect(() => { void loadFeed(); }, [loadFeed]); + + const refresh = () => { loadSummary(); void loadFeed(); }; + + return ( + + + {/* Summary windows */} + + {(summary?.windows ?? []).map((w) => ( + + + + ))} + + {/* By action type roll-up */} + + + {summary && summary.byActionType.length > 0 ? ( + + + + + Action + Category + Total + Ok + Failed + + + + {summary.byActionType.map((a) => ( + + {a.actionType} + + + + {a.total} + {a.success} + + {a.failed > 0 ? {a.failed} : 0} + + + ))} + +
    +
    + ) : ( + No activity recorded yet. + )} +
    +
    + + {/* Most active rules */} + + + {summary && summary.topRules.length > 0 ? ( + + + + + Rule + Actions + Failed + + + + {summary.topRules.map((r) => ( + + {r.ruleName} + {r.total} + + {r.failed > 0 ? {r.failed} : 0} + + + ))} + +
    +
    + ) : ( + No activity recorded yet. + )} +
    +
    +
    + + {/* Drill-in feed */} + + + + + + } + > + + {error && ( + setError(null)}>{error} + )} + + + { setCategory(e.target.value); setPage(0); }} + > + All categories + {CATEGORIES.map((c) => {categoryLabel(c)})} + + { setStatus(e.target.value); setPage(0); }} + > + {STATUS_OPTIONS.map((s) => ( + {s || "All results"} + ))} + + { setSearch(e.target.value); setPage(0); }} + placeholder="CN=…,OU=…" + /> + + + {loading && items.length === 0 ? ( + + ) : items.length === 0 ? ( + No activity matches the current filters. + ) : ( + + + + + When + Category + Action + Object + Rule + Result + By + Details + + + + {items.map((a) => ( + + {formatDateTime(a.createdUtc)} + + + + {a.actionType} + + {a.objectDn} + + {a.ruleName} + + + + {a.triggeredBy ?? "β€”"} + + + setDetail(a)}> + + + + + + ))} + +
    + setPage(p)} + rowsPerPage={pageSize} + onRowsPerPageChange={(e) => { setPageSize(parseInt(e.target.value, 10)); setPage(0); }} + rowsPerPageOptions={[10, 25, 50, 100]} + /> +
    + )} +
    +
    +
    + + setDetail(null)} /> +
    + ); +} diff --git a/frontend/src/app/(app)/layout/horizontal/navbar/Menudata.ts b/frontend/src/app/(app)/layout/horizontal/navbar/Menudata.ts index 0342f26..0b3d219 100644 --- a/frontend/src/app/(app)/layout/horizontal/navbar/Menudata.ts +++ b/frontend/src/app/(app)/layout/horizontal/navbar/Menudata.ts @@ -18,6 +18,7 @@ const Menuitems = [ { id: uniqueId(), title: 'Rules', href: '/rules' }, { id: uniqueId(), title: 'Schedules', href: '/schedules' }, { id: uniqueId(), title: 'Rule Runs', href: '/rule-runs' }, + { id: uniqueId(), title: 'Activity', href: '/activity' }, ], }, { diff --git a/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts b/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts index 70d1c07..c123e1d 100644 --- a/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts +++ b/frontend/src/app/(app)/layout/vertical/sidebar/MenuItems.ts @@ -38,6 +38,13 @@ const Menuitems: NavGroup[] = [ href: "/rule-runs", bgcolor: "secondary", }, + { + id: uniqueId(), + title: "Activity", + icon: "chart-2-line-duotone", + href: "/activity", + bgcolor: "primary", + }, { navlabel: true, subheader: "Directory", diff --git a/frontend/src/lib/api/resources.ts b/frontend/src/lib/api/resources.ts index 57c1f0b..0466f68 100644 --- a/frontend/src/lib/api/resources.ts +++ b/frontend/src/lib/api/resources.ts @@ -12,6 +12,9 @@ import { Credential, CredentialTestResult, DashboardSummary, + ActivitySummary, + ActivityRecord, + ActivityFilter, ImportReport, LoginResponse, Rule, @@ -130,6 +133,11 @@ export const RuleRunsApi = { get: (id: string) => api.get(`/api/v1/rule-runs/${id}`).then((r) => r.data), }; +export const ActivityApi = { + summary: () => api.get("/api/v1/activity/summary").then((r) => r.data), + list: (params?: PageParams & ActivityFilter) => getList("/api/v1/activity", params), +}; + export const UsersApi = { list: (params?: PageParams) => getList("/api/v1/users", params), get: (id: string) => api.get(`/api/v1/users/${id}`).then((r) => r.data), diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index b093072..12ff31f 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -268,6 +268,64 @@ export interface DashboardSummary { generatedUtc: string; } +export interface ActivityWindow { + key: string; + label: string; + total: number; + success: number; + failed: number; + syncs: number; + operations: number; + removals: number; + objectsAffected: number; +} + +export interface ActionTypeCount { + actionType: string; + category: string; + total: number; + success: number; + failed: number; +} + +export interface RuleActivityCount { + ruleId: string; + ruleName: string; + total: number; + failed: number; +} + +export interface ActivitySummary { + windows: ActivityWindow[]; + byActionType: ActionTypeCount[]; + topRules: RuleActivityCount[]; + generatedUtc: string; +} + +export interface ActivityRecord { + id: string; + ruleRunId: string; + ruleId: string; + ruleName: string; + objectDn: string; + actionType: string; + category: string; + status: string; + detailsJson?: string; + errorMessage?: string; + durationMs?: number; + triggeredBy?: string; + createdUtc: string; +} + +export interface ActivityFilter { + category?: string; + actionType?: string; + status?: string; + ruleId?: string; + search?: string; +} + export interface CredentialTestResult { success: boolean; message: string; -- 2.52.0 From 7effc09e8aa5b37974d2e108dba79bb35893b527 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 14:15:36 -0400 Subject: [PATCH 20/34] fix(connections): List must return the full connection shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ad_connections List query selected only a subset of columns, so use_start_tls, allow_invalid_certs, timeout_seconds, paging_enabled and page_size came back as zero-values in the list response. Because the edit form pre-populates directly from the list row, opening and saving a connection silently overwrote those stored values with their zero values β€” e.g. "Allow invalid certs" would never stick, and timeout/page size reset to 0. Select the full column set in List (matching GetByID) and scan the previously dropped fields. Extend the repository round-trip test to set these fields on create and assert List surfaces them. Co-Authored-By: Claude Opus 4.8 --- backend/internal/repository/connections.go | 16 +++++++++++----- backend/internal/repository/timestamps_test.go | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/backend/internal/repository/connections.go b/backend/internal/repository/connections.go index 23563a6..ba6ee6f 100644 --- a/backend/internal/repository/connections.go +++ b/backend/internal/repository/connections.go @@ -99,9 +99,11 @@ func (r *ConnectionRepository) List(offset, limit int) ([]models.ADConnection, i rows, err := r.db.Query(` SELECT id, name, description, is_enabled, hosts, port, - use_tls, root_dn, credential_id, default_search_scope, + use_tls, use_start_tls, allow_invalid_certs, + root_dn, bind_dn, credential_id, default_search_scope, + timeout_seconds, paging_enabled, page_size, last_tested_utc, last_test_result, created_utc, updated_utc - FROM ad_connections + FROM ad_connections WHERE deleted_utc IS NULL ORDER BY created_utc DESC LIMIT ? OFFSET ? @@ -114,13 +116,14 @@ func (r *ConnectionRepository) List(offset, limit int) ([]models.ADConnection, i var conns []models.ADConnection for rows.Next() { var conn models.ADConnection - var isEnabled, useTLS int + var isEnabled, useTLS, useStartTLS, allowInvalidCerts, pagingEnabled int var lastTested sql.NullString var createdStr, updatedStr string if err := rows.Scan( &conn.ID, &conn.Name, &conn.Description, &isEnabled, - &conn.Hosts, &conn.Port, &useTLS, &conn.RootDN, - &conn.CredentialID, &conn.DefaultSearchScope, + &conn.Hosts, &conn.Port, &useTLS, &useStartTLS, &allowInvalidCerts, + &conn.RootDN, &conn.BindDN, &conn.CredentialID, &conn.DefaultSearchScope, + &conn.TimeoutSeconds, &pagingEnabled, &conn.PageSize, &lastTested, &conn.LastTestResult, &createdStr, &updatedStr, ); err != nil { @@ -128,6 +131,9 @@ func (r *ConnectionRepository) List(offset, limit int) ([]models.ADConnection, i } conn.IsEnabled = intToBool(isEnabled) conn.UseTLS = intToBool(useTLS) + conn.UseStartTLS = intToBool(useStartTLS) + conn.AllowInvalidCerts = intToBool(allowInvalidCerts) + conn.PagingEnabled = intToBool(pagingEnabled) conn.LastTestedUTC = parseNullTime(lastTested) conn.CreatedUTC = parseTimeOrZero(createdStr) conn.UpdatedUTC = parseTimeOrZero(updatedStr) diff --git a/backend/internal/repository/timestamps_test.go b/backend/internal/repository/timestamps_test.go index 81190af..19a9b62 100644 --- a/backend/internal/repository/timestamps_test.go +++ b/backend/internal/repository/timestamps_test.go @@ -57,8 +57,10 @@ func TestListTimestampsParse(t *testing.T) { connRepo := NewConnectionRepository(conn) adc := &models.ADConnection{ - Name: "t-conn", Hosts: "dc1", Port: 389, RootDN: "DC=x,DC=y", - DefaultSearchScope: "Subtree", TimeoutSeconds: 15, PageSize: 1000, IsEnabled: true, + Name: "t-conn", Hosts: "dc1", Port: 636, RootDN: "DC=x,DC=y", + UseTLS: true, UseStartTLS: true, AllowInvalidCerts: true, + DefaultSearchScope: "Subtree", TimeoutSeconds: 45, + PagingEnabled: true, PageSize: 500, IsEnabled: true, } if err := connRepo.Create(adc); err != nil { t.Fatalf("create connection: %v", err) @@ -73,4 +75,14 @@ func TestListTimestampsParse(t *testing.T) { if conns[0].CreatedUTC.IsZero() || conns[0].UpdatedUTC.IsZero() { t.Errorf("connection timestamps not parsed: created=%v updated=%v", conns[0].CreatedUTC, conns[0].UpdatedUTC) } + // List must surface the full connection shape: the edit form pre-populates + // from the list row, so any field the List query drops gets silently + // overwritten with a zero value on the next save. Guard the fields the + // original List query omitted. + got := conns[0] + if !got.UseStartTLS || !got.AllowInvalidCerts || !got.PagingEnabled || + got.TimeoutSeconds != 45 || got.PageSize != 500 { + t.Errorf("List dropped connection fields: useStartTls=%v allowInvalidCerts=%v pagingEnabled=%v timeout=%d pageSize=%d", + got.UseStartTLS, got.AllowInvalidCerts, got.PagingEnabled, got.TimeoutSeconds, got.PageSize) + } } -- 2.52.0 From 5920b690d1944a3a09c22f90634d6f1eed2ac25a Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 14:57:30 -0400 Subject: [PATCH 21/34] feat(rules): dynamic-group reconciliation + editor-facing APIs Turns rules into Adaxes/Active-Roles style dynamic groups. The core new capability is a set-level SyncGroupMembership action that reconciles a target group's membership against the matched object set in one pass instead of the old add-only, per-object behaviour. Engine / reconciliation: - New ActionSyncGroupMembership runs once per target group after the match: resolve (and optionally create) the group, read its current members, diff against the matched set, and apply the adds/removes. - Three per-rule sync modes (types.SyncMode): FullSync (membership == matched set; removes stale members incl. manual adds), ManagedAdd (adds matches, removes only members this rule added), AddOnly (never removes). - Managed ownership tracked in a new managed_group_members table (migration 005) + repository, wired into the runner's engine so ManagedAdd removes only what it added. - Adds/removes are recorded as AddToGroup / RemoveFromGroupIfNoLongerMatched run-actions so the activity feed categorises them as syncs/removals. - Preview now computes an accurate, non-mutating diff for sync actions (+add / -remove / already-in-sync counts and per-member entries). - memberOf and memberOf-recursive (LDAP_MATCHING_RULE_IN_CHAIN) operators; Regex no longer silently degrades to equals. - Canonical group targets: CanonicalToLeafDN / NormalizeGroupTarget so a target group can be given as domain.com/OU/Group as well as a DN. Editor-facing APIs (backend-first; UI comes next): - Rule create/update now accept conditionGroups + actions and persist them via RuleRepository.ReplaceLogic (soft-delete + insert, preserving the rule_run_actions FK). Omitting them leaves existing logic untouched. - POST /api/v1/rules/preview evaluates an unsaved draft (live match panel). - GET /api/v1/rules/metadata serves the operator vocabulary (object types, operators, action types, sync modes, common attributes) so UI dropdowns stay in lock-step with the backend. - GET /api/v1/ad-connections/{id}/directory searches groups/OUs for the target pickers. Tests: reconciliation across all three modes + create-if-missing and the missing-group error path (fake directory client); canonical leaf-DN conversion; ReplaceLogic round-trip. Full suite green. Note: RuleRepository.GetByID nests a query (getConditionGroups holds a cursor while calling getConditions); safe under the production pool (25) but a follow-up should flatten it. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/connections_handlers.go | 35 +++ backend/internal/api/rules_handlers.go | 165 ++++++++++++- backend/internal/api/rules_metadata.go | 123 +++++++++ .../005_managed_group_members.down.sql | 2 + .../005_managed_group_members.up.sql | 15 ++ backend/internal/directory/ldap/canonical.go | 43 ++++ .../internal/directory/ldap/canonical_test.go | 20 ++ backend/internal/directory/ldap/filters.go | 18 ++ backend/internal/models/rule.go | 5 + .../internal/repository/managed_members.go | 57 +++++ backend/internal/repository/rules_logic.go | 94 +++++++ .../internal/repository/timestamps_test.go | 77 +++++- backend/internal/rules/engine/engine.go | 119 ++++++++- backend/internal/rules/engine/reconcile.go | 233 ++++++++++++++++++ .../internal/rules/engine/reconcile_test.go | 197 +++++++++++++++ backend/internal/rules/runner/runner.go | 28 ++- backend/internal/server/server.go | 3 + .../internal/services/connection_service.go | 70 ++++++ backend/internal/services/rule_service.go | 10 + backend/internal/types/types.go | 29 ++- 20 files changed, 1331 insertions(+), 12 deletions(-) create mode 100644 backend/internal/api/rules_metadata.go create mode 100644 backend/internal/db/migrations/005_managed_group_members.down.sql create mode 100644 backend/internal/db/migrations/005_managed_group_members.up.sql create mode 100644 backend/internal/repository/managed_members.go create mode 100644 backend/internal/repository/rules_logic.go create mode 100644 backend/internal/rules/engine/reconcile.go create mode 100644 backend/internal/rules/engine/reconcile_test.go diff --git a/backend/internal/api/connections_handlers.go b/backend/internal/api/connections_handlers.go index 628a8f7..57a3196 100644 --- a/backend/internal/api/connections_handlers.go +++ b/backend/internal/api/connections_handlers.go @@ -259,6 +259,41 @@ func (h *ConnectionsHandler) QueryPreview(w http.ResponseWriter, r *http.Request WriteJSON(w, http.StatusOK, result) } +// DirectorySearch handles GET /api/v1/ad-connections/{id}/directory?type=Group&q=&limit= +// It returns groups or OUs (and users/computers) matching an optional +// substring, for the rule editor's target pickers. +func (h *ConnectionsHandler) DirectorySearch(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + conn, err := h.repo.GetByID(id) + if err != nil { + h.logger.Error("ConnectionsHandler", "GetByID failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to load connection") + return + } + if conn == nil { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, "Connection not found") + return + } + + q := r.URL.Query() + objectType := q.Get("type") + if objectType == "" { + objectType = "Group" + } + limit := 50 + if n, err := parseInt(q.Get("limit")); err == nil && n > 0 { + limit = n + } + + objects, err := h.service.SearchDirectory(conn, objectType, q.Get("q"), limit) + if err != nil { + h.logger.Error("ConnectionsHandler", "DirectorySearch failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Directory search failed") + return + } + WriteJSON(w, http.StatusOK, objects) +} + func requestToConnection(req *ConnectionRequest, existing *models.ADConnection) *models.ADConnection { conn := &models.ADConnection{} if existing != nil { diff --git a/backend/internal/api/rules_handlers.go b/backend/internal/api/rules_handlers.go index 79db15a..338cf9e 100644 --- a/backend/internal/api/rules_handlers.go +++ b/backend/internal/api/rules_handlers.go @@ -12,6 +12,7 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/rules/engine" "github.com/Grace-Solutions/OrchestrAD/internal/rules/runner" "github.com/Grace-Solutions/OrchestrAD/internal/services" + "github.com/Grace-Solutions/OrchestrAD/internal/types" "github.com/go-chi/chi/v5" ) @@ -81,6 +82,56 @@ func (h *RulesHandler) Preview(w http.ResponseWriter, r *http.Request) { WriteJSON(w, http.StatusOK, toPreviewResponse(result)) } +// PreviewSpec handles POST /api/v1/rules/preview β€” a preview of an unsaved +// rule draft (the editor's live "matching objects" panel). +func (h *RulesHandler) PreviewSpec(w http.ResponseWriter, r *http.Request) { + var req RuleRequest + if err := DecodeJSON(r, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + if req.ADConnectionID == "" || req.ObjectType == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "adConnectionId and objectType are required") + return + } + + rule := &models.Rule{ + ID: "preview", + Name: req.Name, + ADConnectionID: req.ADConnectionID, + ObjectType: req.ObjectType, + BaseDNOverride: req.BaseDNOverride, + SearchScopeOverride: req.SearchScopeOverride, + GroupJoinOperator: req.GroupJoinOperator, + ExecutionMode: string(types.ExecutionModePreviewOnly), + } + if req.ConditionGroups != nil { + rule.ConditionGroups = toModelGroups(*req.ConditionGroups) + } + if req.Actions != nil { + rule.Actions = toModelActions(*req.Actions) + } + + result, err := h.runner.PreviewRuleSpec(r.Context(), rule) + if err != nil { + h.logger.Error("RulesHandler", "PreviewSpec failed: %v", err) + if isNotFound(err) { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, err.Error()) + return + } + WriteErrorWithDetails(w, http.StatusInternalServerError, ErrCodeInternalError, "preview failed", err.Error()) + return + } + WriteJSON(w, http.StatusOK, toPreviewResponse(result)) +} + +// Metadata handles GET /api/v1/rules/metadata β€” the operator-facing vocabulary +// the editor needs (object types, operators, action types, sync modes, and +// common attributes) so its dropdowns stay in lock-step with the backend. +func (h *RulesHandler) Metadata(w http.ResponseWriter, r *http.Request) { + WriteJSON(w, http.StatusOK, ruleMetadata()) +} + // Run handles POST /api/v1/rules/{id}/run func (h *RulesHandler) Run(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") @@ -153,7 +204,10 @@ func resolveTriggeredBy(r *http.Request) string { return "api" } -// RuleRequest represents a create/update rule payload. +// RuleRequest represents a create/update rule payload. When conditionGroups or +// actions are present (non-nil), the rule's logic is replaced wholesale; +// omitting them leaves existing logic untouched (so legacy scalar-only callers +// keep working). type RuleRequest struct { Name string `json:"name"` Description *string `json:"description,omitempty"` @@ -167,6 +221,101 @@ type RuleRequest struct { GroupJoinOperator string `json:"groupJoinOperator"` MaxParallelism *int `json:"maxParallelism,omitempty"` StopOnError *bool `json:"stopOnError,omitempty"` + + ConditionGroups *[]ruleGroupRequest `json:"conditionGroups,omitempty"` + Actions *[]ruleActionRequest `json:"actions,omitempty"` +} + +type ruleGroupRequest struct { + Name *string `json:"name,omitempty"` + IsEnabled *bool `json:"isEnabled,omitempty"` + JoinOperator string `json:"joinOperator"` + Negate bool `json:"negate"` + Conditions []ruleConditionRequest `json:"conditions"` +} + +type ruleConditionRequest struct { + IsEnabled *bool `json:"isEnabled,omitempty"` + AttributeName string `json:"attributeName"` + Operator string `json:"operator"` + ValueType string `json:"valueType,omitempty"` + ComparisonValue *string `json:"comparisonValue,omitempty"` + CustomLdapExpression *string `json:"customLdapExpression,omitempty"` + Negate bool `json:"negate"` + CaseSensitive bool `json:"caseSensitive"` +} + +type ruleActionRequest struct { + ActionType string `json:"actionType"` + IsEnabled *bool `json:"isEnabled,omitempty"` + ConfigurationJSON string `json:"configurationJson"` + RollbackMode *string `json:"rollbackMode,omitempty"` +} + +func boolOrTrue(p *bool) bool { + if p == nil { + return true + } + return *p +} + +func toModelGroups(reqs []ruleGroupRequest) []models.RuleConditionGroup { + groups := make([]models.RuleConditionGroup, 0, len(reqs)) + for _, gr := range reqs { + g := models.RuleConditionGroup{ + Name: gr.Name, + IsEnabled: boolOrTrue(gr.IsEnabled), + JoinOperator: gr.JoinOperator, + Negate: gr.Negate, + } + for _, cr := range gr.Conditions { + g.Conditions = append(g.Conditions, models.RuleCondition{ + IsEnabled: boolOrTrue(cr.IsEnabled), + AttributeName: cr.AttributeName, + Operator: cr.Operator, + ValueType: cr.ValueType, + ComparisonValue: cr.ComparisonValue, + CustomLdapExpression: cr.CustomLdapExpression, + Negate: cr.Negate, + CaseSensitive: cr.CaseSensitive, + }) + } + groups = append(groups, g) + } + return groups +} + +func toModelActions(reqs []ruleActionRequest) []models.RuleAction { + actions := make([]models.RuleAction, 0, len(reqs)) + for _, ar := range reqs { + actions = append(actions, models.RuleAction{ + ActionType: ar.ActionType, + IsEnabled: boolOrTrue(ar.IsEnabled), + ConfigurationJSON: ar.ConfigurationJSON, + RollbackMode: ar.RollbackMode, + }) + } + return actions +} + +// applyLogic persists a rule's condition groups / actions when the request +// carried them, then returns the freshly re-loaded rule. +func (h *RulesHandler) applyLogic(req *RuleRequest, ruleID string) (*models.Rule, error) { + if req.ConditionGroups == nil && req.Actions == nil { + return h.service.GetByID(ruleID) + } + var groups []models.RuleConditionGroup + var actions []models.RuleAction + if req.ConditionGroups != nil { + groups = toModelGroups(*req.ConditionGroups) + } + if req.Actions != nil { + actions = toModelActions(*req.Actions) + } + if err := h.service.ReplaceLogic(ruleID, groups, actions); err != nil { + return nil, err + } + return h.service.GetByID(ruleID) } // RuleSummaryResponse is the list/summary projection of a rule. @@ -316,6 +465,13 @@ func (h *RulesHandler) Create(w http.ResponseWriter, r *http.Request) { WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to create rule") return } + if withLogic, err := h.applyLogic(&req, rule.ID); err != nil { + h.logger.Error("RulesHandler", "Create: persisting rule logic failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to save rule filter/actions") + return + } else if withLogic != nil { + rule = withLogic + } emitAudit(h.auditService, r, audit.EventCreate, "Rule", rule.ID, "Create", true, map[string]any{"name": rule.Name, "objectType": rule.ObjectType}, "") WriteJSON(w, http.StatusCreated, ruleToDetailResponse(rule)) @@ -364,6 +520,13 @@ func (h *RulesHandler) Update(w http.ResponseWriter, r *http.Request) { WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to update rule") return } + if withLogic, err := h.applyLogic(&req, rule.ID); err != nil { + h.logger.Error("RulesHandler", "Update: persisting rule logic failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to save rule filter/actions") + return + } else if withLogic != nil { + rule = withLogic + } emitAudit(h.auditService, r, audit.EventUpdate, "Rule", rule.ID, "Update", true, map[string]any{"name": rule.Name}, "") WriteJSON(w, http.StatusOK, ruleToDetailResponse(rule)) diff --git a/backend/internal/api/rules_metadata.go b/backend/internal/api/rules_metadata.go new file mode 100644 index 0000000..4c05030 --- /dev/null +++ b/backend/internal/api/rules_metadata.go @@ -0,0 +1,123 @@ +// Package api - rule editor metadata (vocabulary served to the UI) +package api + +// OperatorMeta describes a condition operator for the editor. +type OperatorMeta struct { + Value string `json:"value"` + Label string `json:"label"` + NeedsValue bool `json:"needsValue"` // false for Exists/NotExists + Custom bool `json:"custom,omitempty"` // true for raw LDAP expression + Hint string `json:"hint,omitempty"` +} + +// LabeledValue is a generic {value,label,description} tuple for dropdowns. +type LabeledValue struct { + Value string `json:"value"` + Label string `json:"label"` + Description string `json:"description,omitempty"` +} + +// AttributeMeta is a suggested attribute for a given object type. +type AttributeMeta struct { + Name string `json:"name"` + Label string `json:"label"` +} + +// RuleMetadata is the full vocabulary the rule editor renders from. +type RuleMetadata struct { + ObjectTypes []LabeledValue `json:"objectTypes"` + SearchScopes []LabeledValue `json:"searchScopes"` + JoinOperators []LabeledValue `json:"joinOperators"` + Operators []OperatorMeta `json:"operators"` + ActionTypes []LabeledValue `json:"actionTypes"` + SyncModes []LabeledValue `json:"syncModes"` + ScheduleUnits []LabeledValue `json:"scheduleUnits"` + CommonAttributes map[string][]AttributeMeta `json:"commonAttributes"` +} + +func ruleMetadata() RuleMetadata { + return RuleMetadata{ + ObjectTypes: []LabeledValue{ + {Value: "User", Label: "Users"}, + {Value: "Computer", Label: "Computers"}, + {Value: "Group", Label: "Groups"}, + }, + SearchScopes: []LabeledValue{ + {Value: "Subtree", Label: "This container and everything below"}, + {Value: "OneLevel", Label: "Immediate children only"}, + {Value: "Base", Label: "This object only"}, + }, + JoinOperators: []LabeledValue{ + {Value: "AND", Label: "Match ALL of", Description: "Every condition must be true"}, + {Value: "OR", Label: "Match ANY of", Description: "At least one condition must be true"}, + }, + Operators: []OperatorMeta{ + {Value: "Equals", Label: "equals", NeedsValue: true}, + {Value: "NotEquals", Label: "does not equal", NeedsValue: true}, + {Value: "Contains", Label: "contains", NeedsValue: true}, + {Value: "StartsWith", Label: "starts with", NeedsValue: true}, + {Value: "EndsWith", Label: "ends with", NeedsValue: true}, + {Value: "GreaterThan", Label: "is greater than or equal to", NeedsValue: true}, + {Value: "LessThan", Label: "is less than or equal to", NeedsValue: true}, + {Value: "Exists", Label: "is present", NeedsValue: false}, + {Value: "NotExists", Label: "is not present", NeedsValue: false}, + {Value: "MemberOf", Label: "is a member of (direct)", NeedsValue: true, Hint: "Group DN"}, + {Value: "MemberOfRecursive", Label: "is a member of (incl. nested)", NeedsValue: true, Hint: "Group DN"}, + {Value: "CustomLdap", Label: "raw LDAP filter", NeedsValue: true, Custom: true, Hint: "e.g. (department=Sales)"}, + }, + ActionTypes: []LabeledValue{ + {Value: "SyncGroupMembership", Label: "Sync membership to group", Description: "Keep a target group's members in step with the matched set (add + remove)"}, + {Value: "MoveToOu", Label: "Move to OU", Description: "Move each matched object into a target OU"}, + {Value: "EnsureGroupExists", Label: "Ensure group exists", Description: "Create the target group if it is missing"}, + {Value: "AddToGroup", Label: "Add to group (no removal)", Description: "Add matched objects to a group without ever removing anyone"}, + }, + SyncModes: []LabeledValue{ + {Value: "FullSync", Label: "Full sync (add + remove)", Description: "Membership becomes exactly the matched set; members that no longer match are removed, including manual additions"}, + {Value: "ManagedAdd", Label: "Managed (add + remove only what we added)", Description: "Add matches and remove only members this rule added; leaves manually-added members alone"}, + {Value: "AddOnly", Label: "Add only", Description: "Only add matching members; never remove automatically"}, + }, + ScheduleUnits: []LabeledValue{ + {Value: "Minutes", Label: "minutes"}, + {Value: "Hours", Label: "hours"}, + {Value: "Days", Label: "days"}, + }, + CommonAttributes: map[string][]AttributeMeta{ + "User": { + {Name: "sAMAccountName", Label: "Logon name (sAMAccountName)"}, + {Name: "userPrincipalName", Label: "User principal name (UPN)"}, + {Name: "mail", Label: "Email"}, + {Name: "displayName", Label: "Display name"}, + {Name: "givenName", Label: "First name"}, + {Name: "sn", Label: "Last name"}, + {Name: "department", Label: "Department"}, + {Name: "title", Label: "Job title"}, + {Name: "company", Label: "Company"}, + {Name: "physicalDeliveryOfficeName", Label: "Office"}, + {Name: "l", Label: "City"}, + {Name: "st", Label: "State/Province"}, + {Name: "co", Label: "Country"}, + {Name: "manager", Label: "Manager (DN)"}, + {Name: "employeeType", Label: "Employee type"}, + {Name: "description", Label: "Description"}, + {Name: "memberOf", Label: "Member of (group DN)"}, + {Name: "userAccountControl", Label: "Account control flags"}, + }, + "Computer": { + {Name: "cn", Label: "Name"}, + {Name: "dNSHostName", Label: "DNS host name"}, + {Name: "operatingSystem", Label: "Operating system"}, + {Name: "operatingSystemVersion", Label: "OS version"}, + {Name: "description", Label: "Description"}, + {Name: "memberOf", Label: "Member of (group DN)"}, + }, + "Group": { + {Name: "cn", Label: "Name"}, + {Name: "sAMAccountName", Label: "Group name (sAMAccountName)"}, + {Name: "description", Label: "Description"}, + {Name: "groupType", Label: "Group type flags"}, + {Name: "mail", Label: "Email"}, + {Name: "memberOf", Label: "Member of (group DN)"}, + }, + }, + } +} diff --git a/backend/internal/db/migrations/005_managed_group_members.down.sql b/backend/internal/db/migrations/005_managed_group_members.down.sql new file mode 100644 index 0000000..7fbbf0f --- /dev/null +++ b/backend/internal/db/migrations/005_managed_group_members.down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_managed_group_members_rule_group; +DROP TABLE IF EXISTS managed_group_members; diff --git a/backend/internal/db/migrations/005_managed_group_members.up.sql b/backend/internal/db/migrations/005_managed_group_members.up.sql new file mode 100644 index 0000000..69441c4 --- /dev/null +++ b/backend/internal/db/migrations/005_managed_group_members.up.sql @@ -0,0 +1,15 @@ +-- Tracks group memberships that OrchestrAD added via a SyncGroupMembership +-- action, so the "ManagedAdd" sync mode can remove only what it added and +-- leave hand-added members alone. FullSync does not depend on this table for +-- correctness, but rows are still maintained for both modes so an operator can +-- switch a rule's sync mode without losing ownership history. +CREATE TABLE IF NOT EXISTS managed_group_members ( + rule_id TEXT NOT NULL, + group_dn TEXT NOT NULL, + member_dn TEXT NOT NULL, + added_utc TEXT NOT NULL, + PRIMARY KEY (rule_id, group_dn, member_dn) +); + +CREATE INDEX IF NOT EXISTS idx_managed_group_members_rule_group + ON managed_group_members (rule_id, group_dn); diff --git a/backend/internal/directory/ldap/canonical.go b/backend/internal/directory/ldap/canonical.go index f34c7a9..32f2dc9 100644 --- a/backend/internal/directory/ldap/canonical.go +++ b/backend/internal/directory/ldap/canonical.go @@ -103,6 +103,49 @@ func NormalizeOUTarget(input string) string { return strings.TrimSpace(input) } +// CanonicalToLeafDN converts a canonical path (domain.com/OU/.../Leaf) into a DN +// where the final segment is a CN leaf (a group, user, or computer) and every +// intermediate segment is an organizationalUnit. For example +// +// corp.example.com/HQ/Groups/All Staff +// +// becomes CN=All Staff,OU=Groups,OU=HQ,DC=corp,DC=example,DC=com. +func CanonicalToLeafDN(canonical string) string { + canonical = strings.Trim(strings.TrimSpace(canonical), "/") + if canonical == "" { + return "" + } + parts := strings.Split(canonical, "/") + domain := parts[0] + path := parts[1:] // root-first, last element is the CN leaf + if len(path) == 0 { + // Only a domain was supplied; nothing to anchor a leaf on. + return "" + } + + var comps []string + comps = append(comps, "CN="+escapeDNValue(path[len(path)-1])) + for i := len(path) - 2; i >= 0; i-- { // OU path, leaf-first + comps = append(comps, "OU="+escapeDNValue(path[i])) + } + for _, d := range strings.Split(domain, ".") { + if d != "" { + comps = append(comps, "DC="+escapeDNValue(d)) + } + } + return strings.Join(comps, ",") +} + +// NormalizeGroupTarget accepts either a DN or a canonical path to a group and +// returns a DN. Unlike NormalizeOUTarget, a canonical path's final segment is +// treated as the group's CN, not an OU. +func NormalizeGroupTarget(input string) string { + if LooksCanonical(input) { + return CanonicalToLeafDN(input) + } + return strings.TrimSpace(input) +} + // escapeDNValue escapes the characters that are special inside a DN attribute // value (RFC 4514), enough for OU/DC names entered by operators. func escapeDNValue(v string) string { diff --git a/backend/internal/directory/ldap/canonical_test.go b/backend/internal/directory/ldap/canonical_test.go index d9e8061..0e66480 100644 --- a/backend/internal/directory/ldap/canonical_test.go +++ b/backend/internal/directory/ldap/canonical_test.go @@ -71,3 +71,23 @@ func TestSplitDNComponentsEscaped(t *testing.T) { } } } + +func TestCanonicalToLeafDN(t *testing.T) { + got := CanonicalToLeafDN("corp.example.com/HQ/Groups/All Staff") + want := "CN=All Staff,OU=Groups,OU=HQ,DC=corp,DC=example,DC=com" + if got != want { + t.Errorf("CanonicalToLeafDN = %q, want %q", got, want) + } + // A group directly under the domain root. + if got := CanonicalToLeafDN("corp.example.com/AllStaff"); got != "CN=AllStaff,DC=corp,DC=example,DC=com" { + t.Errorf("root-level leaf = %q", got) + } + // NormalizeGroupTarget: canonical converts, DN passes through. + if got := NormalizeGroupTarget("corp.example.com/Groups/G"); got != "CN=G,OU=Groups,DC=corp,DC=example,DC=com" { + t.Errorf("NormalizeGroupTarget canonical = %q", got) + } + dn := "CN=G,OU=Groups,DC=corp,DC=com" + if got := NormalizeGroupTarget(dn); got != dn { + t.Errorf("NormalizeGroupTarget DN passthrough = %q", got) + } +} diff --git a/backend/internal/directory/ldap/filters.go b/backend/internal/directory/ldap/filters.go index fa9ce3e..64a789e 100644 --- a/backend/internal/directory/ldap/filters.go +++ b/backend/internal/directory/ldap/filters.go @@ -55,8 +55,20 @@ func (c *Condition) BuildFilter() string { filter = fmt.Sprintf("(%s>=%s)", c.Attribute, escapeLDAPValue(c.Value)) case types.OperatorLessThan: filter = fmt.Sprintf("(%s<=%s)", c.Attribute, escapeLDAPValue(c.Value)) + case types.OperatorMemberOf: + // Direct membership: value is the group DN. + filter = fmt.Sprintf("(memberOf=%s)", escapeLDAPValue(c.Value)) + case types.OperatorMemberOfRecursive: + // Nested (transitive) membership via LDAP_MATCHING_RULE_IN_CHAIN. + filter = fmt.Sprintf("(memberOf:1.2.840.113556.1.4.1941:=%s)", escapeLDAPValue(c.Value)) case types.OperatorCustomLdap: filter = c.CustomLdap + case types.OperatorRegex: + // LDAP has no regular-expression matching, so a true regex cannot be + // pushed into the server-side filter. Rather than silently degrade to an + // equality match, require the object to at least have the attribute and + // leave regex out of the advertised operator set (see rules metadata). + filter = fmt.Sprintf("(%s=*)", c.Attribute) default: filter = fmt.Sprintf("(%s=%s)", c.Attribute, escapeLDAPValue(c.Value)) } @@ -137,6 +149,12 @@ func BuildRuleFilter(objectType types.ObjectType, groups []ConditionGroup, group return fmt.Sprintf("(&%s%s)", objectFilter, conditionFilter) } +// EscapeFilterValue escapes special characters in a value destined for an LDAP +// filter (RFC 4515), for callers building filters outside this package. +func EscapeFilterValue(s string) string { + return escapeLDAPValue(s) +} + // escapeLDAPValue escapes special characters in LDAP filter values func escapeLDAPValue(s string) string { replacements := []struct { diff --git a/backend/internal/models/rule.go b/backend/internal/models/rule.go index 54f6ba3..fcbfec3 100644 --- a/backend/internal/models/rule.go +++ b/backend/internal/models/rule.go @@ -122,6 +122,11 @@ type ActionConfig struct { // RemoveFromGroupIfNoMatch RemoveIfNoMatch bool `json:"removeIfNoMatch,omitempty"` + // SyncGroupMembership: how to reconcile the target group's membership + // against the matched set. One of FullSync, ManagedAdd, AddOnly. Empty + // defaults to FullSync. + SyncMode string `json:"syncMode,omitempty"` + // Dynamic path variables UseDynamicPath bool `json:"useDynamicPath,omitempty"` PathTemplate string `json:"pathTemplate,omitempty"` diff --git a/backend/internal/repository/managed_members.go b/backend/internal/repository/managed_members.go new file mode 100644 index 0000000..95b611b --- /dev/null +++ b/backend/internal/repository/managed_members.go @@ -0,0 +1,57 @@ +// Package repository - managed group membership tracking +package repository + +import ( + "database/sql" + "time" +) + +// ManagedMemberRepository records which group memberships OrchestrAD added on +// behalf of a rule, so the ManagedAdd sync mode can remove only what it added. +type ManagedMemberRepository struct { + db *sql.DB +} + +// NewManagedMemberRepository creates a new ManagedMemberRepository. +func NewManagedMemberRepository(db *sql.DB) *ManagedMemberRepository { + return &ManagedMemberRepository{db: db} +} + +// List returns the member DNs this rule has recorded as added to groupDN. +func (r *ManagedMemberRepository) List(ruleID, groupDN string) ([]string, error) { + rows, err := r.db.Query( + `SELECT member_dn FROM managed_group_members WHERE rule_id = ? AND group_dn = ?`, + ruleID, groupDN) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []string + for rows.Next() { + var dn string + if err := rows.Scan(&dn); err != nil { + return nil, err + } + out = append(out, dn) + } + return out, rows.Err() +} + +// Add records that ruleID added memberDN to groupDN. Idempotent. +func (r *ManagedMemberRepository) Add(ruleID, groupDN, memberDN string) error { + _, err := r.db.Exec(` + INSERT INTO managed_group_members (rule_id, group_dn, member_dn, added_utc) + VALUES (?, ?, ?, ?) + ON CONFLICT(rule_id, group_dn, member_dn) DO NOTHING`, + ruleID, groupDN, memberDN, time.Now().UTC().Format(time.RFC3339)) + return err +} + +// Remove clears the ownership record for a single membership. Idempotent. +func (r *ManagedMemberRepository) Remove(ruleID, groupDN, memberDN string) error { + _, err := r.db.Exec( + `DELETE FROM managed_group_members WHERE rule_id = ? AND group_dn = ? AND member_dn = ?`, + ruleID, groupDN, memberDN) + return err +} diff --git a/backend/internal/repository/rules_logic.go b/backend/internal/repository/rules_logic.go new file mode 100644 index 0000000..b1eabb9 --- /dev/null +++ b/backend/internal/repository/rules_logic.go @@ -0,0 +1,94 @@ +// Package repository - rule filter/action authoring (condition groups, +// conditions, and actions). These are the write paths the rule editor uses to +// persist a rule's logic, complementing the scalar Create/Update in rules.go. +package repository + +import ( + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/google/uuid" +) + +// ReplaceLogic atomically replaces a rule's condition groups (with their +// conditions) and actions. Existing rows are soft-deleted rather than removed +// so historical rule_run_actions keep their rule_action_id foreign key; new +// rows are inserted with fresh IDs. Passing empty slices clears the rule's +// logic. +func (r *RuleRepository) ReplaceLogic(ruleID string, groups []models.RuleConditionGroup, actions []models.RuleAction) error { + tx, err := r.db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + now := formatTime(time.Now().UTC()) + + if _, err := tx.Exec(`UPDATE rule_condition_groups SET deleted_utc = ? WHERE rule_id = ? AND deleted_utc IS NULL`, now, ruleID); err != nil { + return err + } + if _, err := tx.Exec(`UPDATE rule_actions SET deleted_utc = ? WHERE rule_id = ? AND deleted_utc IS NULL`, now, ruleID); err != nil { + return err + } + + for gi := range groups { + g := &groups[gi] + if g.ID == "" { + g.ID = uuid.New().String() + } + if g.JoinOperator == "" { + g.JoinOperator = "AND" + } + if g.SortOrder == 0 { + g.SortOrder = gi + } + if _, err := tx.Exec(` + INSERT INTO rule_condition_groups (id, rule_id, name, is_enabled, join_operator, sort_order, negate, created_utc, updated_utc) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + g.ID, ruleID, g.Name, boolToInt(g.IsEnabled), g.JoinOperator, g.SortOrder, boolToInt(g.Negate), now, now); err != nil { + return err + } + for ci := range g.Conditions { + c := &g.Conditions[ci] + if c.ID == "" { + c.ID = uuid.New().String() + } + if c.ValueType == "" { + c.ValueType = "String" + } + if c.SortOrder == 0 { + c.SortOrder = ci + } + if _, err := tx.Exec(` + INSERT INTO rule_conditions (id, condition_group_id, is_enabled, attribute_name, operator, value_type, + comparison_value, custom_ldap_expression, negate, sort_order, case_sensitive, created_utc, updated_utc) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + c.ID, g.ID, boolToInt(c.IsEnabled), c.AttributeName, c.Operator, c.ValueType, + c.ComparisonValue, c.CustomLdapExpression, boolToInt(c.Negate), c.SortOrder, + boolToInt(c.CaseSensitive), now, now); err != nil { + return err + } + } + } + + for ai := range actions { + a := &actions[ai] + if a.ID == "" { + a.ID = uuid.New().String() + } + if a.ConfigurationJSON == "" { + a.ConfigurationJSON = "{}" + } + if a.SortOrder == 0 { + a.SortOrder = ai + } + if _, err := tx.Exec(` + INSERT INTO rule_actions (id, rule_id, action_type, is_enabled, sort_order, configuration_json, rollback_mode, created_utc, updated_utc) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + a.ID, ruleID, a.ActionType, boolToInt(a.IsEnabled), a.SortOrder, a.ConfigurationJSON, a.RollbackMode, now, now); err != nil { + return err + } + } + + return tx.Commit() +} diff --git a/backend/internal/repository/timestamps_test.go b/backend/internal/repository/timestamps_test.go index 19a9b62..cb74423 100644 --- a/backend/internal/repository/timestamps_test.go +++ b/backend/internal/repository/timestamps_test.go @@ -15,9 +15,12 @@ import ( func migratedDB(t *testing.T) *sql.DB { t.Helper() database, err := db.New(config.DatabaseConfig{ - Path: filepath.Join(t.TempDir(), "repo_test.db"), - MaxOpenConns: 1, - MaxIdleConns: 1, + Path: filepath.Join(t.TempDir(), "repo_test.db"), + // Mirror the production pool (default 25). GetByID nests a query + // (getConditionGroups holds a rows cursor while calling getConditions), + // which deadlocks against a single-connection pool but is safe here. + MaxOpenConns: 5, + MaxIdleConns: 5, WALMode: true, ForeignKeys: true, }, logging.Default()) @@ -86,3 +89,71 @@ func TestListTimestampsParse(t *testing.T) { got.UseStartTLS, got.AllowInvalidCerts, got.PagingEnabled, got.TimeoutSeconds, got.PageSize) } } + +// TestReplaceLogicRoundTrip verifies the rule editor's authoring path: a rule's +// condition groups and actions can be written via ReplaceLogic and read back +// through GetByID, and a second Replace supersedes the first. +func TestReplaceLogicRoundTrip(t *testing.T) { + conn := migratedDB(t) + + // A connection to satisfy rules.ad_connection_id FK. + connRepo := NewConnectionRepository(conn) + adc := &models.ADConnection{ + Name: "rl-conn", Hosts: "dc1", Port: 389, RootDN: "DC=x,DC=y", + DefaultSearchScope: "Subtree", TimeoutSeconds: 15, PageSize: 1000, IsEnabled: true, + } + if err := connRepo.Create(adc); err != nil { + t.Fatalf("create connection: %v", err) + } + + ruleRepo := NewRuleRepository(conn) + rule := &models.Rule{Name: "rl-rule", ObjectType: "User", ADConnectionID: adc.ID, ExecutionMode: "Apply", GroupJoinOperator: "AND"} + if err := ruleRepo.Create(rule); err != nil { + t.Fatalf("create rule: %v", err) + } + + val := "Sales" + groups := []models.RuleConditionGroup{{ + JoinOperator: "AND", IsEnabled: true, + Conditions: []models.RuleCondition{{ + IsEnabled: true, AttributeName: "department", Operator: "Equals", ComparisonValue: &val, + }}, + }} + actions := []models.RuleAction{{ + ActionType: "SyncGroupMembership", IsEnabled: true, + ConfigurationJSON: `{"targetGroupDn":"CN=Sales,DC=x,DC=y","syncMode":"FullSync"}`, + }} + if err := ruleRepo.ReplaceLogic(rule.ID, groups, actions); err != nil { + t.Fatalf("ReplaceLogic: %v", err) + } + + got, err := ruleRepo.GetByID(rule.ID) + if err != nil { + t.Fatalf("GetByID: %v", err) + } + if len(got.ConditionGroups) != 1 || len(got.ConditionGroups[0].Conditions) != 1 { + t.Fatalf("expected 1 group/1 condition, got %+v", got.ConditionGroups) + } + c := got.ConditionGroups[0].Conditions[0] + if c.AttributeName != "department" || c.Operator != "Equals" || c.ComparisonValue == nil || *c.ComparisonValue != "Sales" { + t.Errorf("condition round-trip wrong: %+v", c) + } + if len(got.Actions) != 1 || got.Actions[0].ActionType != "SyncGroupMembership" { + t.Fatalf("expected 1 sync action, got %+v", got.Actions) + } + + // Replacing supersedes: swap to a single MoveToOu action and no conditions. + if err := ruleRepo.ReplaceLogic(rule.ID, nil, []models.RuleAction{{ActionType: "MoveToOu", IsEnabled: true, ConfigurationJSON: "{}"}}); err != nil { + t.Fatalf("ReplaceLogic 2: %v", err) + } + got2, err := ruleRepo.GetByID(rule.ID) + if err != nil { + t.Fatalf("GetByID 2: %v", err) + } + if len(got2.ConditionGroups) != 0 { + t.Errorf("expected groups cleared, got %d", len(got2.ConditionGroups)) + } + if len(got2.Actions) != 1 || got2.Actions[0].ActionType != "MoveToOu" { + t.Errorf("expected only MoveToOu after replace, got %+v", got2.Actions) + } +} diff --git a/backend/internal/rules/engine/engine.go b/backend/internal/rules/engine/engine.go index 8344718..5a17e14 100644 --- a/backend/internal/rules/engine/engine.go +++ b/backend/internal/rules/engine/engine.go @@ -15,7 +15,8 @@ import ( // Engine executes rules against Active Directory type Engine struct { - logger *logging.Logger + logger *logging.Logger + managed ManagedMemberStore } // NewEngine creates a new rule engine @@ -23,6 +24,19 @@ func NewEngine(logger *logging.Logger) *Engine { return &Engine{logger: logger} } +// SetManagedStore attaches the managed-membership store used by the ManagedAdd +// sync mode to remember which memberships this engine added. Optional; when +// unset, ManagedAdd never removes and FullSync is unaffected. +func (e *Engine) SetManagedStore(store ManagedMemberStore) { + e.managed = store +} + +// isSetAction reports whether an action operates on the whole matched set at +// once (rather than per matched object). +func isSetAction(actionType string) bool { + return types.ActionType(actionType) == types.ActionSyncGroupMembership +} + // ExecutionResult contains the results of rule execution type ExecutionResult struct { RuleID string @@ -132,7 +146,7 @@ func (e *Engine) Preview(ctx context.Context, rule *models.Rule, conn *models.AD result.MatchedObjects = append(result.MatchedObjects, matched) } - result.PlannedActions = e.planActions(rule, result.MatchedObjects) + result.PlannedActions = e.planActions(rule, result.MatchedObjects, client) e.logger.Info("RuleEngine", "Preview complete: %d objects matched, %d actions planned", len(result.MatchedObjects), len(result.PlannedActions)) @@ -176,6 +190,7 @@ func (e *Engine) Execute(ctx context.Context, rule *models.Rule, conn *models.AD executor := newActionExecutor(client) cancelled := false + matchedDNs := make([]string, 0, len(entries)) objectLoop: for _, entry := range entries { @@ -195,11 +210,12 @@ objectLoop: for _, attr := range entry.Attributes { matched.Attributes[attr.Name] = attr.Values } + matchedDNs = append(matchedDNs, entry.DN) for i := range rule.Actions { action := &rule.Actions[i] - if !action.IsEnabled { - continue + if !action.IsEnabled || isSetAction(action.ActionType) { + continue // set-level actions run once after the loop } actionStart := time.Now() outcome := executor.execute(action, rule, matched) @@ -229,6 +245,34 @@ objectLoop: result.ObjectsProcessed++ } + // Set-level actions (SyncGroupMembership) reconcile the whole matched set + // against a target group's membership in one pass. + if !cancelled { + for i := range rule.Actions { + action := &rule.Actions[i] + if !action.IsEnabled || !isSetAction(action.ActionType) { + continue + } + actionStart := time.Now() + outcomes := e.reconcileMembership(action, rule, matchedDNs, client, e.managed) + dur := time.Since(actionStart) + for _, oc := range outcomes { + oc.Duration = dur + result.ActionResults = append(result.ActionResults, oc) + if oc.Success { + result.ActionsExecuted++ + } else { + result.ActionsFailed++ + result.Errors = append(result.Errors, + fmt.Sprintf("sync %s on %s failed: %s", oc.ActionType, oc.ObjectDN, oc.Error)) + } + } + if result.ActionsFailed > 0 && rule.StopOnError { + break + } + } + } + status := types.RunStatusCompleted if cancelled { status = types.RunStatusCancelled @@ -288,12 +332,24 @@ func buildConditionGroups(rule *models.Rule) []ldap.ConditionGroup { return groups } -func (e *Engine) planActions(rule *models.Rule, objects []MatchedObject) []PlannedAction { +func (e *Engine) planActions(rule *models.Rule, objects []MatchedObject, client membershipClient) []PlannedAction { var planned []PlannedAction - for _, action := range rule.Actions { + matchedDNs := make([]string, 0, len(objects)) + for _, o := range objects { + matchedDNs = append(matchedDNs, o.DN) + } + + for i := range rule.Actions { + action := &rule.Actions[i] if !action.IsEnabled { continue } + + if isSetAction(action.ActionType) { + planned = append(planned, e.planSyncAction(action, rule, matchedDNs, client)...) + continue + } + for _, obj := range objects { planned = append(planned, PlannedAction{ ActionID: action.ID, @@ -307,6 +363,57 @@ func (e *Engine) planActions(rule *models.Rule, objects []MatchedObject) []Plann return planned } +// planSyncAction computes an accurate, non-mutating preview for a +// SyncGroupMembership action: a summary line plus one planned entry per member +// that would be added or removed. +func (e *Engine) planSyncAction(action *models.RuleAction, rule *models.Rule, + matchedDNs []string, client membershipClient) []PlannedAction { + + plan := planMembership(action, rule, matchedDNs, client, e.managed, false) + if plan.Err != nil { + return []PlannedAction{{ + ActionID: action.ID, + ActionType: string(types.ActionSyncGroupMembership), + TargetDN: plan.GroupDN, + Description: "cannot sync: " + plan.Err.Error(), + IsChange: false, + }} + } + + var planned []PlannedAction + summary := fmt.Sprintf("sync %s: +%d add / -%d remove (%d already in sync)", + plan.GroupDN, len(plan.ToAdd), len(plan.ToRemove), plan.Unchanged) + if plan.GroupCreated { + summary = "create group + " + summary + } + planned = append(planned, PlannedAction{ + ActionID: action.ID, + ActionType: string(types.ActionSyncGroupMembership), + TargetDN: plan.GroupDN, + Description: summary, + IsChange: plan.GroupCreated || len(plan.ToAdd) > 0 || len(plan.ToRemove) > 0, + }) + for _, dn := range plan.ToAdd { + planned = append(planned, PlannedAction{ + ActionID: action.ID, + ActionType: string(types.ActionAddToGroup), + TargetDN: dn, + Description: "add to " + plan.GroupDN, + IsChange: true, + }) + } + for _, dn := range plan.ToRemove { + planned = append(planned, PlannedAction{ + ActionID: action.ID, + ActionType: string(types.ActionRemoveFromGroupIfNoMatch), + TargetDN: dn, + Description: "remove from " + plan.GroupDN + " (no longer matches)", + IsChange: true, + }) + } + return planned +} + func resolveBaseDN(rule *models.Rule, conn *models.ADConnection) string { if rule.BaseDNOverride != nil && *rule.BaseDNOverride != "" { return *rule.BaseDNOverride diff --git a/backend/internal/rules/engine/reconcile.go b/backend/internal/rules/engine/reconcile.go new file mode 100644 index 0000000..480c631 --- /dev/null +++ b/backend/internal/rules/engine/reconcile.go @@ -0,0 +1,233 @@ +// Package engine - dynamic-group membership reconciliation. +// +// SyncGroupMembership is a set-level action: unlike per-object actions it runs +// once per target group with the entire matched set, diffs it against the +// group's current membership, and issues the adds/removes needed to make the +// group reflect the rule. This is the behaviour that turns a rule into an +// Adaxes/Active-Roles style dynamic group. +package engine + +import ( + "fmt" + "strings" + + "github.com/Grace-Solutions/OrchestrAD/internal/directory/ldap" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/types" +) + +// membershipClient is the slice of the LDAP client that reconciliation needs. +// Declaring it as an interface keeps the reconciler unit-testable with a fake. +type membershipClient interface { + Exists(dn string) (bool, error) + GetGroupMembers(groupDN string) ([]string, error) + AddGroupMember(groupDN, memberDN string) error + RemoveGroupMember(groupDN, memberDN string) error + CreateGroup(groupDN, groupType, groupScope string) error + EnsureOUPath(ouDN string) error +} + +// ManagedMemberStore records which memberships this rule added, so ManagedAdd +// mode can remove only what it added. Implementations are backed by the DB; a +// nil store disables managed-removal (it degrades to add-only removal). +type ManagedMemberStore interface { + List(ruleID, groupDN string) ([]string, error) + Add(ruleID, groupDN, memberDN string) error + Remove(ruleID, groupDN, memberDN string) error +} + +// membershipPlan is the computed diff for one target group. +type membershipPlan struct { + GroupDN string + GroupCreated bool + ToAdd []string // original-cased member DNs to add + ToRemove []string // original-cased member DNs to remove + Unchanged int + Err error +} + +// normDN returns the case/space-normalised key used to compare DNs. AD returns +// member DNs and search DNs in a consistent form, but comparing case-folded is +// safe and avoids spurious add/remove churn. +func normDN(dn string) string { + return strings.ToLower(strings.TrimSpace(dn)) +} + +// planMembership resolves the target group and computes the add/remove diff +// against the matched DN set without mutating anything. It is shared by preview +// (counts only) and execution (which then applies the plan). +func planMembership(action *models.RuleAction, rule *models.Rule, matchedDNs []string, + client membershipClient, store ManagedMemberStore, allowCreate bool) membershipPlan { + + cfg, err := parseActionConfig(action.ConfigurationJSON) + if err != nil { + return membershipPlan{Err: fmt.Errorf("invalid action configuration: %w", err)} + } + + groupDN := ldap.NormalizeGroupTarget(cfg.TargetGroupDN) + if groupDN == "" { + return membershipPlan{Err: fmt.Errorf("targetGroupDn is required for SyncGroupMembership")} + } + plan := membershipPlan{GroupDN: groupDN} + + exists, err := client.Exists(groupDN) + if err != nil { + plan.Err = fmt.Errorf("checking group existence: %w", err) + return plan + } + if !exists { + if !cfg.CreateIfMissing { + plan.Err = fmt.Errorf("target group does not exist: %s", groupDN) + return plan + } + // Group would be created; its current membership is empty. + plan.GroupCreated = true + if allowCreate { + if err := client.EnsureOUPath(parentDN(groupDN)); err != nil { + plan.Err = fmt.Errorf("ensuring group OU path: %w", err) + return plan + } + if err := client.CreateGroup(groupDN, cfg.GroupType, cfg.GroupScope); err != nil { + plan.Err = fmt.Errorf("creating group: %w", err) + return plan + } + } + } + + // Current members (empty when the group was, or would be, freshly created). + var current []string + if !plan.GroupCreated { + current, err = client.GetGroupMembers(groupDN) + if err != nil { + plan.Err = fmt.Errorf("reading group members: %w", err) + return plan + } + } + + matchedByKey := make(map[string]string, len(matchedDNs)) + for _, dn := range matchedDNs { + matchedByKey[normDN(dn)] = dn + } + currentByKey := make(map[string]string, len(current)) + for _, dn := range current { + currentByKey[normDN(dn)] = dn + } + + // Additions: matched objects not currently in the group. + for key, dn := range matchedByKey { + if _, ok := currentByKey[key]; ok { + plan.Unchanged++ + } else { + plan.ToAdd = append(plan.ToAdd, dn) + } + } + + // Removals depend on the sync mode. + mode := types.SyncMode(cfg.SyncMode) + if mode == "" { + mode = types.SyncModeFull + } + switch mode { + case types.SyncModeFull: + for key, dn := range currentByKey { + if _, ok := matchedByKey[key]; !ok { + plan.ToRemove = append(plan.ToRemove, dn) + } + } + case types.SyncModeManaged: + if store != nil { + managed, err := store.List(rule.ID, groupDN) + if err != nil { + plan.Err = fmt.Errorf("reading managed members: %w", err) + return plan + } + managedKeys := make(map[string]bool, len(managed)) + for _, dn := range managed { + managedKeys[normDN(dn)] = true + } + for key, dn := range currentByKey { + if managedKeys[key] { + if _, ok := matchedByKey[key]; !ok { + plan.ToRemove = append(plan.ToRemove, dn) + } + } + } + } + case types.SyncModeAddOnly: + // never remove + } + + return plan +} + +// reconcileMembership applies a SyncGroupMembership action and returns one +// ActionResult per membership change (plus a group-created result when +// relevant). Adds are reported as AddToGroup and removes as +// RemoveFromGroupIfNoLongerMatched so the activity feed categorises them as +// syncs and removals. +func (e *Engine) reconcileMembership(action *models.RuleAction, rule *models.Rule, + matchedDNs []string, client membershipClient, store ManagedMemberStore) []ActionResult { + + plan := planMembership(action, rule, matchedDNs, client, store, true) + if plan.Err != nil { + return []ActionResult{{ + ActionID: action.ID, + ActionType: string(types.ActionSyncGroupMembership), + ObjectDN: plan.GroupDN, + Success: false, + Error: plan.Err.Error(), + Details: map[string]any{"targetGroupDn": plan.GroupDN}, + }} + } + + var results []ActionResult + if plan.GroupCreated { + results = append(results, ActionResult{ + ActionID: action.ID, + ActionType: string(types.ActionEnsureGroupExists), + ObjectDN: plan.GroupDN, + Success: true, + Details: map[string]any{"groupDn": plan.GroupDN, "groupCreated": true}, + }) + } + + for _, memberDN := range plan.ToAdd { + res := ActionResult{ + ActionID: action.ID, + ActionType: string(types.ActionAddToGroup), + ObjectDN: memberDN, + Details: map[string]any{"groupDn": plan.GroupDN, "memberDn": memberDN, "reason": "matched"}, + } + if err := client.AddGroupMember(plan.GroupDN, memberDN); err != nil { + res.Success = false + res.Error = err.Error() + } else { + res.Success = true + if store != nil { + _ = store.Add(rule.ID, plan.GroupDN, memberDN) + } + } + results = append(results, res) + } + + for _, memberDN := range plan.ToRemove { + res := ActionResult{ + ActionID: action.ID, + ActionType: string(types.ActionRemoveFromGroupIfNoMatch), + ObjectDN: memberDN, + Details: map[string]any{"groupDn": plan.GroupDN, "memberDn": memberDN, "reason": "noLongerMatched"}, + } + if err := client.RemoveGroupMember(plan.GroupDN, memberDN); err != nil { + res.Success = false + res.Error = err.Error() + } else { + res.Success = true + if store != nil { + _ = store.Remove(rule.ID, plan.GroupDN, memberDN) + } + } + results = append(results, res) + } + + return results +} diff --git a/backend/internal/rules/engine/reconcile_test.go b/backend/internal/rules/engine/reconcile_test.go new file mode 100644 index 0000000..72623fb --- /dev/null +++ b/backend/internal/rules/engine/reconcile_test.go @@ -0,0 +1,197 @@ +package engine + +import ( + "encoding/json" + "sort" + "testing" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/types" +) + +// fakeClient is an in-memory membershipClient for reconciliation tests. +type fakeClient struct { + members map[string][]string // groupDN -> member DNs + exists map[string]bool + created []string +} + +func (f *fakeClient) Exists(dn string) (bool, error) { return f.exists[dn], nil } +func (f *fakeClient) GetGroupMembers(groupDN string) ([]string, error) { + return append([]string(nil), f.members[groupDN]...), nil +} +func (f *fakeClient) AddGroupMember(groupDN, memberDN string) error { + f.members[groupDN] = append(f.members[groupDN], memberDN) + return nil +} +func (f *fakeClient) RemoveGroupMember(groupDN, memberDN string) error { + cur := f.members[groupDN] + out := cur[:0] + for _, m := range cur { + if m != memberDN { + out = append(out, m) + } + } + f.members[groupDN] = out + return nil +} +func (f *fakeClient) CreateGroup(groupDN, groupType, groupScope string) error { + f.created = append(f.created, groupDN) + f.exists[groupDN] = true + f.members[groupDN] = nil + return nil +} +func (f *fakeClient) EnsureOUPath(ouDN string) error { return nil } + +// fakeStore is an in-memory ManagedMemberStore. +type fakeStore struct { + m map[string]map[string]bool // ruleID|groupDN -> set of member DNs +} + +func newFakeStore() *fakeStore { return &fakeStore{m: map[string]map[string]bool{}} } +func (s *fakeStore) key(ruleID, groupDN string) string { return ruleID + "|" + groupDN } +func (s *fakeStore) List(ruleID, groupDN string) ([]string, error) { + var out []string + for dn := range s.m[s.key(ruleID, groupDN)] { + out = append(out, dn) + } + return out, nil +} +func (s *fakeStore) Add(ruleID, groupDN, memberDN string) error { + k := s.key(ruleID, groupDN) + if s.m[k] == nil { + s.m[k] = map[string]bool{} + } + s.m[k][memberDN] = true + return nil +} +func (s *fakeStore) Remove(ruleID, groupDN, memberDN string) error { + delete(s.m[s.key(ruleID, groupDN)], memberDN) + return nil +} + +func syncAction(t *testing.T, group, mode string, createIfMissing bool) *models.RuleAction { + t.Helper() + cfg := models.ActionConfig{TargetGroupDN: group, SyncMode: mode, CreateIfMissing: createIfMissing} + b, err := json.Marshal(cfg) + if err != nil { + t.Fatalf("marshal cfg: %v", err) + } + return &models.RuleAction{ID: "action-1", ActionType: string(types.ActionSyncGroupMembership), ConfigurationJSON: string(b), IsEnabled: true} +} + +func sortedMembers(f *fakeClient, group string) []string { + out := append([]string(nil), f.members[group]...) + sort.Strings(out) + return out +} + +const grp = "CN=Dyn,OU=Groups,DC=x,DC=y" + +func TestReconcileFullSync(t *testing.T) { + eng := NewEngine(logging.Default()) + f := &fakeClient{ + members: map[string][]string{grp: {"CN=A,DC=x,DC=y", "CN=B,DC=x,DC=y", "CN=X,DC=x,DC=y"}}, + exists: map[string]bool{grp: true}, + } + rule := &models.Rule{ID: "rule-1"} + matched := []string{"CN=A,DC=x,DC=y", "CN=B,DC=x,DC=y", "CN=C,DC=x,DC=y"} + + results := eng.reconcileMembership(syncAction(t, grp, string(types.SyncModeFull), false), rule, matched, f, newFakeStore()) + + // Expect: add C, remove X. + if got := sortedMembers(f, grp); len(got) != 3 || got[0] != "CN=A,DC=x,DC=y" || got[1] != "CN=B,DC=x,DC=y" || got[2] != "CN=C,DC=x,DC=y" { + t.Fatalf("full sync membership wrong: %v", got) + } + var adds, removes int + for _, r := range results { + if !r.Success { + t.Errorf("unexpected failure: %+v", r) + } + switch r.ActionType { + case string(types.ActionAddToGroup): + adds++ + case string(types.ActionRemoveFromGroupIfNoMatch): + removes++ + } + } + if adds != 1 || removes != 1 { + t.Errorf("expected 1 add / 1 remove, got %d / %d", adds, removes) + } +} + +func TestReconcileManagedAddLeavesManualMembers(t *testing.T) { + eng := NewEngine(logging.Default()) + f := &fakeClient{ + members: map[string][]string{grp: {"CN=B,DC=x,DC=y", "CN=X,DC=x,DC=y", "CN=Manual,DC=x,DC=y"}}, + exists: map[string]bool{grp: true}, + } + store := newFakeStore() + // The rule previously added B and X. + _ = store.Add("rule-1", grp, "CN=B,DC=x,DC=y") + _ = store.Add("rule-1", grp, "CN=X,DC=x,DC=y") + rule := &models.Rule{ID: "rule-1"} + matched := []string{"CN=B,DC=x,DC=y"} // X no longer matches; Manual was never managed + + eng.reconcileMembership(syncAction(t, grp, string(types.SyncModeManaged), false), rule, matched, f, store) + + got := sortedMembers(f, grp) + // B stays (matched), X removed (managed + unmatched), Manual stays (not managed). + if len(got) != 2 || got[0] != "CN=B,DC=x,DC=y" || got[1] != "CN=Manual,DC=x,DC=y" { + t.Fatalf("managed sync membership wrong: %v", got) + } +} + +func TestReconcileAddOnlyNeverRemoves(t *testing.T) { + eng := NewEngine(logging.Default()) + f := &fakeClient{ + members: map[string][]string{grp: {"CN=Stale,DC=x,DC=y"}}, + exists: map[string]bool{grp: true}, + } + rule := &models.Rule{ID: "rule-1"} + matched := []string{"CN=A,DC=x,DC=y"} + + eng.reconcileMembership(syncAction(t, grp, string(types.SyncModeAddOnly), false), rule, matched, f, newFakeStore()) + + got := sortedMembers(f, grp) + if len(got) != 2 || got[0] != "CN=A,DC=x,DC=y" || got[1] != "CN=Stale,DC=x,DC=y" { + t.Fatalf("add-only membership wrong: %v", got) + } +} + +func TestReconcileCreatesMissingGroup(t *testing.T) { + eng := NewEngine(logging.Default()) + f := &fakeClient{members: map[string][]string{}, exists: map[string]bool{}} + rule := &models.Rule{ID: "rule-1"} + matched := []string{"CN=A,DC=x,DC=y"} + + results := eng.reconcileMembership(syncAction(t, grp, string(types.SyncModeFull), true), rule, matched, f, newFakeStore()) + + if len(f.created) != 1 || f.created[0] != grp { + t.Fatalf("expected group to be created, created=%v", f.created) + } + if got := sortedMembers(f, grp); len(got) != 1 || got[0] != "CN=A,DC=x,DC=y" { + t.Fatalf("membership after create wrong: %v", got) + } + var sawCreate bool + for _, r := range results { + if r.ActionType == string(types.ActionEnsureGroupExists) { + sawCreate = true + } + } + if !sawCreate { + t.Errorf("expected an EnsureGroupExists result") + } +} + +func TestReconcileMissingGroupNoCreateFails(t *testing.T) { + eng := NewEngine(logging.Default()) + f := &fakeClient{members: map[string][]string{}, exists: map[string]bool{}} + rule := &models.Rule{ID: "rule-1"} + + results := eng.reconcileMembership(syncAction(t, grp, string(types.SyncModeFull), false), rule, []string{"CN=A,DC=x,DC=y"}, f, newFakeStore()) + if len(results) != 1 || results[0].Success { + t.Fatalf("expected a single failure result, got %+v", results) + } +} diff --git a/backend/internal/rules/runner/runner.go b/backend/internal/rules/runner/runner.go index 0090d04..03e5fc7 100644 --- a/backend/internal/rules/runner/runner.go +++ b/backend/internal/rules/runner/runner.go @@ -30,12 +30,14 @@ type Runner struct { // New creates a new Runner func New(db *sql.DB, connService *services.ConnectionService, logger *logging.Logger) *Runner { + eng := engine.NewEngine(logger) + eng.SetManagedStore(repository.NewManagedMemberRepository(db)) return &Runner{ ruleRepo: repository.NewRuleRepository(db), connRepo: repository.NewConnectionRepository(db), runRepo: repository.NewRuleRunRepository(db), connService: connService, - engine: engine.NewEngine(logger), + engine: eng, logger: logger, } } @@ -123,6 +125,30 @@ func (r *Runner) PreviewRule(ctx context.Context, ruleID string) (*engine.Previe return r.engine.Preview(ctx, rule, conn, client) } +// PreviewRuleSpec previews an unsaved rule draft: it resolves the connection by +// ID, builds a client, and returns matched objects and planned actions without +// persisting the rule. Used by the editor's live preview. +func (r *Runner) PreviewRuleSpec(ctx context.Context, rule *models.Rule) (*engine.PreviewResult, error) { + if rule.ADConnectionID == "" { + return nil, fmt.Errorf("adConnectionId is required") + } + conn, err := r.connRepo.GetByID(rule.ADConnectionID) + if err != nil { + return nil, fmt.Errorf("loading AD connection: %w", err) + } + if conn == nil { + return nil, fmt.Errorf("AD connection not found: %s", rule.ADConnectionID) + } + + client, err := r.connService.BuildClient(conn) + if err != nil { + return nil, fmt.Errorf("building LDAP client: %w", err) + } + defer client.Close() + + return r.engine.Preview(ctx, rule, conn, client) +} + func (r *Runner) persistActionRecord(runID string, ar engine.ActionResult) { status := "Succeeded" if !ar.Success { diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index fdaf230..5470a7a 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -191,6 +191,7 @@ func (s *Server) setupRoutes() { r.Delete("/{id}", connectionsHandler.Delete) r.Post("/{id}/test", connectionsHandler.Test) r.Post("/{id}/query-preview", connectionsHandler.QueryPreview) + r.Get("/{id}/directory", connectionsHandler.DirectorySearch) r.Post("/{id}/enable", connectionsHandler.Enable) r.Post("/{id}/disable", connectionsHandler.Disable) }) @@ -213,6 +214,8 @@ func (s *Server) setupRoutes() { r.Route("/rules", func(r chi.Router) { r.Get("/", rulesHandler.List) r.Post("/", rulesHandler.Create) + r.Get("/metadata", rulesHandler.Metadata) + r.Post("/preview", rulesHandler.PreviewSpec) r.Get("/{id}", rulesHandler.Get) r.Put("/{id}", rulesHandler.Update) r.Delete("/{id}", rulesHandler.Delete) diff --git a/backend/internal/services/connection_service.go b/backend/internal/services/connection_service.go index fda53cd..930e00f 100644 --- a/backend/internal/services/connection_service.go +++ b/backend/internal/services/connection_service.go @@ -261,6 +261,76 @@ func (s *ConnectionService) QueryPreview(conn *models.ADConnection, input QueryP return result, nil } +// DirectoryObject is a lightweight directory entry returned by SearchDirectory +// for operator-facing pickers (target groups, base/target OUs). +type DirectoryObject struct { + DN string `json:"dn"` + Name string `json:"name"` + CanonicalName string `json:"canonicalName"` + ObjectType string `json:"objectType"` +} + +// SearchDirectory finds groups or organizational units (and users/computers) +// matching an optional substring, for use by the rule editor's pickers. +// objectType is one of "Group", "OU", "User", "Computer". +func (s *ConnectionService) SearchDirectory(conn *models.ADConnection, objectType, q string, limit int) ([]DirectoryObject, error) { + if limit <= 0 || limit > 200 { + limit = 50 + } + + var classFilter, nameAttr string + switch objectType { + case "OU", "OrganizationalUnit": + classFilter, nameAttr = "(objectClass=organizationalUnit)", "ou" + case "User": + classFilter, nameAttr = "(&(objectCategory=person)(objectClass=user))", "cn" + case "Computer": + classFilter, nameAttr = "(objectClass=computer)", "cn" + default: // Group + objectType, classFilter, nameAttr = "Group", "(objectClass=group)", "cn" + } + + filter := classFilter + if q = strings.TrimSpace(q); q != "" { + esc := ldap.EscapeFilterValue(q) + if nameAttr == "ou" { + filter = fmt.Sprintf("(&%s(ou=*%s*))", classFilter, esc) + } else { + filter = fmt.Sprintf("(&%s(|(cn=*%s*)(sAMAccountName=*%s*)))", classFilter, esc, esc) + } + } + + client, err := s.BuildClient(conn) + if err != nil { + return nil, err + } + defer client.Close() + + entries, err := client.Search(conn.RootDN, goldap.ScopeWholeSubtree, filter, + []string{"cn", "ou", "distinguishedName", "canonicalName"}) + if err != nil { + return nil, err + } + + out := make([]DirectoryObject, 0, limit) + for _, entry := range entries { + if len(out) >= limit { + break + } + name := entry.GetAttributeValue(nameAttr) + if name == "" { + name = entry.GetAttributeValue("cn") + } + out = append(out, DirectoryObject{ + DN: entry.DN, + Name: name, + CanonicalName: ldap.CanonicalName(entry.DN, entry.GetAttributeValue("canonicalName")), + ObjectType: objectType, + }) + } + return out, nil +} + func mapScope(scope string) int { switch scope { case "base", "Base", "BaseObject": diff --git a/backend/internal/services/rule_service.go b/backend/internal/services/rule_service.go index 218957d..764403c 100644 --- a/backend/internal/services/rule_service.go +++ b/backend/internal/services/rule_service.go @@ -69,6 +69,16 @@ func (s *RuleService) GetByID(id string) (*models.Rule, error) { return s.repo.GetByID(id) } +// ReplaceLogic replaces a rule's condition groups and actions wholesale. Used +// by the rule editor to persist the filter and target-group actions. +func (s *RuleService) ReplaceLogic(ruleID string, groups []models.RuleConditionGroup, actions []models.RuleAction) error { + if err := s.repo.ReplaceLogic(ruleID, groups, actions); err != nil { + return err + } + s.logger.Info("RuleService", "Replaced logic for rule %s (%d groups, %d actions)", ruleID, len(groups), len(actions)) + return nil +} + // RuleListItem represents a rule in list views type RuleListItem struct { ID string diff --git a/backend/internal/types/types.go b/backend/internal/types/types.go index 306ac30..888476b 100644 --- a/backend/internal/types/types.go +++ b/backend/internal/types/types.go @@ -34,7 +34,13 @@ const ( OperatorNotExists ConditionOperator = "NotExists" OperatorGreaterThan ConditionOperator = "GreaterThan" OperatorLessThan ConditionOperator = "LessThan" - OperatorCustomLdap ConditionOperator = "CustomLdap" + // OperatorMemberOf matches objects that are direct members of a group. + OperatorMemberOf ConditionOperator = "MemberOf" + // OperatorMemberOfRecursive matches objects that are members of a group + // directly or transitively (nested groups) using the LDAP_MATCHING_RULE_IN_CHAIN + // (1.2.840.113556.1.4.1941) AD matching rule. + OperatorMemberOfRecursive ConditionOperator = "MemberOfRecursive" + OperatorCustomLdap ConditionOperator = "CustomLdap" ) // ActionType for rule actions @@ -46,6 +52,27 @@ const ( ActionAddGroupToGroup ActionType = "AddGroupToGroup" ActionEnsureGroupExists ActionType = "EnsureGroupExists" ActionRemoveFromGroupIfNoMatch ActionType = "RemoveFromGroupIfNoLongerMatched" + // ActionSyncGroupMembership reconciles a target group's membership against + // the matched object set as a single set operation: add matched objects + // that are missing, and (depending on SyncMode) remove members that no + // longer match. This is the dynamic-group action. + ActionSyncGroupMembership ActionType = "SyncGroupMembership" +) + +// SyncMode controls how ActionSyncGroupMembership reconciles a target group. +type SyncMode string + +const ( + // SyncModeFull makes the group's membership exactly equal the matched set: + // matched objects are added, and any current member that no longer matches + // is removed β€” including members added by hand. The rule owns the group. + SyncModeFull SyncMode = "FullSync" + // SyncModeManaged adds matched objects and removes only members that this + // rule previously added (tracked ownership); members added by other means + // are left untouched. + SyncModeManaged SyncMode = "ManagedAdd" + // SyncModeAddOnly only adds matched objects and never removes anyone. + SyncModeAddOnly SyncMode = "AddOnly" ) // ExecutionMode for rules -- 2.52.0 From d5b8e693df7ee7aae5ae922e405edd742935ea8c Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 15:11:28 -0400 Subject: [PATCH 22/34] fix(repository): flatten GetByID condition loading to avoid nested cursor getConditionGroups held an open rows cursor while calling getConditions per group, so each GetByID needed two pooled connections at once and could deadlock under a small/exhausted pool. Read all groups first, close the cursor, then load every group's conditions in a single bulk IN-query keyed by condition_group_id. The repository round-trip test now runs against a single-connection pool, which guards the no-nested-cursor invariant. Co-Authored-By: Claude Opus 4.8 --- backend/internal/repository/rules.go | 58 ++++++++++++++----- .../internal/repository/timestamps_test.go | 11 ++-- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/backend/internal/repository/rules.go b/backend/internal/repository/rules.go index 716c142..655a47c 100644 --- a/backend/internal/repository/rules.go +++ b/backend/internal/repository/rules.go @@ -3,6 +3,7 @@ package repository import ( "database/sql" + "strings" "time" "github.com/Grace-Solutions/OrchestrAD/internal/models" @@ -97,6 +98,10 @@ func (r *RuleRepository) GetByID(id string) (*models.Rule, error) { } func (r *RuleRepository) getConditionGroups(ruleID string) ([]models.RuleConditionGroup, error) { + // Read all groups first and close the cursor before loading conditions. + // Loading conditions per-group while this cursor is still open would hold + // one pooled connection and acquire a second, which deadlocks when the + // pool is small; a single bulk conditions query avoids that entirely. rows, err := r.db.Query(` SELECT id, rule_id, name, is_enabled, join_operator, sort_order, negate, created_utc, updated_utc @@ -107,9 +112,9 @@ func (r *RuleRepository) getConditionGroups(ruleID string) ([]models.RuleConditi if err != nil { return nil, err } - defer rows.Close() var groups []models.RuleConditionGroup + groupIDs := []string{} for rows.Next() { var g models.RuleConditionGroup var isEnabled, negate int @@ -118,39 +123,60 @@ func (r *RuleRepository) getConditionGroups(ruleID string) ([]models.RuleConditi &g.ID, &g.RuleID, &g.Name, &isEnabled, &g.JoinOperator, &g.SortOrder, &negate, &createdStr, &updatedStr, ); err != nil { + rows.Close() return nil, err } g.IsEnabled = intToBool(isEnabled) g.Negate = intToBool(negate) g.CreatedUTC = parseTimeOrZero(createdStr) g.UpdatedUTC = parseTimeOrZero(updatedStr) - - // Load conditions for this group - g.Conditions, err = r.getConditions(g.ID) - if err != nil { - return nil, err - } groups = append(groups, g) + groupIDs = append(groupIDs, g.ID) } + if err := rows.Err(); err != nil { + rows.Close() + return nil, err + } + rows.Close() - return groups, rows.Err() + byGroup, err := r.getConditionsForGroups(groupIDs) + if err != nil { + return nil, err + } + for i := range groups { + groups[i].Conditions = byGroup[groups[i].ID] + } + return groups, nil } -func (r *RuleRepository) getConditions(groupID string) ([]models.RuleCondition, error) { - rows, err := r.db.Query(` +// getConditionsForGroups loads the conditions for every group in one query and +// returns them keyed by condition_group_id, preserving sort order. +func (r *RuleRepository) getConditionsForGroups(groupIDs []string) (map[string][]models.RuleCondition, error) { + out := make(map[string][]models.RuleCondition, len(groupIDs)) + if len(groupIDs) == 0 { + return out, nil + } + + placeholders := make([]string, len(groupIDs)) + args := make([]any, len(groupIDs)) + for i, id := range groupIDs { + placeholders[i] = "?" + args[i] = id + } + query := ` SELECT id, condition_group_id, is_enabled, attribute_name, operator, value_type, comparison_value, custom_ldap_expression, negate, sort_order, case_sensitive, created_utc, updated_utc FROM rule_conditions - WHERE condition_group_id = ? AND deleted_utc IS NULL - ORDER BY sort_order - `, groupID) + WHERE condition_group_id IN (` + strings.Join(placeholders, ",") + `) AND deleted_utc IS NULL + ORDER BY sort_order` + + rows, err := r.db.Query(query, args...) if err != nil { return nil, err } defer rows.Close() - var conditions []models.RuleCondition for rows.Next() { var c models.RuleCondition var isEnabled, negate, caseSensitive int @@ -167,10 +193,10 @@ func (r *RuleRepository) getConditions(groupID string) ([]models.RuleCondition, c.CaseSensitive = intToBool(caseSensitive) c.CreatedUTC = parseTimeOrZero(createdStr) c.UpdatedUTC = parseTimeOrZero(updatedStr) - conditions = append(conditions, c) + out[c.ConditionGroupID] = append(out[c.ConditionGroupID], c) } - return conditions, rows.Err() + return out, rows.Err() } // List retrieves rules with summary counts and without nested detail. diff --git a/backend/internal/repository/timestamps_test.go b/backend/internal/repository/timestamps_test.go index cb74423..fa068f7 100644 --- a/backend/internal/repository/timestamps_test.go +++ b/backend/internal/repository/timestamps_test.go @@ -16,11 +16,12 @@ func migratedDB(t *testing.T) *sql.DB { t.Helper() database, err := db.New(config.DatabaseConfig{ Path: filepath.Join(t.TempDir(), "repo_test.db"), - // Mirror the production pool (default 25). GetByID nests a query - // (getConditionGroups holds a rows cursor while calling getConditions), - // which deadlocks against a single-connection pool but is safe here. - MaxOpenConns: 5, - MaxIdleConns: 5, + // A single connection guards the invariant that repository reads never + // hold one cursor open while opening another query on the pool (which + // would deadlock here). GetByID loads conditions in one bulk query + // rather than per-group, so this passes. + MaxOpenConns: 1, + MaxIdleConns: 1, WALMode: true, ForeignKeys: true, }, logging.Default()) -- 2.52.0 From e513687b5fb56e1eb3148ecd01afc6e1c6786f02 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 15:19:30 -0400 Subject: [PATCH 23/34] feat(ui): dynamic-group rule editor Replace the old read-only rule form (which could only edit scalar metadata and showed "author via config import" for conditions/actions) with a full tabbed editor that authors the whole rule: - Scope: connection, object type, and a searchable base-OU picker. - Filter: a condition-group builder with attribute autocomplete (from the backend metadata), operator dropdowns, per-condition/-group NOT, AND/OR join within and across groups, and a raw-LDAP escape hatch. memberOf operators render a group picker for the value. - Target & action: choose the action (Sync membership to group / Move to OU / Ensure group exists / Add to group), pick or paste the target group/OU, set the sync mode (Full / Managed / Add-only) and create-if-missing; multiple actions supported. - Schedule: run manually, on an existing schedule, or create an interval or cron schedule inline. - Preview: live dry-run against the directory (POST /rules/preview) showing match count, generated filter, planned +add/-remove, and sample matches. New DirectoryPicker (search-or-type Autocomplete backed by the connection directory endpoint) is reused for base OU, target group, target OU, and memberOf values. API layer gains RuleMetadata/DirectoryObject/RuleInput types and RulesApi.metadata/previewSpec + ConnectionsApi.directory. Co-Authored-By: Claude Opus 4.8 --- .../src/app/(app)/rules/DirectoryPicker.tsx | 102 +++ .../src/app/(app)/rules/RuleEditorDialog.tsx | 591 ++++++++++++++++++ .../src/app/(app)/rules/RuleFormDialog.tsx | 202 ------ frontend/src/app/(app)/rules/page.tsx | 4 +- frontend/src/lib/api/resources.ts | 14 +- frontend/src/lib/api/types.ts | 80 +++ 6 files changed, 787 insertions(+), 206 deletions(-) create mode 100644 frontend/src/app/(app)/rules/DirectoryPicker.tsx create mode 100644 frontend/src/app/(app)/rules/RuleEditorDialog.tsx delete mode 100644 frontend/src/app/(app)/rules/RuleFormDialog.tsx diff --git a/frontend/src/app/(app)/rules/DirectoryPicker.tsx b/frontend/src/app/(app)/rules/DirectoryPicker.tsx new file mode 100644 index 0000000..852fa82 --- /dev/null +++ b/frontend/src/app/(app)/rules/DirectoryPicker.tsx @@ -0,0 +1,102 @@ +"use client"; + +import Autocomplete from "@mui/material/Autocomplete"; +import CircularProgress from "@mui/material/CircularProgress"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useEffect, useMemo, useRef, useState } from "react"; + +import { ConnectionsApi } from "@/lib/api/resources"; +import type { DirectoryObject } from "@/lib/api/types"; + +interface Props { + connectionId: string; + type: "Group" | "OU"; + value: string; // a DN or canonical string + onChange: (dn: string) => void; + label: string; + disabled?: boolean; + helperText?: string; +} + +// DirectoryPicker is a search-or-type control: it live-searches the directory +// for groups/OUs as the operator types, but also accepts a hand-entered DN or +// canonical path (freeSolo), so a target can be selected or specified manually. +export default function DirectoryPicker({ connectionId, type, value, onChange, label, disabled, helperText }: Props) { + const [input, setInput] = useState(""); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + const timer = useRef | null>(null); + + useEffect(() => { + if (!connectionId || disabled) return; + if (timer.current) clearTimeout(timer.current); + timer.current = setTimeout(() => { + setLoading(true); + ConnectionsApi.directory(connectionId, type, input, 25) + .then(setOptions) + .catch(() => setOptions([])) + .finally(() => setLoading(false)); + }, 300); + return () => { + if (timer.current) clearTimeout(timer.current); + }; + }, [connectionId, type, input, disabled]); + + const labelByDn = useMemo(() => { + const m = new Map(); + options.forEach((o) => m.set(o.dn, o)); + return m; + }, [options]); + + return ( + + freeSolo + disabled={disabled} + options={options} + loading={loading} + filterOptions={(x) => x} // server-side filtering + value={value} + onChange={(_, v) => { + if (v == null) onChange(""); + else if (typeof v === "string") onChange(v); + else onChange(v.dn); + }} + onInputChange={(_, v, reason) => { + if (reason === "input") setInput(v); + }} + getOptionLabel={(o) => (typeof o === "string" ? o : o.dn)} + isOptionEqualToValue={(o, v) => (typeof o === "string" ? o === v : o.dn === v)} + renderOption={(props, o) => { + const obj = typeof o === "string" ? labelByDn.get(o) : o; + return ( +
  1. +
    + {obj?.name ?? (typeof o === "string" ? o : o.name)} + + {obj?.canonicalName ?? (typeof o === "string" ? o : o.dn)} + +
    +
  2. + ); + }} + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + ); +} diff --git a/frontend/src/app/(app)/rules/RuleEditorDialog.tsx b/frontend/src/app/(app)/rules/RuleEditorDialog.tsx new file mode 100644 index 0000000..9e6c0b3 --- /dev/null +++ b/frontend/src/app/(app)/rules/RuleEditorDialog.tsx @@ -0,0 +1,591 @@ +"use client"; + +import Alert from "@mui/material/Alert"; +import Autocomplete from "@mui/material/Autocomplete"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Chip from "@mui/material/Chip"; +import CircularProgress from "@mui/material/CircularProgress"; +import Dialog from "@mui/material/Dialog"; +import DialogActions from "@mui/material/DialogActions"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import Divider from "@mui/material/Divider"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import IconButton from "@mui/material/IconButton"; +import MenuItem from "@mui/material/MenuItem"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Switch from "@mui/material/Switch"; +import Tab from "@mui/material/Tab"; +import Tabs from "@mui/material/Tabs"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; +import AddOutlinedIcon from "@mui/icons-material/AddOutlined"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; +import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { ApiError } from "@/lib/api/client"; +import { ConnectionsApi, RulesApi, SchedulesApi } from "@/lib/api/resources"; +import type { + ADConnection, + DirectoryObject, + RuleInput, + RuleMetadata, + RulePreviewResult, + Schedule, +} from "@/lib/api/types"; + +import DirectoryPicker from "./DirectoryPicker"; + +interface Props { + open: boolean; + ruleId: string | null; + onClose: () => void; + onSaved: () => void | Promise; +} + +interface CondDraft { + attributeName: string; + operator: string; + value: string; + negate: boolean; +} +interface GroupDraft { + name: string; + joinOperator: string; + negate: boolean; + conditions: CondDraft[]; +} +interface ActionDraft { + actionType: string; + targetGroupDn: string; + targetOu: string; + syncMode: string; + createIfMissing: boolean; + groupScope: string; + groupType: string; +} + +type ScheduleMode = "manual" | "existing" | "interval" | "cron"; + +const newCondition = (): CondDraft => ({ attributeName: "", operator: "Equals", value: "", negate: false }); +const newGroup = (): GroupDraft => ({ name: "", joinOperator: "AND", negate: false, conditions: [newCondition()] }); +const newAction = (): ActionDraft => ({ + actionType: "SyncGroupMembership", + targetGroupDn: "", + targetOu: "", + syncMode: "FullSync", + createIfMissing: true, + groupScope: "Global", + groupType: "Security", +}); + +export default function RuleEditorDialog({ open, ruleId, onClose, onSaved }: Props) { + const [tab, setTab] = useState(0); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + + const [meta, setMeta] = useState(null); + const [connections, setConnections] = useState([]); + const [schedules, setSchedules] = useState([]); + + // Scalar fields + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isEnabled, setIsEnabled] = useState(true); + const [adConnectionId, setAdConnectionId] = useState(""); + const [objectType, setObjectType] = useState("User"); + const [baseDnOverride, setBaseDnOverride] = useState(""); + const [searchScopeOverride, setSearchScopeOverride] = useState(""); + const [executionMode, setExecutionMode] = useState("Apply"); + const [groupJoinOperator, setGroupJoinOperator] = useState("AND"); + const [stopOnError, setStopOnError] = useState(false); + + // Logic + const [groups, setGroups] = useState([newGroup()]); + const [actions, setActions] = useState([newAction()]); + + // Schedule + const [scheduleMode, setScheduleMode] = useState("manual"); + const [scheduleId, setScheduleId] = useState(""); + const [intervalValue, setIntervalValue] = useState(1); + const [intervalUnit, setIntervalUnit] = useState("Hours"); + const [cronExpression, setCronExpression] = useState("0 0 * * * *"); + + // Preview + const [preview, setPreview] = useState(null); + const [previewing, setPreviewing] = useState(false); + const [previewError, setPreviewError] = useState(null); + + useEffect(() => { + if (!open) return; + setTab(0); + setError(null); + setPreview(null); + setPreviewError(null); + // reset + setName(""); setDescription(""); setIsEnabled(true); setAdConnectionId(""); + setObjectType("User"); setBaseDnOverride(""); setSearchScopeOverride(""); + setExecutionMode("Apply"); setGroupJoinOperator("AND"); setStopOnError(false); + setGroups([newGroup()]); setActions([newAction()]); + setScheduleMode("manual"); setScheduleId(""); setIntervalValue(1); setIntervalUnit("Hours"); + setCronExpression("0 0 * * * *"); + + RulesApi.metadata().then(setMeta).catch(() => setMeta(null)); + ConnectionsApi.list({ pageSize: 200 }).then((r) => setConnections(r.items)).catch(() => setConnections([])); + SchedulesApi.list({ pageSize: 200 }).then((r) => setSchedules(r.items)).catch(() => setSchedules([])); + + if (ruleId) { + setLoading(true); + RulesApi.get(ruleId) + .then((r) => { + setName(r.name); setDescription(r.description ?? ""); setIsEnabled(r.isEnabled); + setAdConnectionId(r.adConnectionId); setObjectType(r.objectType); + setBaseDnOverride(r.baseDnOverride ?? ""); setSearchScopeOverride(r.searchScopeOverride ?? ""); + setExecutionMode(r.executionMode); setGroupJoinOperator(r.groupJoinOperator); + setStopOnError(r.stopOnError); + setGroups( + r.conditionGroups.length + ? r.conditionGroups.map((g) => ({ + name: g.name ?? "", + joinOperator: g.joinOperator, + negate: g.negate, + conditions: g.conditions.length + ? g.conditions.map((c) => ({ + attributeName: c.attributeName, + operator: c.operator, + value: c.operator === "CustomLdap" ? (c.customLdapExpression ?? "") : (c.comparisonValue ?? ""), + negate: c.negate, + })) + : [newCondition()], + })) + : [newGroup()], + ); + setActions( + r.actions.length + ? r.actions.map((a) => { + let cfg: Record = {}; + try { cfg = JSON.parse(a.configurationJson || "{}"); } catch { /* ignore */ } + return { + actionType: a.actionType, + targetGroupDn: String(cfg.targetGroupDn ?? ""), + targetOu: String(cfg.targetOu ?? ""), + syncMode: String(cfg.syncMode ?? "FullSync"), + createIfMissing: Boolean(cfg.createIfMissing ?? cfg.createOuIfMissing ?? true), + groupScope: String(cfg.groupScope ?? "Global"), + groupType: String(cfg.groupType ?? "Security"), + }; + }) + : [newAction()], + ); + if (r.scheduleId) { setScheduleMode("existing"); setScheduleId(r.scheduleId); } + }) + .catch((err) => setError(err instanceof ApiError ? err.message : "Failed to load rule")) + .finally(() => setLoading(false)); + } + }, [open, ruleId]); + + const operators = meta?.operators ?? []; + const attrOptions = useMemo( + () => (meta?.commonAttributes?.[objectType] ?? []).map((a) => a.name), + [meta, objectType], + ); + const opNeedsValue = useCallback( + (op: string) => operators.find((o) => o.value === op)?.needsValue ?? true, + [operators], + ); + const opIsCustom = useCallback( + (op: string) => operators.find((o) => o.value === op)?.custom ?? false, + [operators], + ); + + const buildBody = (): RuleInput => ({ + name: name.trim(), + description: description.trim() || undefined, + isEnabled, + adConnectionId, + objectType, + baseDnOverride: baseDnOverride.trim() || undefined, + searchScopeOverride: searchScopeOverride || undefined, + executionMode, + groupJoinOperator, + stopOnError, + conditionGroups: groups + .filter((g) => g.conditions.some((c) => c.attributeName.trim() || c.value.trim())) + .map((g) => ({ + name: g.name || undefined, + joinOperator: g.joinOperator, + negate: g.negate, + isEnabled: true, + conditions: g.conditions + .filter((c) => c.attributeName.trim() || c.value.trim() || !opNeedsValue(c.operator)) + .map((c) => ({ + attributeName: c.attributeName.trim(), + operator: c.operator, + negate: c.negate, + isEnabled: true, + comparisonValue: opIsCustom(c.operator) ? undefined : (opNeedsValue(c.operator) ? c.value : undefined), + customLdapExpression: opIsCustom(c.operator) ? c.value : undefined, + })), + })), + actions: actions.map((a) => ({ + actionType: a.actionType, + isEnabled: true, + configurationJson: JSON.stringify(actionConfig(a)), + })), + }); + + const runPreview = async () => { + setPreviewing(true); + setPreviewError(null); + try { + const body = buildBody(); + if (!body.adConnectionId) throw new Error("Select an AD connection first"); + setPreview(await RulesApi.previewSpec(body)); + } catch (err) { + setPreviewError(err instanceof ApiError ? err.message : (err as Error).message); + setPreview(null); + } finally { + setPreviewing(false); + } + }; + + const ensureScheduleId = async (): Promise => { + if (scheduleMode === "manual") return undefined; + if (scheduleMode === "existing") return scheduleId || undefined; + // create a schedule then return its id + const body: Partial = + scheduleMode === "interval" + ? { + name: `${name.trim() || "Rule"} β€” every ${intervalValue} ${intervalUnit.toLowerCase()}`, + isEnabled: true, scheduleKind: "Easy", + easyIntervalValue: intervalValue, easyIntervalUnit: intervalUnit, timezoneMode: "UTC", + } + : { + name: `${name.trim() || "Rule"} β€” cron`, + isEnabled: true, scheduleKind: "Cron", cronExpression, timezoneMode: "UTC", + }; + const created = await SchedulesApi.create(body); + return created.id; + }; + + const handleSave = async () => { + if (!name.trim()) { setTab(0); return setError("Name is required"); } + if (!adConnectionId) { setTab(0); return setError("AD connection is required"); } + setSaving(true); + setError(null); + try { + const sid = await ensureScheduleId(); + const body = { ...buildBody(), scheduleId: sid }; + if (ruleId) await RulesApi.update(ruleId, body); + else await RulesApi.create(body); + await onSaved(); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to save rule"); + } finally { + setSaving(false); + } + }; + + // ---- render helpers ---- + const setGroup = (gi: number, patch: Partial) => + setGroups((gs) => gs.map((g, i) => (i === gi ? { ...g, ...patch } : g))); + const setCond = (gi: number, ci: number, patch: Partial) => + setGroups((gs) => gs.map((g, i) => (i === gi ? { ...g, conditions: g.conditions.map((c, j) => (j === ci ? { ...c, ...patch } : c)) } : g))); + const setAction = (ai: number, patch: Partial) => + setActions((as) => as.map((a, i) => (i === ai ? { ...a, ...patch } : a))); + + return ( + + {ruleId ? "Edit dynamic group rule" : "New dynamic group rule"} + + {loading ? ( + + ) : ( + <> + {error && setError(null)}>{error}} + setTab(v)} sx={{ mb: 2 }} variant="scrollable" scrollButtons="auto"> + + + + + + + + {/* ---- Scope ---- */} + {tab === 0 && ( + + setName(e.target.value)} required fullWidth /> + setDescription(e.target.value)} fullWidth multiline rows={2} /> + + setAdConnectionId(e.target.value)} fullWidth required> + {connections.map((c) => {c.name})} + + setObjectType(e.target.value)} fullWidth> + {(meta?.objectTypes ?? [{ value: "User", label: "Users" }]).map((o) => {o.label})} + + + + + setSearchScopeOverride(e.target.value)} fullWidth> + (connection default) + {(meta?.searchScopes ?? []).map((s) => {s.label})} + + setExecutionMode(e.target.value)} fullWidth> + Apply (make changes) + Preview only (dry-run) + + + + setIsEnabled(v)} />} label="Enabled" /> + setStopOnError(v)} />} label="Stop on first error" /> + + + )} + + {/* ---- Filter ---- */} + {tab === 1 && ( + + + Select the objects to sync. Objects match when they satisfy the condition groups below. + + {groups.length > 1 && ( + setGroupJoinOperator(e.target.value)} sx={{ maxWidth: 260 }}> + {(meta?.joinOperators ?? [{ value: "AND", label: "Match ALL of" }, { value: "OR", label: "Match ANY of" }]).map((o) => + {o.label})} + + )} + {groups.map((g, gi) => ( + + + Group {gi + 1} + setGroup(gi, { joinOperator: e.target.value })} sx={{ minWidth: 150 }}> + {(meta?.joinOperators ?? [{ value: "AND", label: "Match ALL of" }, { value: "OR", label: "Match ANY of" }]).map((o) => + {o.label})} + + setGroup(gi, { negate: v })} />} label="NOT" /> + + {groups.length > 1 && ( + setGroups((gs) => gs.filter((_, i) => i !== gi))}> + )} + + + {g.conditions.map((c, ci) => ( + + setCond(gi, ci, { attributeName: v })} + renderInput={(p) => } + /> + setCond(gi, ci, { operator: e.target.value })} sx={{ minWidth: 200 }}> + {operators.map((o) => {o.label})} + + {opNeedsValue(c.operator) && ( + (c.operator === "MemberOf" || c.operator === "MemberOfRecursive") ? ( + + setCond(gi, ci, { value: dn })} /> + + ) : ( + setCond(gi, ci, { value: e.target.value })} sx={{ flex: 1, minWidth: 180 }} + placeholder={operators.find((o) => o.value === c.operator)?.hint} /> + ) + )} + setCond(gi, ci, { negate: v })} />} label="NOT" /> + + + setGroup(gi, { conditions: g.conditions.filter((_, j) => j !== ci) })}> + + + + + + ))} + + + + ))} + + + )} + + {/* ---- Target & action ---- */} + {tab === 2 && ( + + What to do with the matched objects. + {actions.map((a, ai) => ( + + + setAction(ai, { actionType: e.target.value })} sx={{ minWidth: 280 }}> + {(meta?.actionTypes ?? [{ value: "SyncGroupMembership", label: "Sync membership to group" }]).map((o) => + {o.label})} + + + {meta?.actionTypes?.find((t) => t.value === a.actionType)?.description} + + {actions.length > 1 && ( + setActions((as) => as.filter((_, i) => i !== ai))}> + )} + + + {(a.actionType === "SyncGroupMembership" || a.actionType === "AddToGroup" || a.actionType === "EnsureGroupExists") && ( + + setAction(ai, { targetGroupDn: dn })} + helperText="Search, or paste a group DN / canonical path." /> + {a.actionType === "SyncGroupMembership" && ( + setAction(ai, { syncMode: e.target.value })} fullWidth + helperText={meta?.syncModes?.find((m) => m.value === a.syncMode)?.description}> + {(meta?.syncModes ?? [{ value: "FullSync", label: "Full sync (add + remove)" }]).map((m) => + {m.label})} + + )} + + setAction(ai, { createIfMissing: v })} />} label="Create group if missing" /> + {a.createIfMissing && ( + <> + setAction(ai, { groupScope: e.target.value })} sx={{ minWidth: 140 }}> + {["Global", "DomainLocal", "Universal"].map((s) => {s})} + + setAction(ai, { groupType: e.target.value })} sx={{ minWidth: 150 }}> + {["Security", "Distribution"].map((s) => {s})} + + + )} + + + )} + + {a.actionType === "MoveToOu" && ( + + setAction(ai, { targetOu: dn })} + helperText="Move each matched object into this OU." /> + setAction(ai, { createIfMissing: v })} />} label="Create OU path if missing" /> + + )} + + ))} + + + )} + + {/* ---- Schedule ---- */} + {tab === 3 && ( + + setScheduleMode(e.target.value as ScheduleMode)}> + Manually only + On an existing schedule + On an interval (create) + On a cron expression (create) + + {scheduleMode === "existing" && ( + setScheduleId(e.target.value)}> + Select… + {schedules.map((s) => {s.name})} + + )} + {scheduleMode === "interval" && ( + + Every + setIntervalValue(Math.max(1, Number(e.target.value)))} sx={{ width: 100 }} /> + setIntervalUnit(e.target.value)} sx={{ minWidth: 140 }}> + {(meta?.scheduleUnits ?? [{ value: "Hours", label: "hours" }]).map((u) => {u.label})} + + + )} + {scheduleMode === "cron" && ( + setCronExpression(e.target.value)} + helperText="6-field (with seconds), UTC. e.g. '0 0 * * * *' = top of every hour." /> + )} + + )} + + {/* ---- Preview ---- */} + {tab === 4 && ( + + + + Dry-run against the directory β€” makes no changes. + + {previewError && {previewError}} + {preview && ( + <> + + {preview.matchedObjects.length} object(s) match. + {preview.generatedFilter} + + {(preview.warnings ?? []).map((w, i) => {w})} + + Planned changes + + {preview.plannedActions + .filter((p) => p.actionType === "SyncGroupMembership") + .map((p, i) => ( + {p.description} + ))} + {preview.plannedActions.length === 0 && No changes.} + + + Sample matches + + {preview.matchedObjects.slice(0, 20).map((m) => ( + + {m.canonicalName || m.dn} + + ))} + {preview.matchedObjects.length > 20 && ( + …and {preview.matchedObjects.length - 20} more + )} + + + )} + + )} + + )} + + + + n + g.conditions.length, 0)} condition(s) Β· ${actions.length} action(s)`} /> + + + + + + ); +} + +function actionConfig(a: ActionDraft): Record { + if (a.actionType === "MoveToOu") { + return { targetOu: a.targetOu, createOuIfMissing: a.createIfMissing }; + } + return { + targetGroupDn: a.targetGroupDn, + syncMode: a.actionType === "SyncGroupMembership" ? a.syncMode : undefined, + createIfMissing: a.createIfMissing, + groupScope: a.groupScope, + groupType: a.groupType, + }; +} diff --git a/frontend/src/app/(app)/rules/RuleFormDialog.tsx b/frontend/src/app/(app)/rules/RuleFormDialog.tsx deleted file mode 100644 index 0755c36..0000000 --- a/frontend/src/app/(app)/rules/RuleFormDialog.tsx +++ /dev/null @@ -1,202 +0,0 @@ -"use client"; - -import Alert from "@mui/material/Alert"; -import Button from "@mui/material/Button"; -import Chip from "@mui/material/Chip"; -import CircularProgress from "@mui/material/CircularProgress"; -import Dialog from "@mui/material/Dialog"; -import DialogActions from "@mui/material/DialogActions"; -import DialogContent from "@mui/material/DialogContent"; -import DialogTitle from "@mui/material/DialogTitle"; -import Divider from "@mui/material/Divider"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import MenuItem from "@mui/material/MenuItem"; -import Stack from "@mui/material/Stack"; -import Switch from "@mui/material/Switch"; -import TextField from "@mui/material/TextField"; -import Typography from "@mui/material/Typography"; -import { useEffect, useState } from "react"; - -import { ApiError } from "@/lib/api/client"; -import { ConnectionsApi, RulesApi, SchedulesApi } from "@/lib/api/resources"; -import type { ADConnection, Rule, Schedule } from "@/lib/api/types"; - -interface Props { - open: boolean; - ruleId: string | null; - onClose: () => void; - onSaved: () => void | Promise; -} - -const OBJECT_TYPES = ["User", "Computer", "Group"]; -const EXECUTION_MODES = [ - { value: "Apply", label: "Apply (execute actions)" }, - { value: "PreviewOnly", label: "Preview only (dry-run)" }, -]; -const JOIN_OPERATORS = ["AND", "OR"]; -const SEARCH_SCOPES = ["", "Base", "OneLevel", "Subtree"]; - -export default function RuleFormDialog({ open, ruleId, onClose, onSaved }: Props) { - const [rule, setRule] = useState(null); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); - const [error, setError] = useState(null); - const [form, setForm] = useState>({}); - const [connections, setConnections] = useState([]); - const [schedules, setSchedules] = useState([]); - - useEffect(() => { - if (!open) return; - setError(null); - setRule(null); - setForm({ - name: "", - description: "", - isEnabled: true, - adConnectionId: "", - objectType: "User", - baseDnOverride: "", - searchScopeOverride: "", - scheduleId: "", - executionMode: "Apply", - groupJoinOperator: "AND", - maxParallelism: 1, - stopOnError: false, - }); - Promise.all([ - ConnectionsApi.list({ pageSize: 200 }).then((r) => setConnections(r.items)).catch(() => setConnections([])), - SchedulesApi.list({ pageSize: 200 }).then((r) => setSchedules(r.items)).catch(() => setSchedules([])), - ]); - if (ruleId) { - setLoading(true); - RulesApi.get(ruleId) - .then((r) => { setRule(r); setForm(r); }) - .catch((err) => setError(err instanceof ApiError ? err.message : "Failed to load rule")) - .finally(() => setLoading(false)); - } - }, [open, ruleId]); - - const set = (key: K, value: Rule[K] | undefined) => - setForm((f) => ({ ...f, [key]: value })); - - const handleSubmit = async () => { - if (!form.name?.trim()) return setError("Name is required"); - if (!form.adConnectionId) return setError("AD connection is required"); - if (!form.objectType) return setError("Object type is required"); - setSaving(true); - setError(null); - try { - const body: Partial = { - ...form, - name: form.name.trim(), - description: form.description?.trim() || undefined, - baseDnOverride: form.baseDnOverride?.trim() || undefined, - searchScopeOverride: form.searchScopeOverride || undefined, - scheduleId: form.scheduleId || undefined, - }; - if (ruleId) await RulesApi.update(ruleId, body); - else await RulesApi.create(body); - await onSaved(); - } catch (err) { - setError(err instanceof ApiError ? err.message : "Failed to save rule"); - } finally { - setSaving(false); - } - }; - - return ( - - {ruleId ? "Edit Rule" : "New Rule"} - - {loading ? ( - - ) : ( - - {error && {error}} - - Identity - set("name", e.target.value)} required fullWidth /> - set("description", e.target.value)} fullWidth multiline rows={2} /> - set("isEnabled", v)} />} label="Enabled" /> - - - Target Directory - - set("adConnectionId", e.target.value)} fullWidth required> - {connections.map((c) => {c.name})} - - set("objectType", e.target.value)} fullWidth required> - {OBJECT_TYPES.map((o) => {o})} - - - set("baseDnOverride", e.target.value)} fullWidth placeholder="Leave blank to use connection root" /> - set("searchScopeOverride", e.target.value)} fullWidth> - {SEARCH_SCOPES.map((s) => {s || "(use connection default)"})} - - - - Execution - - set("executionMode", e.target.value)} fullWidth> - {EXECUTION_MODES.map((m) => {m.label})} - - set("scheduleId", e.target.value)} fullWidth> - Manual only - {schedules.map((s) => {s.name})} - - - - set("groupJoinOperator", e.target.value)} fullWidth> - {JOIN_OPERATORS.map((o) => {o})} - - set("maxParallelism", e.target.value === "" ? undefined : Number(e.target.value))} fullWidth /> - set("stopOnError", v)} />} label="Stop on error" /> - - - {rule && ( - <> - - - Conditions & Actions - Read-only β€” author via config import - - - - {rule.conditionGroups.length} condition group{rule.conditionGroups.length === 1 ? "" : "s"} - - {rule.conditionGroups.map((g, gi) => ( - - - Group {gi + 1}{g.name ? `: ${g.name}` : ""} ({g.joinOperator}{g.negate ? " / NOT" : ""}) - - {g.conditions.map((c) => ( - - {c.negate ? "NOT " : ""}{c.attributeName} {c.operator}{" "} - {c.operator === "CustomLdap" ? (c.customLdapExpression ?? "") : (c.comparisonValue ?? "")} - - ))} - - ))} - - {rule.actions.length} action{rule.actions.length === 1 ? "" : "s"} - - - {rule.actions.map((a) => ( - - ))} - - - - )} - - )} - - - - - - - ); -} diff --git a/frontend/src/app/(app)/rules/page.tsx b/frontend/src/app/(app)/rules/page.tsx index 86c34fc..d6c0dfc 100644 --- a/frontend/src/app/(app)/rules/page.tsx +++ b/frontend/src/app/(app)/rules/page.tsx @@ -29,7 +29,7 @@ import { RulesApi } from "@/lib/api/resources"; import type { RuleSummary } from "@/lib/api/types"; import { formatDateTime, testResultColor } from "@/lib/format"; -import RuleFormDialog from "./RuleFormDialog"; +import RuleEditorDialog from "./RuleEditorDialog"; import RulePreviewDialog from "./RulePreviewDialog"; export default function RulesPage() { @@ -189,7 +189,7 @@ export default function RulesPage() {
    - setFormOpen(false)} diff --git a/frontend/src/lib/api/resources.ts b/frontend/src/lib/api/resources.ts index 0466f68..4828aa0 100644 --- a/frontend/src/lib/api/resources.ts +++ b/frontend/src/lib/api/resources.ts @@ -18,10 +18,13 @@ import { ImportReport, LoginResponse, Rule, + RuleInput, + RuleMetadata, RulePreviewResult, RuleRun, RuleRunDetail, RuleSummary, + DirectoryObject, Schedule, User, UserInfo, @@ -88,6 +91,10 @@ export const ConnectionsApi = { api.post(`/api/v1/ad-connections/${id}/test`).then((r) => r.data), queryPreview: (id: string, body: Record) => api.post(`/api/v1/ad-connections/${id}/query-preview`, body).then((r) => r.data), + directory: (id: string, type: string, q: string, limit = 25) => + api + .get(`/api/v1/ad-connections/${id}/directory`, { query: { type, q, limit } }) + .then((r) => r.data ?? []), enable: (id: string) => api.post<{ enabled: boolean }>(`/api/v1/ad-connections/${id}/enable`).then((r) => r.data), disable: (id: string) => @@ -112,12 +119,15 @@ export const SchedulesApi = { export const RulesApi = { list: (params?: PageParams) => getList("/api/v1/rules", params), get: (id: string) => api.get(`/api/v1/rules/${id}`).then((r) => r.data), - create: (body: Partial) => api.post("/api/v1/rules", body).then((r) => r.data), - update: (id: string, body: Partial) => + metadata: () => api.get("/api/v1/rules/metadata").then((r) => r.data), + create: (body: RuleInput) => api.post("/api/v1/rules", body).then((r) => r.data), + update: (id: string, body: RuleInput) => api.put(`/api/v1/rules/${id}`, body).then((r) => r.data), remove: (id: string) => api.delete<{ deleted: boolean }>(`/api/v1/rules/${id}`).then((r) => r.data), preview: (id: string) => api.post(`/api/v1/rules/${id}/preview`).then((r) => r.data), + previewSpec: (body: RuleInput) => + api.post("/api/v1/rules/preview", body).then((r) => r.data), run: (id: string) => api.post<{ ruleId: string; status: string; triggeredBy: string }>(`/api/v1/rules/${id}/run`).then((r) => r.data), enable: (id: string) => diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 12ff31f..fd23362 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -369,6 +369,86 @@ export interface RulePreviewResult { plannedActions: RulePreviewPlannedAction[]; } +// ---- Rule editor metadata (GET /rules/metadata) ---- +export interface LabeledValue { + value: string; + label: string; + description?: string; +} + +export interface OperatorMeta { + value: string; + label: string; + needsValue: boolean; + custom?: boolean; + hint?: string; +} + +export interface AttributeMeta { + name: string; + label: string; +} + +export interface RuleMetadata { + objectTypes: LabeledValue[]; + searchScopes: LabeledValue[]; + joinOperators: LabeledValue[]; + operators: OperatorMeta[]; + actionTypes: LabeledValue[]; + syncModes: LabeledValue[]; + scheduleUnits: LabeledValue[]; + commonAttributes: Record; +} + +// A directory object returned by the target pickers. +export interface DirectoryObject { + dn: string; + name: string; + canonicalName: string; + objectType: string; +} + +// The full create/update payload the editor sends (superset of Rule scalars +// plus the authored filter and actions). +export interface RuleConditionInput { + attributeName: string; + operator: string; + comparisonValue?: string; + customLdapExpression?: string; + negate?: boolean; + isEnabled?: boolean; +} + +export interface RuleConditionGroupInput { + name?: string; + joinOperator: string; + negate?: boolean; + isEnabled?: boolean; + conditions: RuleConditionInput[]; +} + +export interface RuleActionInput { + actionType: string; + configurationJson: string; + isEnabled?: boolean; +} + +export interface RuleInput { + name?: string; + description?: string; + isEnabled?: boolean; + adConnectionId?: string; + objectType?: string; + baseDnOverride?: string; + searchScopeOverride?: string; + scheduleId?: string; + executionMode?: string; + groupJoinOperator?: string; + stopOnError?: boolean; + conditionGroups?: RuleConditionGroupInput[]; + actions?: RuleActionInput[]; +} + export interface CredentialExport { id: string; name: string; -- 2.52.0 From dc3c2c9811481b335bf83351135873d8b2965e97 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 15:57:52 -0400 Subject: [PATCH 24/34] feat(connections): schema attribute + distinct-value lookups for filter building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two directory introspection endpoints the rule filter builder uses: - GET /ad-connections/{id}/attributes?objectType=&q= returns the schema attributes that APPLY to the given object type (User/Computer/Group). The applicable set is derived by walking the classSchema hierarchy from the object's class up through subClassOf to top, plus auxiliary classes, unioning each class's may/must-contain attributes; results are filtered by substring and returned with adminDescription. The per-(connection,object type) set is cached for 10 minutes so the walk is not repeated per keystroke. (Computer inherits user attributes, since AD's computer class subclasses user β€” reflected correctly.) - GET /ad-connections/{id}/attribute-values?attribute=&objectType=&q= samples objects and returns the distinct values present for one attribute, so the value field can suggest real directory values. Backed by a new bounded ldap Client.SearchWithLimit that tolerates the server's size-limit response. Attribute names are validated against the LDAP descriptor charset before use in a filter. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/connections_handlers.go | 74 ++++++ backend/internal/directory/ldap/client.go | 23 ++ backend/internal/server/server.go | 2 + .../internal/services/connection_service.go | 243 ++++++++++++++++++ 4 files changed, 342 insertions(+) diff --git a/backend/internal/api/connections_handlers.go b/backend/internal/api/connections_handlers.go index 57a3196..42ae37d 100644 --- a/backend/internal/api/connections_handlers.go +++ b/backend/internal/api/connections_handlers.go @@ -294,6 +294,80 @@ func (h *ConnectionsHandler) DirectorySearch(w http.ResponseWriter, r *http.Requ WriteJSON(w, http.StatusOK, objects) } +// Attributes handles GET /api/v1/ad-connections/{id}/attributes?q=&limit= +// It returns directory schema attributes matching the substring q, for the +// filter builder's attribute autocomplete. +func (h *ConnectionsHandler) Attributes(w http.ResponseWriter, r *http.Request) { + conn, ok := h.loadConn(w, r) + if !ok { + return + } + q := r.URL.Query() + limit := 50 + if n, err := parseInt(q.Get("limit")); err == nil && n > 0 { + limit = n + } + objectType := q.Get("objectType") + if objectType == "" { + objectType = "User" + } + attrs, err := h.service.SchemaAttributes(conn, objectType, q.Get("q"), limit) + if err != nil { + h.logger.Error("ConnectionsHandler", "Attributes failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to read schema attributes") + return + } + WriteJSON(w, http.StatusOK, attrs) +} + +// AttributeValues handles +// GET /api/v1/ad-connections/{id}/attribute-values?attribute=&objectType=&q=&baseDn=&limit= +// It returns the distinct values present for one attribute, for the value +// autocomplete in the filter builder. +func (h *ConnectionsHandler) AttributeValues(w http.ResponseWriter, r *http.Request) { + conn, ok := h.loadConn(w, r) + if !ok { + return + } + q := r.URL.Query() + attribute := q.Get("attribute") + if attribute == "" { + WriteError(w, http.StatusBadRequest, ErrCodeValidation, "attribute is required") + return + } + objectType := q.Get("objectType") + if objectType == "" { + objectType = "User" + } + limit := 50 + if n, err := parseInt(q.Get("limit")); err == nil && n > 0 { + limit = n + } + values, err := h.service.DistinctAttributeValues(conn, objectType, attribute, q.Get("q"), q.Get("baseDn"), limit) + if err != nil { + h.logger.Error("ConnectionsHandler", "AttributeValues failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to read attribute values") + return + } + WriteJSON(w, http.StatusOK, values) +} + +// loadConn resolves the {id} connection or writes the appropriate error. +func (h *ConnectionsHandler) loadConn(w http.ResponseWriter, r *http.Request) (*models.ADConnection, bool) { + id := chi.URLParam(r, "id") + conn, err := h.repo.GetByID(id) + if err != nil { + h.logger.Error("ConnectionsHandler", "GetByID failed: %v", err) + WriteError(w, http.StatusInternalServerError, ErrCodeInternalError, "Failed to load connection") + return nil, false + } + if conn == nil { + WriteError(w, http.StatusNotFound, ErrCodeNotFound, "Connection not found") + return nil, false + } + return conn, true +} + func requestToConnection(req *ConnectionRequest, existing *models.ADConnection) *models.ADConnection { conn := &models.ADConnection{} if existing != nil { diff --git a/backend/internal/directory/ldap/client.go b/backend/internal/directory/ldap/client.go index da6ba71..d1617e5 100644 --- a/backend/internal/directory/ldap/client.go +++ b/backend/internal/directory/ldap/client.go @@ -127,6 +127,29 @@ func (c *Client) Search(baseDN string, scope int, filter string, attributes []st return result.Entries, nil } +// SearchWithLimit performs a non-paged search bounded by sizeLimit, tolerating +// the server's size-limit-exceeded response by returning the partial results. +// Used for bounded lookups (schema browsing, distinct-value sampling) where a +// full paged sweep would be wasteful. +func (c *Client) SearchWithLimit(baseDN string, scope int, filter string, attributes []string, sizeLimit int) ([]*ldap.Entry, error) { + if c.conn == nil { + return nil, fmt.Errorf("not connected") + } + req := ldap.NewSearchRequest( + baseDN, scope, ldap.NeverDerefAliases, + sizeLimit, int(c.config.Timeout.Seconds()), false, + filter, attributes, nil, + ) + res, err := c.conn.Search(req) + if err != nil { + if ldap.IsErrorWithCode(err, ldap.LDAPResultSizeLimitExceeded) && res != nil { + return res.Entries, nil + } + return nil, fmt.Errorf("search failed: %w", err) + } + return res.Entries, nil +} + // SearchOne performs a search expecting exactly one result func (c *Client) SearchOne(baseDN string, scope int, filter string, attributes []string) (*ldap.Entry, error) { entries, err := c.Search(baseDN, scope, filter, attributes) diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 5470a7a..284ffd2 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -192,6 +192,8 @@ func (s *Server) setupRoutes() { r.Post("/{id}/test", connectionsHandler.Test) r.Post("/{id}/query-preview", connectionsHandler.QueryPreview) r.Get("/{id}/directory", connectionsHandler.DirectorySearch) + r.Get("/{id}/attributes", connectionsHandler.Attributes) + r.Get("/{id}/attribute-values", connectionsHandler.AttributeValues) r.Post("/{id}/enable", connectionsHandler.Enable) r.Post("/{id}/disable", connectionsHandler.Disable) }) diff --git a/backend/internal/services/connection_service.go b/backend/internal/services/connection_service.go index 930e00f..5db72e2 100644 --- a/backend/internal/services/connection_service.go +++ b/backend/internal/services/connection_service.go @@ -4,7 +4,10 @@ package services import ( "database/sql" "fmt" + "regexp" + "sort" "strings" + "sync" "time" "github.com/Grace-Solutions/OrchestrAD/internal/crypto" @@ -12,6 +15,7 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/logging" "github.com/Grace-Solutions/OrchestrAD/internal/models" "github.com/Grace-Solutions/OrchestrAD/internal/repository" + "github.com/Grace-Solutions/OrchestrAD/internal/types" goldap "github.com/go-ldap/ldap/v3" ) @@ -21,6 +25,15 @@ type ConnectionService struct { credRepo *repository.CredentialRepository encryptor *crypto.Encryptor logger *logging.Logger + + // schemaCache memoises the per-(connection, objectType) applicable attribute + // name set so the class-hierarchy walk is not repeated on every keystroke. + schemaCache sync.Map // key "connID|objectType" -> *attrSetCache +} + +type attrSetCache struct { + names []string + expires time.Time } // NewConnectionService creates a new ConnectionService @@ -331,6 +344,236 @@ func (s *ConnectionService) SearchDirectory(conn *models.ADConnection, objectTyp return out, nil } +// AttributeInfo is a schema attribute offered to the filter builder. +type AttributeInfo struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +// attrNameRe restricts attribute names to the LDAP descriptor charset, so a +// name can be placed into a filter without escaping / injection risk. +var attrNameRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9-]*$`) + +// classForObjectType maps an OrchestrAD object type to its AD classSchema +// lDAPDisplayName, the entry point for the attribute-applicability walk. +func classForObjectType(objectType string) string { + switch objectType { + case "Computer": + return "computer" + case "Group": + return "group" + default: + return "user" + } +} + +// SchemaAttributes returns the schema attributes that apply to objectType +// (User/Computer/Group), optionally narrowed to those whose name contains q. +// The applicable-attribute set is derived from the class hierarchy and cached. +func (s *ConnectionService) SchemaAttributes(conn *models.ADConnection, objectType, q string, limit int) ([]AttributeInfo, error) { + q = strings.TrimSpace(q) + if limit <= 0 || limit > 500 { + limit = 50 + } + + client, err := s.BuildClient(conn) + if err != nil { + return nil, err + } + defer client.Close() + + schemaNC, err := s.schemaNamingContext(client) + if err != nil { + return nil, err + } + + names, err := s.applicableAttributeNames(client, conn.ID, schemaNC, objectType) + if err != nil { + return nil, err + } + + // Filter by substring, cap. + ql := strings.ToLower(q) + matched := make([]string, 0, limit) + for _, n := range names { + if ql == "" || strings.Contains(strings.ToLower(n), ql) { + matched = append(matched, n) + if len(matched) >= limit { + break + } + } + } + if len(matched) == 0 { + return []AttributeInfo{}, nil + } + + // Fetch descriptions for the matched subset in a single OR-filter search. + descByName := s.attributeDescriptions(client, schemaNC, matched) + out := make([]AttributeInfo, 0, len(matched)) + for _, n := range matched { + out = append(out, AttributeInfo{Name: n, Description: descByName[strings.ToLower(n)]}) + } + return out, nil +} + +func (s *ConnectionService) schemaNamingContext(client *ldap.Client) (string, error) { + root, err := client.SearchOne("", goldap.ScopeBaseObject, "(objectClass=*)", []string{"schemaNamingContext"}) + if err != nil { + return "", fmt.Errorf("reading RootDSE: %w", err) + } + if root == nil { + return "", fmt.Errorf("RootDSE not available") + } + nc := root.GetAttributeValue("schemaNamingContext") + if nc == "" { + return "", fmt.Errorf("directory does not expose a schema naming context") + } + return nc, nil +} + +// applicableAttributeNames returns the sorted set of attribute lDAPDisplayNames +// that may be set on objectType, by walking the classSchema hierarchy +// (subClassOf up to top, plus auxiliary classes) and unioning each class's +// may/must-contain attributes. Cached per connection+objectType for 10 minutes. +func (s *ConnectionService) applicableAttributeNames(client *ldap.Client, connID, schemaNC, objectType string) ([]string, error) { + cacheKey := connID + "|" + objectType + if v, ok := s.schemaCache.Load(cacheKey); ok { + if c := v.(*attrSetCache); time.Now().Before(c.expires) { + return c.names, nil + } + } + + attrSet := map[string]bool{} + visited := map[string]bool{} + queue := []string{classForObjectType(objectType)} + for len(queue) > 0 { + cls := queue[0] + queue = queue[1:] + if cls == "" || visited[cls] { + continue + } + visited[cls] = true + + entry, err := client.SearchOne(schemaNC, goldap.ScopeSingleLevel, + fmt.Sprintf("(&(objectClass=classSchema)(lDAPDisplayName=%s))", ldap.EscapeFilterValue(cls)), + []string{"mayContain", "systemMayContain", "mustContain", "systemMustContain", + "subClassOf", "auxiliaryClass", "systemAuxiliaryClass"}) + if err != nil { + return nil, err + } + if entry == nil { + continue + } + for _, a := range []string{"mayContain", "systemMayContain", "mustContain", "systemMustContain"} { + for _, v := range entry.GetAttributeValues(a) { + attrSet[v] = true + } + } + for _, a := range []string{"subClassOf", "auxiliaryClass", "systemAuxiliaryClass"} { + for _, v := range entry.GetAttributeValues(a) { + if v != "" && v != cls { + queue = append(queue, v) + } + } + } + } + + names := make([]string, 0, len(attrSet)) + for n := range attrSet { + names = append(names, n) + } + sort.Slice(names, func(i, j int) bool { return strings.ToLower(names[i]) < strings.ToLower(names[j]) }) + + s.schemaCache.Store(cacheKey, &attrSetCache{names: names, expires: time.Now().Add(10 * time.Minute)}) + return names, nil +} + +// attributeDescriptions fetches adminDescription for a set of attribute names +// in one search, returning a lowercase-name-keyed map. +func (s *ConnectionService) attributeDescriptions(client *ldap.Client, schemaNC string, names []string) map[string]string { + if len(names) == 0 { + return map[string]string{} + } + var b strings.Builder + b.WriteString("(&(objectClass=attributeSchema)(|") + for _, n := range names { + b.WriteString("(lDAPDisplayName=") + b.WriteString(ldap.EscapeFilterValue(n)) + b.WriteString(")") + } + b.WriteString("))") + + out := map[string]string{} + entries, err := client.SearchWithLimit(schemaNC, goldap.ScopeSingleLevel, b.String(), + []string{"lDAPDisplayName", "adminDescription"}, len(names)) + if err != nil { + return out // descriptions are best-effort + } + for _, e := range entries { + name := e.GetAttributeValue("lDAPDisplayName") + if name != "" { + out[strings.ToLower(name)] = e.GetAttributeValue("adminDescription") + } + } + return out +} + +// DistinctAttributeValues samples objects of objectType and returns the +// distinct values present for a single attribute, optionally narrowed by a +// substring q and a base DN. Bounded by a scan cap so it stays cheap on large +// directories. +func (s *ConnectionService) DistinctAttributeValues(conn *models.ADConnection, objectType, attribute, q, baseDN string, limit int) ([]string, error) { + attribute = strings.TrimSpace(attribute) + if !attrNameRe.MatchString(attribute) { + return nil, fmt.Errorf("invalid attribute name") + } + if limit <= 0 || limit > 200 { + limit = 50 + } + if baseDN == "" { + baseDN = conn.RootDN + } + + filter := fmt.Sprintf("(&%s(%s=*))", ldap.ObjectFilter(types.ObjectType(objectType)), attribute) + if q = strings.TrimSpace(q); q != "" { + filter = fmt.Sprintf("(&%s(%s=*%s*))", ldap.ObjectFilter(types.ObjectType(objectType)), attribute, ldap.EscapeFilterValue(q)) + } + + client, err := s.BuildClient(conn) + if err != nil { + return nil, err + } + defer client.Close() + + // Scan up to ~2000 objects; that is plenty to surface the common values + // without walking an entire large directory. + entries, err := client.SearchWithLimit(baseDN, goldap.ScopeWholeSubtree, filter, []string{attribute}, 2000) + if err != nil { + return nil, err + } + + seen := make(map[string]bool) + out := make([]string, 0, limit) + for _, e := range entries { + for _, v := range e.GetAttributeValues(attribute) { + key := strings.ToLower(v) + if v == "" || seen[key] { + continue + } + seen[key] = true + out = append(out, v) + if len(out) >= limit { + break + } + } + if len(out) >= limit { + break + } + } + sort.Slice(out, func(i, j int) bool { return strings.ToLower(out[i]) < strings.ToLower(out[j]) }) + return out, nil +} + func mapScope(scope string) int { switch scope { case "base", "Base", "BaseObject": -- 2.52.0 From 4c20fe1e1584c45d45609dd7d70b734227814b15 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 15:58:02 -0400 Subject: [PATCH 25/34] feat(ui): live attribute + value pickers in the filter builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filter builder's attribute field now searches the selected connection's full directory schema (scoped to the rule's object type) instead of a fixed curated list, and the value field suggests the distinct values actually present in the directory for the chosen attribute β€” both search-as-you-type with the curated common attributes still shown first. New AttributePicker and ValuePicker components (async MUI autocompletes over ConnectionsApi.attributes / attributeValues), wired into each condition row; memberOf still uses the group picker and raw-LDAP keeps a plain field. Co-Authored-By: Claude Opus 4.8 --- .../src/app/(app)/rules/AttributePicker.tsx | 107 ++++++++++++++++++ .../src/app/(app)/rules/RuleEditorDialog.tsx | 31 +++-- frontend/src/app/(app)/rules/ValuePicker.tsx | 84 ++++++++++++++ frontend/src/lib/api/resources.ts | 12 ++ frontend/src/lib/api/types.ts | 6 + 5 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 frontend/src/app/(app)/rules/AttributePicker.tsx create mode 100644 frontend/src/app/(app)/rules/ValuePicker.tsx diff --git a/frontend/src/app/(app)/rules/AttributePicker.tsx b/frontend/src/app/(app)/rules/AttributePicker.tsx new file mode 100644 index 0000000..4d1f3d5 --- /dev/null +++ b/frontend/src/app/(app)/rules/AttributePicker.tsx @@ -0,0 +1,107 @@ +"use client"; + +import Autocomplete from "@mui/material/Autocomplete"; +import CircularProgress from "@mui/material/CircularProgress"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useEffect, useMemo, useRef, useState } from "react"; + +import { ConnectionsApi } from "@/lib/api/resources"; +import type { AttributeInfo, AttributeMeta } from "@/lib/api/types"; + +interface Props { + connectionId: string; + objectType: string; + common: AttributeMeta[]; // curated suggestions shown before the user types + value: string; + onChange: (attr: string) => void; +} + +// AttributePicker is a search-or-type control for an LDAP attribute: it shows +// the curated common attributes up front and live-searches the connection's +// full directory schema as the operator types, so any attribute is reachable. +export default function AttributePicker({ connectionId, objectType, common, value, onChange }: Props) { + const [input, setInput] = useState(""); + const [schema, setSchema] = useState([]); + const [loading, setLoading] = useState(false); + const timer = useRef | null>(null); + + useEffect(() => { + if (!connectionId) return; + const q = input.trim(); + if (q.length < 2) { + setSchema([]); + return; + } + if (timer.current) clearTimeout(timer.current); + timer.current = setTimeout(() => { + setLoading(true); + ConnectionsApi.attributes(connectionId, objectType, q, 50) + .then(setSchema) + .catch(() => setSchema([])) + .finally(() => setLoading(false)); + }, 300); + return () => { + if (timer.current) clearTimeout(timer.current); + }; + }, [connectionId, objectType, input]); + + // Merge curated + schema results, de-duplicated by name; curated first. + const options = useMemo(() => { + const map = new Map(); + common.forEach((a) => map.set(a.name.toLowerCase(), { name: a.name, description: a.label })); + schema.forEach((a) => { + if (!map.has(a.name.toLowerCase())) map.set(a.name.toLowerCase(), a); + }); + return Array.from(map.values()); + }, [common, schema]); + + return ( + + freeSolo + size="small" + sx={{ minWidth: 220, flex: 1 }} + options={options} + loading={loading} + filterOptions={(x) => x} + value={value} + onChange={(_, v) => onChange(v == null ? "" : typeof v === "string" ? v : v.name)} + onInputChange={(_, v, reason) => { + if (reason === "input") { + setInput(v); + onChange(v); + } + }} + getOptionLabel={(o) => (typeof o === "string" ? o : o.name)} + isOptionEqualToValue={(o, v) => (typeof o === "string" ? o === v : o.name === v)} + renderOption={(props, o) => { + const name = typeof o === "string" ? o : o.name; + const desc = typeof o === "string" ? undefined : o.description; + return ( +
  3. +
    + {name} + {desc && {desc}} +
    +
  4. + ); + }} + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + ); +} diff --git a/frontend/src/app/(app)/rules/RuleEditorDialog.tsx b/frontend/src/app/(app)/rules/RuleEditorDialog.tsx index 9e6c0b3..dfc3fe0 100644 --- a/frontend/src/app/(app)/rules/RuleEditorDialog.tsx +++ b/frontend/src/app/(app)/rules/RuleEditorDialog.tsx @@ -1,7 +1,6 @@ "use client"; import Alert from "@mui/material/Alert"; -import Autocomplete from "@mui/material/Autocomplete"; import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import Chip from "@mui/material/Chip"; @@ -25,7 +24,7 @@ import Typography from "@mui/material/Typography"; import AddOutlinedIcon from "@mui/icons-material/AddOutlined"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { ApiError } from "@/lib/api/client"; import { ConnectionsApi, RulesApi, SchedulesApi } from "@/lib/api/resources"; @@ -39,6 +38,8 @@ import type { } from "@/lib/api/types"; import DirectoryPicker from "./DirectoryPicker"; +import AttributePicker from "./AttributePicker"; +import ValuePicker from "./ValuePicker"; interface Props { open: boolean; @@ -190,10 +191,6 @@ export default function RuleEditorDialog({ open, ruleId, onClose, onSaved }: Pro }, [open, ruleId]); const operators = meta?.operators ?? []; - const attrOptions = useMemo( - () => (meta?.commonAttributes?.[objectType] ?? []).map((a) => a.name), - [meta, objectType], - ); const opNeedsValue = useCallback( (op: string) => operators.find((o) => o.value === op)?.needsValue ?? true, [operators], @@ -381,11 +378,12 @@ export default function RuleEditorDialog({ open, ruleId, onClose, onSaved }: Pro {g.conditions.map((c, ci) => ( - setCond(gi, ci, { attributeName: v })} - renderInput={(p) => } + onChange={(v) => setCond(gi, ci, { attributeName: v })} /> setCond(gi, ci, { operator: e.target.value })} sx={{ minWidth: 200 }}> {operators.map((o) => {o.label})} @@ -395,10 +393,19 @@ export default function RuleEditorDialog({ open, ruleId, onClose, onSaved }: Pro setCond(gi, ci, { value: dn })} /> - ) : ( - setCond(gi, ci, { value: e.target.value })} sx={{ flex: 1, minWidth: 180 }} placeholder={operators.find((o) => o.value === c.operator)?.hint} /> + ) : ( + setCond(gi, ci, { value: v })} + /> ) )} setCond(gi, ci, { negate: v })} />} label="NOT" /> diff --git a/frontend/src/app/(app)/rules/ValuePicker.tsx b/frontend/src/app/(app)/rules/ValuePicker.tsx new file mode 100644 index 0000000..ffccaac --- /dev/null +++ b/frontend/src/app/(app)/rules/ValuePicker.tsx @@ -0,0 +1,84 @@ +"use client"; + +import Autocomplete from "@mui/material/Autocomplete"; +import CircularProgress from "@mui/material/CircularProgress"; +import TextField from "@mui/material/TextField"; +import { useEffect, useRef, useState } from "react"; + +import { ConnectionsApi } from "@/lib/api/resources"; + +interface Props { + connectionId: string; + objectType: string; + attribute: string; + baseDn?: string; + value: string; + onChange: (value: string) => void; + label?: string; + placeholder?: string; +} + +// ValuePicker suggests the distinct values actually present in the directory +// for the chosen attribute (sampled), while still allowing any typed value. +export default function ValuePicker({ connectionId, objectType, attribute, baseDn, value, onChange, label, placeholder }: Props) { + const [input, setInput] = useState(""); + const [options, setOptions] = useState([]); + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const timer = useRef | null>(null); + + const canQuery = Boolean(connectionId && attribute.trim()); + + useEffect(() => { + if (!canQuery || !open) return; + if (timer.current) clearTimeout(timer.current); + timer.current = setTimeout(() => { + setLoading(true); + ConnectionsApi.attributeValues(connectionId, { attribute: attribute.trim(), objectType, q: input, baseDn, limit: 50 }) + .then(setOptions) + .catch(() => setOptions([])) + .finally(() => setLoading(false)); + }, 350); + return () => { + if (timer.current) clearTimeout(timer.current); + }; + }, [canQuery, open, input, attribute, objectType, baseDn, connectionId]); + + return ( + + freeSolo + size="small" + sx={{ flex: 1, minWidth: 180 }} + options={options} + loading={loading} + open={open} + onOpen={() => setOpen(true)} + onClose={() => setOpen(false)} + filterOptions={(x) => x} + value={value} + onChange={(_, v) => onChange(v ?? "")} + onInputChange={(_, v, reason) => { + if (reason === "input") { + setInput(v); + onChange(v); + } + }} + renderInput={(params) => ( + + {loading ? : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> + ); +} diff --git a/frontend/src/lib/api/resources.ts b/frontend/src/lib/api/resources.ts index 4828aa0..f31d50a 100644 --- a/frontend/src/lib/api/resources.ts +++ b/frontend/src/lib/api/resources.ts @@ -25,6 +25,7 @@ import { RuleRunDetail, RuleSummary, DirectoryObject, + AttributeInfo, Schedule, User, UserInfo, @@ -95,6 +96,17 @@ export const ConnectionsApi = { api .get(`/api/v1/ad-connections/${id}/directory`, { query: { type, q, limit } }) .then((r) => r.data ?? []), + attributes: (id: string, objectType: string, q: string, limit = 50) => + api + .get(`/api/v1/ad-connections/${id}/attributes`, { query: { objectType, q, limit } }) + .then((r) => r.data ?? []), + attributeValues: ( + id: string, + params: { attribute: string; objectType?: string; q?: string; baseDn?: string; limit?: number }, + ) => + api + .get(`/api/v1/ad-connections/${id}/attribute-values`, { query: params as Record }) + .then((r) => r.data ?? []), enable: (id: string) => api.post<{ enabled: boolean }>(`/api/v1/ad-connections/${id}/enable`).then((r) => r.data), disable: (id: string) => diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index fd23362..28b822c 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -408,6 +408,12 @@ export interface DirectoryObject { objectType: string; } +// A schema attribute returned by the attribute autocomplete. +export interface AttributeInfo { + name: string; + description?: string; +} + // The full create/update payload the editor sends (superset of Rule scalars // plus the authored filter and actions). export interface RuleConditionInput { -- 2.52.0 From 1f77473d8fc2994889ab5c5e4b8669fdbd48c9ba Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 17:14:49 -0400 Subject: [PATCH 26/34] feat(server): trust local proxies by default, seed built-in schedules Proxy / base-URL: - ORCHESTRAD_TRUSTED_PROXIES now defaults to "local", trusting reverse proxies in loopback + RFC1918 + link-local/ULA ranges out of the box, so X-Forwarded-* (client IP, scheme, host) is honored behind an edge proxy without extra config. New keywords: local/private, all/any, none. - OIDC redirect URI derivation now uses the trust-gated request base URL instead of reading X-Forwarded-Proto directly, and audit client IP now trusts the middleware-rewritten RemoteAddr rather than the raw (spoofable) X-Forwarded-For header. Both honor forwarded values only from trusted peers. Schedules: - Seed eight built-in schedules on startup (every 5/15/30 min, hourly, every 6/12h, daily, weekly), idempotent by name, so operators have ready-made cadences in the Schedules page and the rule editor's schedule dropdown without hand-building one. Test covers the trusted-proxy keyword expansion. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/audit_helpers.go | 31 +++++++-- backend/internal/api/oidc_handlers.go | 13 ++-- backend/internal/cli/cli.go | 3 + backend/internal/config/config.go | 6 +- backend/internal/server/proxy.go | 29 +++++++- backend/internal/server/proxy_test.go | 51 ++++++++++++++ backend/internal/services/schedule_seed.go | 80 ++++++++++++++++++++++ 7 files changed, 197 insertions(+), 16 deletions(-) create mode 100644 backend/internal/server/proxy_test.go create mode 100644 backend/internal/services/schedule_seed.go diff --git a/backend/internal/api/audit_helpers.go b/backend/internal/api/audit_helpers.go index 346f04b..1e8b5d3 100644 --- a/backend/internal/api/audit_helpers.go +++ b/backend/internal/api/audit_helpers.go @@ -2,6 +2,7 @@ package api import ( + "net" "net/http" "github.com/Grace-Solutions/OrchestrAD/internal/audit" @@ -50,12 +51,32 @@ func emitAudit(svc *audit.Service, r *http.Request, eventType audit.EventType, r _ = svc.Log(event) } +// clientIP returns the caller's address. The proxy middleware already rewrites +// r.RemoteAddr from X-Forwarded-For / X-Real-IP when the immediate peer is a +// trusted proxy, so we trust r.RemoteAddr rather than re-reading the (spoofable) +// forwarded headers here. func clientIP(r *http.Request) string { - if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" { - return fwd - } - if real := r.Header.Get("X-Real-IP"); real != "" { - return real + if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { + return host } return r.RemoteAddr } + +// requestBaseURL returns the externally-visible scheme://host for the request. +// It reflects X-Forwarded-Proto/Host only when the proxy middleware applied +// them (trusted peer); otherwise it falls back to the actual scheme and Host. +func requestBaseURL(r *http.Request) string { + scheme := r.URL.Scheme + if scheme == "" { + if r.TLS != nil { + scheme = "https" + } else { + scheme = "http" + } + } + host := r.Host + if host == "" { + host = r.URL.Host + } + return scheme + "://" + host +} diff --git a/backend/internal/api/oidc_handlers.go b/backend/internal/api/oidc_handlers.go index 92b89d7..f2abae9 100644 --- a/backend/internal/api/oidc_handlers.go +++ b/backend/internal/api/oidc_handlers.go @@ -10,7 +10,6 @@ import ( "context" "crypto/rand" "encoding/base64" - "fmt" "net/http" "net/url" "strings" @@ -73,14 +72,10 @@ func (h *OIDCHandler) redirectURI(r *http.Request, cfg oidcConfig) string { if cfg.RedirectURL != "" { return cfg.RedirectURL } - scheme := "http" - if r.TLS != nil { - scheme = "https" - } - if p := r.Header.Get("X-Forwarded-Proto"); p != "" { - scheme = p - } - return fmt.Sprintf("%s://%s/api/v1/auth/oidc/callback", scheme, r.Host) + // Derive from the request. requestBaseURL honors X-Forwarded-Proto/Host only + // when the proxy middleware already applied them (trusted peer), so an + // untrusted client cannot forge the callback host. + return requestBaseURL(r) + "/api/v1/auth/oidc/callback" } func (h *OIDCHandler) build(ctx context.Context, cfg oidcConfig, redirectURL string) (*oidc.Provider, oauth2.Config, error) { diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index 1230e9d..f17fb9c 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -91,6 +91,9 @@ func runServer(ctx context.Context) error { return fmt.Errorf("bootstrapping admin user: %w", err) } + // Seed the built-in schedules so operators have ready-made cadences. + services.EnsureDefaultSchedules(database.Conn(), logger) + // Derive a 32-byte AES key from the configured secret key keyHash := sha256.Sum256(cfg.SecretKey) encryptor, err := crypto.NewEncryptor(keyHash[:]) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 8db5266..f79153a 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -90,7 +90,11 @@ func Load() (*Config, error) { // built-in default. (On non-Windows registrySetting returns "".) Host: getEnv("ORCHESTRAD_HOST", firstNonEmpty(registrySetting("ListenAddress"), "0.0.0.0")), Port: getEnvInt("ORCHESTRAD_PORT", atoiOr(registrySetting("ListenPort"), 18090)), - TrustedProxies: splitCSV(getEnv("ORCHESTRAD_TRUSTED_PROXIES", "")), + // Trust reverse proxies in local/private ranges by default so + // X-Forwarded-* headers (client IP, scheme, host) are honored out + // of the box behind an edge proxy. Override with an explicit CIDR + // list, the keywords "local"/"private"/"all"/"none", or "" to disable. + TrustedProxies: splitCSV(getEnv("ORCHESTRAD_TRUSTED_PROXIES", "local")), }, Database: DatabaseConfig{ Path: filepath.Join(dataPath, "orchestrad.db"), diff --git a/backend/internal/server/proxy.go b/backend/internal/server/proxy.go index 289a5d9..3d48b56 100644 --- a/backend/internal/server/proxy.go +++ b/backend/internal/server/proxy.go @@ -11,9 +11,36 @@ import ( // entirely, which is the safe default for direct exposure. type trustedProxies []*net.IPNet +// localProxyCIDRs are the loopback and private/link-local ranges trusted when +// the configuration uses the "local"/"private" keyword β€” the common case for +// an edge proxy running on the same host or LAN. +var localProxyCIDRs = []string{ + "127.0.0.0/8", "::1/128", + "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", + "169.254.0.0/16", "fc00::/7", "fe80::/10", +} + func parseTrustedProxies(cidrs []string) (trustedProxies, error) { - var nets trustedProxies + // Expand keywords first: "local"/"private" -> local ranges; "all"/"*" -> + // everything; "none" -> nothing. + var expanded []string for _, raw := range cidrs { + switch strings.ToLower(strings.TrimSpace(raw)) { + case "": + continue + case "none", "off", "false": + return nil, nil + case "local", "private": + expanded = append(expanded, localProxyCIDRs...) + case "all", "any", "*": + expanded = append(expanded, "0.0.0.0/0", "::/0") + default: + expanded = append(expanded, raw) + } + } + + var nets trustedProxies + for _, raw := range expanded { raw = strings.TrimSpace(raw) if raw == "" { continue diff --git a/backend/internal/server/proxy_test.go b/backend/internal/server/proxy_test.go new file mode 100644 index 0000000..6bc561f --- /dev/null +++ b/backend/internal/server/proxy_test.go @@ -0,0 +1,51 @@ +package server + +import ( + "net" + "testing" +) + +func TestParseTrustedProxiesKeywords(t *testing.T) { + // "local" expands to loopback + private ranges. + tp, err := parseTrustedProxies([]string{"local"}) + if err != nil { + t.Fatalf("local: %v", err) + } + for _, ip := range []string{"127.0.0.1", "10.1.2.3", "192.168.5.5", "172.16.0.1", "169.254.1.1"} { + if !tp.contains(net.ParseIP(ip)) { + t.Errorf("local should trust %s", ip) + } + } + if tp.contains(net.ParseIP("8.8.8.8")) { + t.Errorf("local should NOT trust a public IP") + } + + // "all" trusts everything. + all, err := parseTrustedProxies([]string{"all"}) + if err != nil { + t.Fatalf("all: %v", err) + } + if !all.contains(net.ParseIP("8.8.8.8")) { + t.Errorf("all should trust any IP") + } + + // "none" and "" trust nothing. + for _, kw := range []string{"none", ""} { + none, err := parseTrustedProxies([]string{kw}) + if err != nil { + t.Fatalf("%q: %v", kw, err) + } + if none.contains(net.ParseIP("127.0.0.1")) { + t.Errorf("%q should trust nothing", kw) + } + } + + // An explicit CIDR still works and is additive with a keyword. + mix, err := parseTrustedProxies([]string{"local", "203.0.113.0/24"}) + if err != nil { + t.Fatalf("mix: %v", err) + } + if !mix.contains(net.ParseIP("203.0.113.7")) || !mix.contains(net.ParseIP("10.0.0.1")) { + t.Errorf("mix should trust both the explicit CIDR and local ranges") + } +} diff --git a/backend/internal/services/schedule_seed.go b/backend/internal/services/schedule_seed.go new file mode 100644 index 0000000..407f743 --- /dev/null +++ b/backend/internal/services/schedule_seed.go @@ -0,0 +1,80 @@ +// Package services - built-in schedule seeding +package services + +import ( + "database/sql" + + "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/models" + "github.com/Grace-Solutions/OrchestrAD/internal/repository" +) + +// defaultSchedule describes one built-in schedule offered out of the box. +type defaultSchedule struct { + name string + kind string // "Easy" or "Cron" + interval int // for Easy + unit string // for Easy: Minutes/Hours/Days + cron string // for Cron (6-field, UTC) + desc string +} + +var builtinSchedules = []defaultSchedule{ + {name: "Every 5 minutes", kind: "Easy", interval: 5, unit: "Minutes", desc: "Built-in"}, + {name: "Every 15 minutes", kind: "Easy", interval: 15, unit: "Minutes", desc: "Built-in"}, + {name: "Every 30 minutes", kind: "Easy", interval: 30, unit: "Minutes", desc: "Built-in"}, + {name: "Hourly", kind: "Easy", interval: 1, unit: "Hours", desc: "Built-in"}, + {name: "Every 6 hours", kind: "Easy", interval: 6, unit: "Hours", desc: "Built-in"}, + {name: "Every 12 hours", kind: "Easy", interval: 12, unit: "Hours", desc: "Built-in"}, + {name: "Daily (00:00 UTC)", kind: "Cron", cron: "0 0 0 * * *", desc: "Built-in β€” every day at midnight UTC"}, + {name: "Weekly (Sun 00:00 UTC)", kind: "Cron", cron: "0 0 0 * * 0", desc: "Built-in β€” every Sunday at midnight UTC"}, +} + +// EnsureDefaultSchedules idempotently seeds the built-in schedules so operators +// have ready-made cadences (in the Schedules page and the rule editor) without +// hand-building one. Existing schedules with the same name are left untouched; +// deleting a built-in schedule will not resurrect it within the same run but it +// reappears on next startup unless renamed. +func EnsureDefaultSchedules(db *sql.DB, logger *logging.Logger) { + repo := repository.NewScheduleRepository(db) + existing, _, err := repo.List(0, 500) + if err != nil { + logger.Warn("Schedules", "Could not list schedules for seeding: %v", err) + return + } + have := make(map[string]bool, len(existing)) + for _, s := range existing { + have[s.Name] = true + } + + seeded := 0 + for _, d := range builtinSchedules { + if have[d.name] { + continue + } + desc := d.desc + sched := &models.Schedule{ + Name: d.name, + Description: &desc, + IsEnabled: true, + ScheduleKind: d.kind, + TimezoneMode: "UTC", + } + if d.kind == "Easy" { + v, u := d.interval, d.unit + sched.EasyIntervalValue = &v + sched.EasyIntervalUnit = &u + } else { + c := d.cron + sched.CronExpression = &c + } + if err := repo.Create(sched); err != nil { + logger.Warn("Schedules", "Failed to seed built-in schedule %q: %v", d.name, err) + continue + } + seeded++ + } + if seeded > 0 { + logger.Info("Schedules", "Seeded %d built-in schedule(s)", seeded) + } +} -- 2.52.0 From 50147f357fb514ef5720c714417cf4cc559e8501 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 17:14:56 -0400 Subject: [PATCH 27/34] feat(ui): show schedule + next run on the rules list Add a Schedule column to the rules table that resolves each rule's scheduleId to the schedule name and its next run time (or "Manual"), pairing with the newly seeded built-in schedules. Co-Authored-By: Claude Opus 4.8 --- frontend/src/app/(app)/rules/page.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/(app)/rules/page.tsx b/frontend/src/app/(app)/rules/page.tsx index d6c0dfc..09e8589 100644 --- a/frontend/src/app/(app)/rules/page.tsx +++ b/frontend/src/app/(app)/rules/page.tsx @@ -25,8 +25,8 @@ import { useCallback, useEffect, useState } from "react"; import PageContainer from "@/app/components/container/PageContainer"; import DashboardCard from "@/app/components/shared/DashboardCard"; import { ApiError } from "@/lib/api/client"; -import { RulesApi } from "@/lib/api/resources"; -import type { RuleSummary } from "@/lib/api/types"; +import { RulesApi, SchedulesApi } from "@/lib/api/resources"; +import type { RuleSummary, Schedule } from "@/lib/api/types"; import { formatDateTime, testResultColor } from "@/lib/format"; import RuleEditorDialog from "./RuleEditorDialog"; @@ -41,13 +41,18 @@ export default function RulesPage() { const [editingId, setEditingId] = useState(null); const [previewing, setPreviewing] = useState(null); const [running, setRunning] = useState(null); + const [schedulesById, setSchedulesById] = useState>(new Map()); const load = useCallback(async () => { setLoading(true); setError(null); try { - const res = await RulesApi.list({ pageSize: 100 }); + const [res, sched] = await Promise.all([ + RulesApi.list({ pageSize: 100 }), + SchedulesApi.list({ pageSize: 200 }).catch(() => ({ items: [] as Schedule[] })), + ]); setItems(res.items); + setSchedulesById(new Map(sched.items.map((s) => [s.id, s]))); } catch (err) { setError(err instanceof ApiError ? err.message : "Failed to load rules"); } finally { @@ -128,6 +133,7 @@ export default function RulesPage() { Conditions Actions Enabled + Schedule Last Run Last Result Actions @@ -150,6 +156,20 @@ export default function RulesPage() { handleToggle(r)} /> + + {r.scheduleId && schedulesById.get(r.scheduleId) ? ( + + {schedulesById.get(r.scheduleId)!.name} + {schedulesById.get(r.scheduleId)!.nextRunUtc && ( + + next {formatDateTime(schedulesById.get(r.scheduleId)!.nextRunUtc)} + + )} + + ) : ( + Manual + )} + {formatDateTime(r.lastRunUtc)} {r.lastRunResult ? ( -- 2.52.0 From 429e8bd39805dec36d5c93238cca04249d039824 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 17:48:27 -0400 Subject: [PATCH 28/34] fix(rules): record last run outcome on the rule row The runner updated the rule_runs history but never wrote the rules table's last_run_utc / last_run_result columns, so the rules list showed blank Last Run / Last Result. Add RuleRepository.UpdateLastRun and call it from the runner's finalize and fail paths with the run's completion time and status. Co-Authored-By: Claude Opus 4.8 --- backend/internal/repository/rules.go | 9 +++++++++ backend/internal/rules/runner/runner.go | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/backend/internal/repository/rules.go b/backend/internal/repository/rules.go index 655a47c..fbd13d4 100644 --- a/backend/internal/repository/rules.go +++ b/backend/internal/repository/rules.go @@ -270,6 +270,15 @@ func (r *RuleRepository) Update(rule *models.Rule) error { return err } +// UpdateLastRun records the outcome of the most recent execution on the rule +// row, so the rules list can show when it last ran and how it went. +func (r *RuleRepository) UpdateLastRun(id string, runUTC time.Time, result string) error { + _, err := r.db.Exec(` + UPDATE rules SET last_run_utc = ?, last_run_result = ? WHERE id = ? + `, formatTime(runUTC), result, id) + return err +} + // UpdateEnabled toggles the enabled flag on a rule. func (r *RuleRepository) UpdateEnabled(id string, enabled bool) error { now := time.Now().UTC() diff --git a/backend/internal/rules/runner/runner.go b/backend/internal/rules/runner/runner.go index 03e5fc7..6bf87c8 100644 --- a/backend/internal/rules/runner/runner.go +++ b/backend/internal/rules/runner/runner.go @@ -196,6 +196,9 @@ func (r *Runner) finalizeRun(run *models.RuleRun, result *engine.ExecutionResult if err := r.runRepo.Update(run); err != nil { r.logger.Warn("Runner", "Failed to update rule run %s: %v", run.ID, err) } + if err := r.ruleRepo.UpdateLastRun(run.RuleID, completed, run.Status); err != nil { + r.logger.Warn("Runner", "Failed to update last-run on rule %s: %v", run.RuleID, err) + } } func (r *Runner) failRun(run *models.RuleRun, msg string) { @@ -208,4 +211,7 @@ func (r *Runner) failRun(run *models.RuleRun, msg string) { if err := r.runRepo.Update(run); err != nil { r.logger.Warn("Runner", "Failed to update failed rule run %s: %v", run.ID, err) } + if err := r.ruleRepo.UpdateLastRun(run.RuleID, now, string(types.RunStatusFailed)); err != nil { + r.logger.Warn("Runner", "Failed to update last-run on rule %s: %v", run.RuleID, err) + } } -- 2.52.0 From 154cadc7925ba26f12fd65990a5040c622725016 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 17:48:28 -0400 Subject: [PATCH 29/34] feat(ui): per-rule history dialog showing objects added/removed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a History icon to each rule that opens a dialog listing the actual objects this rule acted on β€” added, removed, moved β€” scoped to the rule via the activity feed's ruleId filter, with category/result filters, paging, and the per-action detail drill-in. Colour a "Completed" last-run result as success (not amber) now that the rules list surfaces it. Co-Authored-By: Claude Opus 4.8 --- .../src/app/(app)/rules/RuleHistoryDialog.tsx | 179 ++++++++++++++++++ frontend/src/app/(app)/rules/page.tsx | 14 ++ frontend/src/lib/format.ts | 3 +- 3 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/(app)/rules/RuleHistoryDialog.tsx diff --git a/frontend/src/app/(app)/rules/RuleHistoryDialog.tsx b/frontend/src/app/(app)/rules/RuleHistoryDialog.tsx new file mode 100644 index 0000000..ba53758 --- /dev/null +++ b/frontend/src/app/(app)/rules/RuleHistoryDialog.tsx @@ -0,0 +1,179 @@ +"use client"; + +import Alert from "@mui/material/Alert"; +import Box from "@mui/material/Box"; +import Chip from "@mui/material/Chip"; +import CircularProgress from "@mui/material/CircularProgress"; +import Dialog from "@mui/material/Dialog"; +import DialogContent from "@mui/material/DialogContent"; +import DialogTitle from "@mui/material/DialogTitle"; +import IconButton from "@mui/material/IconButton"; +import MenuItem from "@mui/material/MenuItem"; +import Stack from "@mui/material/Stack"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import TextField from "@mui/material/TextField"; +import Tooltip from "@mui/material/Tooltip"; +import Typography from "@mui/material/Typography"; +import RefreshOutlinedIcon from "@mui/icons-material/RefreshOutlined"; +import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; +import { useCallback, useEffect, useState } from "react"; + +import { ApiError } from "@/lib/api/client"; +import { ActivityApi } from "@/lib/api/resources"; +import type { ActivityRecord } from "@/lib/api/types"; +import { formatDateTime, runStatusColor } from "@/lib/format"; + +import ActivityDetailDialog from "../activity/ActivityDetailDialog"; +import { CATEGORIES, categoryColor, categoryLabel } from "../activity/categories"; + +interface Props { + ruleId: string | null; + ruleName?: string; + onClose: () => void; +} + +const STATUS_OPTIONS = ["", "Succeeded", "Failed"]; + +// RuleHistoryDialog shows the per-object action history for a single rule β€” +// the actual objects added, removed, moved, etc. β€” with filtering and paging. +// It reuses the activity feed scoped by ruleId. +export default function RuleHistoryDialog({ ruleId, ruleName, onClose }: Props) { + const [items, setItems] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(25); + const [category, setCategory] = useState(""); + const [status, setStatus] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [detail, setDetail] = useState(null); + + const load = useCallback(async () => { + if (!ruleId) return; + setLoading(true); + setError(null); + try { + const res = await ActivityApi.list({ + ruleId, + page: page + 1, + pageSize, + category: category || undefined, + status: status || undefined, + }); + setItems(res.items); + setTotal(Number(res.meta?.totalCount ?? res.items.length)); + } catch (err) { + setError(err instanceof ApiError ? err.message : "Failed to load history"); + } finally { + setLoading(false); + } + }, [ruleId, page, pageSize, category, status]); + + useEffect(() => { + if (ruleId) void load(); + }, [ruleId, load]); + + // Reset paging/filters whenever a different rule is opened. + useEffect(() => { + setPage(0); + setCategory(""); + setStatus(""); + }, [ruleId]); + + return ( + + + + History{ruleName ? ` β€” ${ruleName}` : ""} + + + + void load()} disabled={loading}> + + + + + + + + {error && setError(null)}>{error}} + + + { setCategory(e.target.value); setPage(0); }} sx={{ minWidth: 160 }}> + All categories + {CATEGORIES.map((c) => {categoryLabel(c)})} + + { setStatus(e.target.value); setPage(0); }} sx={{ minWidth: 150 }}> + {STATUS_OPTIONS.map((s) => {s || "All results"})} + + + + {loading && items.length === 0 ? ( + + ) : items.length === 0 ? ( + No actions recorded for this rule yet. + ) : ( + + + + + When + Category + Action + Object + Result + By + Details + + + + {items.map((a) => ( + + {formatDateTime(a.createdUtc)} + + + + {a.actionType} + + {a.objectDn} + + + + + {a.triggeredBy ?? "β€”"} + + + setDetail(a)}> + + + + + + ))} + +
    + setPage(p)} + rowsPerPage={pageSize} + onRowsPerPageChange={(e) => { setPageSize(parseInt(e.target.value, 10)); setPage(0); }} + rowsPerPageOptions={[10, 25, 50, 100]} + /> +
    + )} +
    + + setDetail(null)} /> +
    + ); +} diff --git a/frontend/src/app/(app)/rules/page.tsx b/frontend/src/app/(app)/rules/page.tsx index 09e8589..5ac9833 100644 --- a/frontend/src/app/(app)/rules/page.tsx +++ b/frontend/src/app/(app)/rules/page.tsx @@ -18,6 +18,7 @@ import Tooltip from "@mui/material/Tooltip"; import Typography from "@mui/material/Typography"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; +import HistoryOutlinedIcon from "@mui/icons-material/HistoryOutlined"; import PlayArrowOutlinedIcon from "@mui/icons-material/PlayArrowOutlined"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { useCallback, useEffect, useState } from "react"; @@ -31,6 +32,7 @@ import { formatDateTime, testResultColor } from "@/lib/format"; import RuleEditorDialog from "./RuleEditorDialog"; import RulePreviewDialog from "./RulePreviewDialog"; +import RuleHistoryDialog from "./RuleHistoryDialog"; export default function RulesPage() { const [items, setItems] = useState([]); @@ -40,6 +42,7 @@ export default function RulesPage() { const [formOpen, setFormOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [previewing, setPreviewing] = useState(null); + const [historyRule, setHistoryRule] = useState(null); const [running, setRunning] = useState(null); const [schedulesById, setSchedulesById] = useState>(new Map()); @@ -182,6 +185,11 @@ export default function RulesPage() {
    + + setHistoryRule(r)}> + + + handleRun(r)}> @@ -217,6 +225,12 @@ export default function RulesPage() { /> setPreviewing(null)} /> + + setHistoryRule(null)} + /> ); } diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index f4f74bd..71f2622 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -42,7 +42,8 @@ export function runStatusColor(status: string): ChipColor { export function testResultColor(result?: string | null): ChipColor { if (!result) return "default"; const v = result.toLowerCase(); - if (v.includes("success") || v === "ok" || v === "passed") return "success"; + if (v.includes("success") || v.includes("complete") || v === "ok" || v === "passed") return "success"; if (v.includes("fail") || v.includes("error")) return "error"; + if (v.includes("cancel")) return "warning"; return "warning"; } -- 2.52.0 From 1e38b3b99ac0e50cff1063ab599c24c1cb1b5487 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 18:25:14 -0400 Subject: [PATCH 30/34] feat(maintenance): history retention + VACUUM; robust config import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Database maintenance: - New MaintenanceService prunes rule_runs (+ their action detail) and audit_events older than their retention windows, then VACUUMs to reclaim space. Runs once at startup and then on an interval, bound to the run context. Configurable via ORCHESTRAD_RUN_RETENTION_DAYS (90), ORCHESTRAD_AUDIT_RETENTION_DAYS (180), ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS (24), ORCHESTRAD_MAINTENANCE_VACUUM. This stops the database growing forever. (Log rotation already existed via lumberjack: ORCHESTRAD_LOG_MAX_SIZE_MB/_MAX_BACKUPS/_MAX_AGE_DAYS.) Config import: - Import now accepts either the wrapped {payload,dryRun} shape or a bare exported config object, so a file downloaded from Export re-imports directly (dryRun via ?dryRun=true) β€” useful for automation. Test covers retention pruning with FK-cascaded action rows. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/config_handlers.go | 25 ++++- backend/internal/cli/cli.go | 4 + backend/internal/config/config.go | 33 ++++++- .../internal/services/maintenance_service.go | 91 +++++++++++++++++++ .../services/maintenance_service_test.go | 85 +++++++++++++++++ 5 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 backend/internal/services/maintenance_service.go create mode 100644 backend/internal/services/maintenance_service_test.go diff --git a/backend/internal/api/config_handlers.go b/backend/internal/api/config_handlers.go index a1925ce..843f4ec 100644 --- a/backend/internal/api/config_handlers.go +++ b/backend/internal/api/config_handlers.go @@ -2,6 +2,8 @@ package api import ( + "encoding/json" + "io" "net/http" "github.com/Grace-Solutions/OrchestrAD/internal/audit" @@ -46,14 +48,31 @@ func (h *ConfigHandler) Export(w http.ResponseWriter, r *http.Request) { WriteJSON(w, http.StatusOK, payload) } -// Import handles POST /api/v1/config/import +// Import handles POST /api/v1/config/import. It accepts either the wrapped +// shape {"payload": , "dryRun": bool} or a bare exported config object +// (as produced by GET /config/export), so a downloaded export file can be +// re-imported directly; with the bare shape, dryRun comes from ?dryRun=true. func (h *ConfigHandler) Import(w http.ResponseWriter, r *http.Request) { - var req ImportRequest - if err := DecodeJSON(r, &req); err != nil { + raw, err := io.ReadAll(io.LimitReader(r.Body, 32<<20)) + if err != nil { WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") return } + var req ImportRequest + if err := json.Unmarshal(raw, &req); err != nil { + WriteError(w, http.StatusBadRequest, ErrCodeBadRequest, "Invalid request body") + return + } + // Fall back to a bare exported config when no wrapped payload was supplied. + if req.Payload.FormatVersion == "" { + var bare services.ConfigExport + if err := json.Unmarshal(raw, &bare); err == nil && bare.FormatVersion != "" { + req.Payload = bare + req.DryRun = r.URL.Query().Get("dryRun") == "true" + } + } + report, err := h.service.Import(&req.Payload, services.ImportOptions{DryRun: req.DryRun}) if err != nil { h.logger.Error("ConfigHandler", "Import failed: %v", err) diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index f17fb9c..c9f7b83 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -168,6 +168,10 @@ func runServer(ctx context.Context) error { } defer sched.Stop() + // Start background database maintenance (history retention + VACUUM) so the + // database does not grow forever. Bound to the run context. + services.NewMaintenanceService(database.Conn(), cfg.Maintenance, logger).Start(ctx) + // Create and run the HTTP server. srv.Run blocks until ctx is cancelled, // which happens on an interactive interrupt or a service stop request. srv := server.New(cfg, database, deps, logger) diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index f79153a..3becb0f 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -15,9 +15,19 @@ type Config struct { SecretKey []byte Server ServerConfig Database DatabaseConfig - Logging LoggingConfig - OIDC OIDCConfig - CORS CORSConfig + Logging LoggingConfig + OIDC OIDCConfig + CORS CORSConfig + Maintenance MaintenanceConfig +} + +// MaintenanceConfig controls background history pruning and database compaction +// so the database does not grow without bound. +type MaintenanceConfig struct { + RunRetentionDays int // rule_runs (+ their actions) older than this are deleted + AuditRetentionDays int // audit_events older than this are deleted + IntervalHours int // how often maintenance runs + Vacuum bool // run VACUUM after pruning to reclaim space } // ServerConfig holds HTTP server settings @@ -117,6 +127,12 @@ func Load() (*Config, error) { AllowedOrigins: corsOrigins(), AllowCredentials: true, }, + Maintenance: MaintenanceConfig{ + RunRetentionDays: getEnvInt("ORCHESTRAD_RUN_RETENTION_DAYS", 90), + AuditRetentionDays: getEnvInt("ORCHESTRAD_AUDIT_RETENTION_DAYS", 180), + IntervalHours: getEnvInt("ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS", 24), + Vacuum: getEnvBool("ORCHESTRAD_MAINTENANCE_VACUUM", true), + }, } return cfg, nil @@ -178,6 +194,17 @@ func getEnvInt(key string, defaultVal int) int { return defaultVal } +func getEnvBool(key string, defaultVal bool) bool { + switch strings.ToLower(strings.TrimSpace(os.Getenv(key))) { + case "": + return defaultVal + case "1", "true", "yes", "on": + return true + default: + return false + } +} + // firstNonEmpty returns v if it is non-empty, otherwise def. func firstNonEmpty(v, def string) string { if v != "" { diff --git a/backend/internal/services/maintenance_service.go b/backend/internal/services/maintenance_service.go new file mode 100644 index 0000000..5119240 --- /dev/null +++ b/backend/internal/services/maintenance_service.go @@ -0,0 +1,91 @@ +// Package services - database maintenance (history retention + compaction) +package services + +import ( + "context" + "database/sql" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" +) + +// MaintenanceService prunes aged history and compacts the database so it does +// not grow without bound. It runs once at startup and then on a fixed interval. +type MaintenanceService struct { + db *sql.DB + cfg config.MaintenanceConfig + logger *logging.Logger +} + +// NewMaintenanceService creates a new MaintenanceService. +func NewMaintenanceService(db *sql.DB, cfg config.MaintenanceConfig, logger *logging.Logger) *MaintenanceService { + return &MaintenanceService{db: db, cfg: cfg, logger: logger} +} + +// Start runs maintenance once, then on the configured interval until ctx is +// cancelled. It returns immediately; the loop runs in its own goroutine. +func (s *MaintenanceService) Start(ctx context.Context) { + interval := time.Duration(s.cfg.IntervalHours) * time.Hour + if interval <= 0 { + interval = 24 * time.Hour + } + go func() { + s.RunOnce() + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + s.RunOnce() + } + } + }() +} + +// RunOnce performs a single maintenance pass: delete rule runs (and their +// action detail) and audit events older than their retention windows, then +// VACUUM to reclaim space. Retention of 0 or less disables that prune. +func (s *MaintenanceService) RunOnce() { + now := time.Now().UTC() + + if s.cfg.RunRetentionDays > 0 { + cutoff := now.AddDate(0, 0, -s.cfg.RunRetentionDays).Format(time.RFC3339) + // Delete action detail first so the rows are gone even if FK cascade is + // unavailable, then the runs themselves. + actions := s.exec( + `DELETE FROM rule_run_actions WHERE rule_run_id IN (SELECT id FROM rule_runs WHERE started_utc < ?)`, + cutoff) + runs := s.exec(`DELETE FROM rule_runs WHERE started_utc < ?`, cutoff) + if runs > 0 || actions > 0 { + s.logger.Info("Maintenance", "Pruned %d rule run(s) and %d action record(s) older than %d day(s)", + runs, actions, s.cfg.RunRetentionDays) + } + } + + if s.cfg.AuditRetentionDays > 0 { + cutoff := now.AddDate(0, 0, -s.cfg.AuditRetentionDays).Format(time.RFC3339) + if n := s.exec(`DELETE FROM audit_events WHERE created_utc < ?`, cutoff); n > 0 { + s.logger.Info("Maintenance", "Pruned %d audit event(s) older than %d day(s)", n, s.cfg.AuditRetentionDays) + } + } + + if s.cfg.Vacuum { + if _, err := s.db.Exec("VACUUM"); err != nil { + s.logger.Warn("Maintenance", "VACUUM failed: %v", err) + } + } +} + +// exec runs a DELETE and returns the affected row count (0 on error, logged). +func (s *MaintenanceService) exec(query string, args ...any) int64 { + res, err := s.db.Exec(query, args...) + if err != nil { + s.logger.Warn("Maintenance", "maintenance query failed: %v", err) + return 0 + } + n, _ := res.RowsAffected() + return n +} diff --git a/backend/internal/services/maintenance_service_test.go b/backend/internal/services/maintenance_service_test.go new file mode 100644 index 0000000..1b0b800 --- /dev/null +++ b/backend/internal/services/maintenance_service_test.go @@ -0,0 +1,85 @@ +package services + +import ( + "path/filepath" + "testing" + "time" + + "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/db" + "github.com/Grace-Solutions/OrchestrAD/internal/logging" +) + +func TestMaintenancePrunesOldHistory(t *testing.T) { + database, err := db.New(config.DatabaseConfig{ + Path: filepath.Join(t.TempDir(), "maint_test.db"), + MaxOpenConns: 2, MaxIdleConns: 2, WALMode: true, ForeignKeys: true, + }, logging.Default()) + if err != nil { + t.Fatalf("db.New: %v", err) + } + defer database.Close() + if err := database.Migrate(); err != nil { + t.Fatalf("migrate: %v", err) + } + conn := database.Conn() + + old := time.Now().UTC().AddDate(0, 0, -120).Format(time.RFC3339) + recent := time.Now().UTC().AddDate(0, 0, -1).Format(time.RFC3339) + + // A connection + rule to satisfy rule_runs FK; a rule_action for actions FK. + if _, err := conn.Exec(`INSERT INTO ad_connections (id,name,is_enabled,hosts,port,root_dn,default_search_scope,timeout_seconds,paging_enabled,page_size,created_utc,updated_utc) VALUES ('c1','c',1,'h',389,'DC=x','Subtree',15,0,0,?,?)`, recent, recent); err != nil { + t.Fatalf("conn: %v", err) + } + if _, err := conn.Exec(`INSERT INTO rules (id,name,is_enabled,ad_connection_id,object_type,execution_mode,group_join_operator,max_parallelism,stop_on_error,created_utc,updated_utc) VALUES ('r1','r',1,'c1','User','Apply','AND',1,0,?,?)`, recent, recent); err != nil { + t.Fatalf("rule: %v", err) + } + if _, err := conn.Exec(`INSERT INTO rule_actions (id,rule_id,action_type,is_enabled,sort_order,configuration_json,created_utc,updated_utc) VALUES ('a1','r1','AddToGroup',1,0,'{}',?,?)`, recent, recent); err != nil { + t.Fatalf("action: %v", err) + } + + // Old + recent runs, each with an action row. + mkRun := func(id, started string) { + if _, err := conn.Exec(`INSERT INTO rule_runs (id,rule_id,status,started_utc,execution_mode,created_utc) VALUES (?,?,?,?,?,?)`, id, "r1", "Completed", started, "Apply", started); err != nil { + t.Fatalf("run %s: %v", id, err) + } + if _, err := conn.Exec(`INSERT INTO rule_run_actions (id,rule_run_id,rule_action_id,object_dn,action_type,status,created_utc) VALUES (?,?,?,?,?,?,?)`, "act-"+id, id, "a1", "CN=x", "AddToGroup", "Succeeded", started); err != nil { + t.Fatalf("run action %s: %v", id, err) + } + } + mkRun("old", old) + mkRun("new", recent) + + // Old + recent audit events. + if _, err := conn.Exec(`INSERT INTO audit_events (id,event_type,component,action,success,created_utc) VALUES ('ae-old','X','C','A',1,?)`, old); err != nil { + t.Fatalf("audit old: %v", err) + } + if _, err := conn.Exec(`INSERT INTO audit_events (id,event_type,component,action,success,created_utc) VALUES ('ae-new','X','C','A',1,?)`, recent); err != nil { + t.Fatalf("audit new: %v", err) + } + + svc := NewMaintenanceService(conn, config.MaintenanceConfig{ + RunRetentionDays: 30, AuditRetentionDays: 30, IntervalHours: 24, Vacuum: true, + }, logging.Default()) + svc.RunOnce() + + count := func(q string) int { + var n int + if err := conn.QueryRow(q).Scan(&n); err != nil { + t.Fatalf("count %q: %v", q, err) + } + return n + } + if got := count(`SELECT COUNT(*) FROM rule_runs`); got != 1 { + t.Errorf("rule_runs = %d, want 1 (recent kept)", got) + } + if got := count(`SELECT COUNT(*) FROM rule_run_actions`); got != 1 { + t.Errorf("rule_run_actions = %d, want 1 (old cascaded away)", got) + } + if got := count(`SELECT COUNT(*) FROM audit_events`); got != 1 { + t.Errorf("audit_events = %d, want 1 (recent kept)", got) + } + if count(`SELECT COUNT(*) FROM rule_runs WHERE id='new'`) != 1 { + t.Errorf("recent run should be retained") + } +} -- 2.52.0 From 8ee9b95cde163eaed94cf5e9117d215080e45978 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 18:25:15 -0400 Subject: [PATCH 31/34] feat(ui): make dashboard widgets drill in Count tiles link to their resource pages; the run-stat cards open Activity, Recent Rule Runs opens Rule Runs, and Connection Health opens Connections. Co-Authored-By: Claude Opus 4.8 --- .../(app)/components/dashboard/CountTile.tsx | 18 +++- frontend/src/app/(app)/page.tsx | 99 ++++++++++--------- 2 files changed, 70 insertions(+), 47 deletions(-) diff --git a/frontend/src/app/(app)/components/dashboard/CountTile.tsx b/frontend/src/app/(app)/components/dashboard/CountTile.tsx index 8577ace..120a06a 100644 --- a/frontend/src/app/(app)/components/dashboard/CountTile.tsx +++ b/frontend/src/app/(app)/components/dashboard/CountTile.tsx @@ -2,6 +2,7 @@ import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; +import Link from "next/link"; import BlankCard from "@/app/components/shared/BlankCard"; type ColorKey = @@ -17,13 +18,17 @@ interface Props { total: number; enabled: number; color?: ColorKey; + href?: string; } -const CountTile = ({ label, total, enabled, color = "primary" }: Props) => { +const CountTile = ({ label, total, enabled, color = "primary", href }: Props) => { const disabled = Math.max(0, total - enabled); - return ( + const body = ( - + {label} @@ -41,6 +46,13 @@ const CountTile = ({ label, total, enabled, color = "primary" }: Props) => { ); + + if (!href) return body; + return ( + + {body} + + ); }; export default CountTile; diff --git a/frontend/src/app/(app)/page.tsx b/frontend/src/app/(app)/page.tsx index 7d19832..bea552f 100644 --- a/frontend/src/app/(app)/page.tsx +++ b/frontend/src/app/(app)/page.tsx @@ -6,6 +6,7 @@ import CircularProgress from "@mui/material/CircularProgress"; import Grid from "@mui/material/Grid"; import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; +import Link from "next/link"; import { useEffect, useState } from "react"; import PageContainer from "@/app/components/container/PageContainer"; @@ -17,6 +18,8 @@ import { DashboardApi } from "@/lib/api/resources"; import { ApiError } from "@/lib/api/client"; import type { DashboardSummary } from "@/lib/api/types"; +const cardLink: React.CSSProperties = { textDecoration: "none", color: "inherit", display: "block" }; + export default function Dashboard() { const [data, setData] = useState(null); const [error, setError] = useState(null); @@ -74,72 +77,80 @@ export default function Dashboard() { - + - + - + - + - + - + - - - - {ruleRunStats.last24hTotal} - Total - - - {ruleRunStats.last24hSuccess} - Success - - - {ruleRunStats.last24hFailed} - Failed - - - + + + + + {ruleRunStats.last24hTotal} + Total + + + {ruleRunStats.last24hSuccess} + Success + + + {ruleRunStats.last24hFailed} + Failed + + + + - - - - {ruleRunStats.last7dTotal} - Total - - - {ruleRunStats.last7dSuccess} - Success - - - {ruleRunStats.last7dFailed} - Failed - - - + + + + + {ruleRunStats.last7dTotal} + Total + + + {ruleRunStats.last7dSuccess} + Success + + + {ruleRunStats.last7dFailed} + Failed + + + + - - - + + + + + - - - + + + + + -- 2.52.0 From c74dd632813383e29b45bcdabce684eeb3292dbe Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 18:33:53 -0400 Subject: [PATCH 32/34] feat(api): OpenAPI/Swagger docs generated from the router + PowerShell example - GET /api/docs serves a Swagger UI and GET /api/openapi.json serves an OpenAPI 3 spec built by walking the live chi router, so documented paths always match what the build serves. A small registry adds rich detail (request bodies, params, schemas) for the automation-critical operations (auth login, rule create/update/preview, connection introspection); RuleInput and friends are defined as reusable component schemas. - Add docs/examples/Create-OrchestrADRule.ps1: a no-alias PowerShell sample that builds headers/body as typed dictionaries, serializes with ConvertTo-Json, logs in, creates a SyncGroupMembership rule, and runs it. - README: new "API & Automation" section (Swagger + PowerShell), and a "Logging & Retention" section documenting log rotation and the new database maintenance/retention knobs. Tests cover spec generation from a router and registry well-formedness. Co-Authored-By: Claude Opus 4.8 --- README.md | 73 ++++- backend/internal/api/openapi.go | 342 ++++++++++++++++++++++++ backend/internal/api/openapi_test.go | 62 +++++ backend/internal/server/server.go | 7 + docs/examples/Create-OrchestrADRule.ps1 | 110 ++++++++ 5 files changed, 592 insertions(+), 2 deletions(-) create mode 100644 backend/internal/api/openapi.go create mode 100644 backend/internal/api/openapi_test.go create mode 100644 docs/examples/Create-OrchestrADRule.ps1 diff --git a/README.md b/README.md index 3fdd06e..b8e32af 100644 --- a/README.md +++ b/README.md @@ -127,14 +127,83 @@ OrchestrAD separates authentication concerns: --- -## 🧾 Logging Format +## πŸ”Œ API & Automation -Standard logs: +Everything the UI does is available over the REST API, so rules can be created and run programmatically. + +* **Interactive docs (Swagger UI):** `https://:18090/api/docs` +* **OpenAPI 3 spec:** `https://:18090/api/openapi.json` + +The spec is **generated from the live router**, so it always reflects the endpoints the running build actually serves. Authenticate at `POST /api/v1/auth/login`, then send the returned token as `Authorization: Bearer `. + +### Create a rule from PowerShell + +A ready-to-run example lives at [`docs/examples/Create-OrchestrADRule.ps1`](docs/examples/Create-OrchestrADRule.ps1). It builds the headers and body as strongly-typed dictionaries, serializes the body with `ConvertTo-Json`, and uses full cmdlet names (no aliases): + +```powershell +# Authenticate +$loginBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$loginBody.Add('username', 'admin') +$loginBody.Add('password', 'admin') +$login = Invoke-RestMethod -Method 'Post' -Uri 'https://localhost:18090/api/v1/auth/login' ` + -ContentType 'application/json' -Body ($loginBody | ConvertTo-Json) -SkipCertificateCheck + +$headers = New-Object 'System.Collections.Generic.Dictionary[String,String]' +$headers.Add('Authorization', "Bearer $($login.data.token)") + +# Build the rule: users where department = Sales -> synced into a target group +$condition = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$condition.Add('attributeName', 'department'); $condition.Add('operator', 'Equals'); $condition.Add('comparisonValue', 'Sales') +$conditions = New-Object 'System.Collections.Generic.List[Object]'; $conditions.Add($condition) +$group = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$group.Add('joinOperator', 'AND'); $group.Add('conditions', $conditions) +$conditionGroups = New-Object 'System.Collections.Generic.List[Object]'; $conditionGroups.Add($group) + +$actionConfig = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$actionConfig.Add('targetGroupDn', 'CN=Sales,OU=Groups,DC=corp,DC=com') +$actionConfig.Add('syncMode', 'FullSync'); $actionConfig.Add('createIfMissing', $true) +$action = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$action.Add('actionType', 'SyncGroupMembership'); $action.Add('configurationJson', ($actionConfig | ConvertTo-Json -Compress)) +$actions = New-Object 'System.Collections.Generic.List[Object]'; $actions.Add($action) + +$ruleBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$ruleBody.Add('name', 'Sales Team'); $ruleBody.Add('adConnectionId', '') +$ruleBody.Add('objectType', 'User'); $ruleBody.Add('executionMode', 'Apply'); $ruleBody.Add('groupJoinOperator', 'AND') +$ruleBody.Add('conditionGroups', $conditionGroups); $ruleBody.Add('actions', $actions) + +Invoke-RestMethod -Method 'Post' -Uri 'https://localhost:18090/api/v1/rules' ` + -Headers $headers -ContentType 'application/json' -Body ($ruleBody | ConvertTo-Json -Depth 10) -SkipCertificateCheck +``` + +Tip: `GET /api/v1/rules/metadata` returns the valid object types, operators, action types, and sync modes for building rule bodies. + +--- + +## 🧾 Logging & Retention + +Standard log format: ``` [TimestampUTC] - [Component] - [Level] - Message ``` +**Log rotation** is automatic (max size, backups, and age; compressed): + +| Setting | Env var | Default | +| --- | --- | --- | +| Max file size (MB) | `ORCHESTRAD_LOG_MAX_SIZE_MB` | `100` | +| Rotated copies kept | `ORCHESTRAD_LOG_MAX_BACKUPS` | `3` | +| Max age (days) | `ORCHESTRAD_LOG_MAX_AGE_DAYS` | `30` | + +**Database maintenance** keeps history from growing forever β€” old rule runs and audit events are pruned and the database is compacted (`VACUUM`) on a schedule: + +| Setting | Env var | Default | +| --- | --- | --- | +| Rule-run history retained (days) | `ORCHESTRAD_RUN_RETENTION_DAYS` | `90` | +| Audit history retained (days) | `ORCHESTRAD_AUDIT_RETENTION_DAYS` | `180` | +| Maintenance interval (hours) | `ORCHESTRAD_MAINTENANCE_INTERVAL_HOURS` | `24` | +| Run VACUUM | `ORCHESTRAD_MAINTENANCE_VACUUM` | `true` | + Error logs: ``` diff --git a/backend/internal/api/openapi.go b/backend/internal/api/openapi.go new file mode 100644 index 0000000..1c90808 --- /dev/null +++ b/backend/internal/api/openapi.go @@ -0,0 +1,342 @@ +// Package api - OpenAPI (Swagger) spec generated from the live router. +// +// The path set is produced by walking the actual chi router, so new routes +// appear in the spec automatically and it cannot drift out of sync. A small +// registry supplies rich summaries, request bodies, and response schemas for +// the automation-critical operations (auth, rules, connection introspection); +// every other route is documented generically from its method and path. +package api + +import ( + "encoding/json" + "net/http" + "regexp" + "sort" + "strings" + "sync" + + "github.com/go-chi/chi/v5" +) + +// OpenAPIHandler serves the generated spec and a Swagger UI. The spec is built +// lazily on first request (once the whole router exists) and cached. +type OpenAPIHandler struct { + router chi.Router + once sync.Once + spec []byte +} + +// NewOpenAPIHandler creates a handler that documents the given router. +func NewOpenAPIHandler(router chi.Router) *OpenAPIHandler { + return &OpenAPIHandler{router: router} +} + +// Spec handles GET /api/openapi.json. +func (h *OpenAPIHandler) Spec(w http.ResponseWriter, r *http.Request) { + h.once.Do(func() { + spec := BuildOpenAPISpec(h.router) + h.spec, _ = json.Marshal(spec) + }) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(h.spec) +} + +// UI handles GET /api/docs β€” a self-contained Swagger UI page (CDN assets). +func (h *OpenAPIHandler) UI(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(swaggerUIHTML)) +} + +var pathParamRe = regexp.MustCompile(`\{([^}]+)\}`) + +// BuildOpenAPISpec walks the router and assembles an OpenAPI 3.0 document. +func BuildOpenAPISpec(router chi.Router) map[string]any { + paths := map[string]map[string]any{} + + _ = chi.Walk(router, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error { + // Only document the JSON API surface and the health/version probes. + if !strings.HasPrefix(route, "/api/") && route != "/health" { + return nil + } + route = strings.TrimSuffix(route, "/*") + if route == "" { + return nil + } + if paths[route] == nil { + paths[route] = map[string]any{} + } + paths[route][strings.ToLower(method)] = operationFor(method, route) + return nil + }) + + // map[string]map[string]any -> map[string]any for JSON. + pathsOut := make(map[string]any, len(paths)) + for p, ops := range paths { + pathsOut[p] = ops + } + + return map[string]any{ + "openapi": "3.0.3", + "info": map[string]any{ + "title": "OrchestrAD API", + "version": "1", + "description": "Active Directory rule automation. Authenticate at /api/v1/auth/login, then send the returned token as `Authorization: Bearer `.", + }, + "servers": []any{map[string]any{"url": "/", "description": "This server"}}, + "security": []any{ + map[string]any{"bearerAuth": []any{}}, + }, + "paths": pathsOut, + "components": map[string]any{ + "securitySchemes": map[string]any{ + "bearerAuth": map[string]any{"type": "http", "scheme": "bearer"}, + }, + "schemas": openAPISchemas(), + }, + } +} + +// publicRoutes need no auth; their operations advertise empty security. +var publicRoutes = map[string]bool{ + "/health": true, + "/api/health": true, + "/api/v1/health": true, + "/api/v1/version": true, + "/api/v1/auth/login": true, + "/api/v1/auth/csrf": true, + "/api/v1/auth/oidc/status": true, + "/api/v1/auth/oidc/login": true, + "/api/v1/auth/oidc/callback": true, + "/api/openapi.json": true, + "/api/docs": true, +} + +func operationFor(method, route string) map[string]any { + op := map[string]any{ + "tags": []any{tagFor(route)}, + "responses": defaultResponses(), + } + + // Path parameters. + var params []any + for _, m := range pathParamRe.FindAllStringSubmatch(route, -1) { + params = append(params, map[string]any{ + "name": m[1], "in": "path", "required": true, + "schema": map[string]any{"type": "string"}, + }) + } + if len(params) > 0 { + op["parameters"] = params + } + if publicRoutes[route] { + op["security"] = []any{} + } + + if meta, ok := operationRegistry[method+" "+route]; ok { + for k, v := range meta { + op[k] = v + } + } else { + op["summary"] = strings.ToUpper(method) + " " + route + } + return op +} + +func tagFor(route string) string { + parts := strings.Split(strings.TrimPrefix(route, "/api/v1/"), "/") + if len(parts) == 0 || parts[0] == "" { + return "system" + } + return parts[0] +} + +func defaultResponses() map[string]any { + return map[string]any{ + "200": map[string]any{"description": "OK"}, + "400": map[string]any{"description": "Bad request"}, + "401": map[string]any{"description": "Unauthorized"}, + } +} + +func jsonBody(schemaRef string) map[string]any { + return map[string]any{ + "required": true, + "content": map[string]any{ + "application/json": map[string]any{ + "schema": map[string]any{"$ref": "#/components/schemas/" + schemaRef}, + }, + }, + } +} + +// operationRegistry supplies rich detail for the operations most useful to +// automate. Everything else is documented generically. +var operationRegistry = map[string]map[string]any{ + "POST /api/v1/auth/login": { + "summary": "Log in and obtain a bearer token", + "security": []any{}, + "requestBody": jsonBody("LoginRequest"), + "responses": map[string]any{ + "200": map[string]any{"description": "Token issued"}, + "401": map[string]any{"description": "Invalid credentials"}, + }, + }, + "GET /api/v1/rules/metadata": { + "summary": "Vocabulary for building rules (object types, operators, action types, sync modes, common attributes)", + }, + "POST /api/v1/rules": { + "summary": "Create a rule (dynamic group)", + "requestBody": jsonBody("RuleInput"), + }, + "PUT /api/v1/rules/{id}": { + "summary": "Update a rule; conditionGroups/actions replace the rule's logic when present", + "requestBody": jsonBody("RuleInput"), + }, + "POST /api/v1/rules/preview": { + "summary": "Preview an unsaved rule draft (matched objects + planned +add/-remove)", + "requestBody": jsonBody("RuleInput"), + }, + "POST /api/v1/rules/{id}/run": { + "summary": "Run a rule now", + }, + "GET /api/v1/ad-connections/{id}/directory": { + "summary": "Search groups / OUs on the connection (for target pickers)", + "parameters": []any{ + map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}, + map[string]any{"name": "type", "in": "query", "schema": map[string]any{"type": "string", "enum": []any{"Group", "OU", "User", "Computer"}}}, + map[string]any{"name": "q", "in": "query", "schema": map[string]any{"type": "string"}}, + }, + }, + "GET /api/v1/ad-connections/{id}/attributes": { + "summary": "Schema attributes applicable to an object type (filter builder)", + "parameters": []any{ + map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}, + map[string]any{"name": "objectType", "in": "query", "schema": map[string]any{"type": "string", "enum": []any{"User", "Computer", "Group"}}}, + map[string]any{"name": "q", "in": "query", "schema": map[string]any{"type": "string"}}, + }, + }, + "GET /api/v1/ad-connections/{id}/attribute-values": { + "summary": "Distinct values present for an attribute (value autocomplete)", + "parameters": []any{ + map[string]any{"name": "id", "in": "path", "required": true, "schema": map[string]any{"type": "string"}}, + map[string]any{"name": "attribute", "in": "query", "required": true, "schema": map[string]any{"type": "string"}}, + map[string]any{"name": "objectType", "in": "query", "schema": map[string]any{"type": "string"}}, + map[string]any{"name": "q", "in": "query", "schema": map[string]any{"type": "string"}}, + }, + }, + "GET /api/v1/activity": { + "summary": "Action feed (filter by ruleId, category, status, actionType, search)", + }, +} + +func openAPISchemas() map[string]any { + str := map[string]any{"type": "string"} + boolean := map[string]any{"type": "boolean"} + return map[string]any{ + "LoginRequest": map[string]any{ + "type": "object", + "required": []any{"username", "password"}, + "properties": map[string]any{ + "username": str, + "password": str, + }, + }, + "RuleConditionInput": map[string]any{ + "type": "object", + "required": []any{"attributeName", "operator"}, + "properties": map[string]any{ + "attributeName": map[string]any{"type": "string", "example": "department"}, + "operator": map[string]any{"type": "string", "example": "Equals", "description": "See /rules/metadata operators"}, + "comparisonValue": map[string]any{"type": "string", "example": "Sales"}, + "customLdapExpression": map[string]any{"type": "string", "description": "Used when operator is CustomLdap"}, + "negate": boolean, + "isEnabled": boolean, + }, + }, + "RuleConditionGroupInput": map[string]any{ + "type": "object", + "properties": map[string]any{ + "name": str, + "joinOperator": map[string]any{"type": "string", "enum": []any{"AND", "OR"}}, + "negate": boolean, + "isEnabled": boolean, + "conditions": map[string]any{ + "type": "array", + "items": map[string]any{"$ref": "#/components/schemas/RuleConditionInput"}, + }, + }, + }, + "RuleActionInput": map[string]any{ + "type": "object", + "required": []any{"actionType", "configurationJson"}, + "properties": map[string]any{ + "actionType": map[string]any{"type": "string", "example": "SyncGroupMembership", "description": "SyncGroupMembership | MoveToOu | EnsureGroupExists | AddToGroup"}, + "configurationJson": map[string]any{"type": "string", "example": "{\"targetGroupDn\":\"CN=Sales,OU=Groups,DC=corp,DC=com\",\"syncMode\":\"FullSync\",\"createIfMissing\":true}"}, + "isEnabled": boolean, + }, + }, + "RuleInput": map[string]any{ + "type": "object", + "required": []any{"name", "adConnectionId", "objectType"}, + "properties": map[string]any{ + "name": str, + "description": str, + "isEnabled": boolean, + "adConnectionId": str, + "objectType": map[string]any{"type": "string", "enum": []any{"User", "Computer", "Group"}}, + "baseDnOverride": str, + "searchScopeOverride": map[string]any{"type": "string", "enum": []any{"Base", "OneLevel", "Subtree"}}, + "scheduleId": str, + "executionMode": map[string]any{"type": "string", "enum": []any{"Apply", "PreviewOnly"}}, + "groupJoinOperator": map[string]any{"type": "string", "enum": []any{"AND", "OR"}}, + "stopOnError": boolean, + "conditionGroups": map[string]any{ + "type": "array", + "items": map[string]any{"$ref": "#/components/schemas/RuleConditionGroupInput"}, + }, + "actions": map[string]any{ + "type": "array", + "items": map[string]any{"$ref": "#/components/schemas/RuleActionInput"}, + }, + }, + }, + } +} + +// DocumentedAPIPaths returns the set of "METHOD /path" the spec covers, for the +// drift test that keeps documentation and routing in lock-step. +func DocumentedAPIPaths(router chi.Router) []string { + spec := BuildOpenAPISpec(router) + var out []string + for p, ops := range spec["paths"].(map[string]any) { + for m := range ops.(map[string]any) { + out = append(out, strings.ToUpper(m)+" "+p) + } + } + sort.Strings(out) + return out +} + +const swaggerUIHTML = ` + + + + + OrchestrAD API + + + +
    + + + +` diff --git a/backend/internal/api/openapi_test.go b/backend/internal/api/openapi_test.go new file mode 100644 index 0000000..3370125 --- /dev/null +++ b/backend/internal/api/openapi_test.go @@ -0,0 +1,62 @@ +package api + +import ( + "net/http" + "strings" + "testing" + + "github.com/go-chi/chi/v5" +) + +func TestBuildOpenAPISpecFromRouter(t *testing.T) { + r := chi.NewRouter() + noop := func(w http.ResponseWriter, _ *http.Request) {} + r.Route("/api/v1", func(r chi.Router) { + r.Get("/rules/metadata", noop) + r.Post("/rules", noop) + r.Get("/ad-connections/{id}/directory", noop) + }) + + spec := BuildOpenAPISpec(r) + paths, ok := spec["paths"].(map[string]any) + if !ok { + t.Fatal("paths missing") + } + + // Walked routes are documented. + for _, p := range []string{"/api/v1/rules/metadata", "/api/v1/rules", "/api/v1/ad-connections/{id}/directory"} { + if _, ok := paths[p]; !ok { + t.Errorf("path %q not documented", p) + } + } + + // Registry detail is applied: POST /rules carries a request body. + post := paths["/api/v1/rules"].(map[string]any)["post"].(map[string]any) + if _, ok := post["requestBody"]; !ok { + t.Errorf("POST /rules should have a requestBody from the registry") + } + + // Path parameters are derived. + dir := paths["/api/v1/ad-connections/{id}/directory"].(map[string]any)["get"].(map[string]any) + if _, ok := dir["parameters"]; !ok { + t.Errorf("directory route should declare path/query parameters") + } + + // Components include the RuleInput schema used for automation. + schemas := spec["components"].(map[string]any)["schemas"].(map[string]any) + if _, ok := schemas["RuleInput"]; !ok { + t.Errorf("RuleInput schema missing") + } +} + +// TestOperationRegistryWellFormed guards against typos: every registry key must +// be " /api/..." so it can only enrich a real API route. +func TestOperationRegistryWellFormed(t *testing.T) { + methods := map[string]bool{"GET": true, "POST": true, "PUT": true, "DELETE": true, "PATCH": true} + for key := range operationRegistry { + parts := strings.SplitN(key, " ", 2) + if len(parts) != 2 || !methods[parts[0]] || !strings.HasPrefix(parts[1], "/api/") { + t.Errorf("malformed operationRegistry key %q", key) + } + } +} diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 284ffd2..d106acb 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -124,6 +124,13 @@ func (s *Server) setupRoutes() { s.router.Get("/api/health", s.handleHealth) s.router.Head("/api/health", s.handleHealth) + // API documentation: an OpenAPI 3 spec generated from this router (so it + // stays in sync) and a Swagger UI to browse and try it. Public so tooling + // can read the schema before authenticating. + openAPIHandler := api.NewOpenAPIHandler(s.router) + s.router.Get("/api/openapi.json", openAPIHandler.Spec) + s.router.Get("/api/docs", openAPIHandler.UI) + // API v1 routes s.router.Route("/api/v1", func(r chi.Router) { // Public system endpoints β€” discoverable without a session so that diff --git a/docs/examples/Create-OrchestrADRule.ps1 b/docs/examples/Create-OrchestrADRule.ps1 new file mode 100644 index 0000000..104f77c --- /dev/null +++ b/docs/examples/Create-OrchestrADRule.ps1 @@ -0,0 +1,110 @@ +<# +.SYNOPSIS + Creates an OrchestrAD dynamic-group rule through the REST API. + +.DESCRIPTION + Demonstrates authenticating to OrchestrAD and creating a rule that syncs all + users whose department is "Sales" into a target group (creating the group if + missing, and keeping its membership exactly in step with the filter). + + Style notes (as requested): + * Body and headers are built as strongly-typed dictionaries via + New-Object with the full type name. + * The body is converted to JSON with ConvertTo-Json. + * Full cmdlet names are used throughout β€” no aliases. + + Requires PowerShell 7+ (uses -SkipCertificateCheck for the self-signed dev + certificate; drop that switch when a trusted certificate is in use). + +.EXAMPLE + ./Create-OrchestrADRule.ps1 -BaseUrl 'https://localhost:18090' -Username 'admin' -Password 'admin' -ConnectionId '57feb058-...' -TargetGroupDn 'CN=Sales,OU=Groups,DC=corp,DC=com' +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] [string] $BaseUrl, + [Parameter(Mandatory = $true)] [string] $Username, + [Parameter(Mandatory = $true)] [string] $Password, + [Parameter(Mandatory = $true)] [string] $ConnectionId, + [Parameter(Mandatory = $true)] [string] $TargetGroupDn, + [Parameter(Mandatory = $false)] [string] $Department = 'Sales' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# --- 1. Authenticate and obtain a bearer token ---------------------------------- + +$loginBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$loginBody.Add('username', $Username) +$loginBody.Add('password', $Password) + +$loginResponse = Invoke-RestMethod -Method 'Post' -Uri "$BaseUrl/api/v1/auth/login" ` + -ContentType 'application/json' -Body ($loginBody | ConvertTo-Json) -SkipCertificateCheck + +$token = $loginResponse.data.token +Write-Output "Authenticated as '$Username'." + +# --- 2. Build request headers as a dictionary ----------------------------------- + +$headers = New-Object 'System.Collections.Generic.Dictionary[String,String]' +$headers.Add('Authorization', "Bearer $token") +$headers.Add('Accept', 'application/json') + +# --- 3. Build the rule body as nested dictionaries / lists ---------------------- + +# One condition: department equals $Department. +$condition = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$condition.Add('attributeName', 'department') +$condition.Add('operator', 'Equals') +$condition.Add('comparisonValue', $Department) + +$conditions = New-Object 'System.Collections.Generic.List[Object]' +$conditions.Add($condition) + +$group = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$group.Add('joinOperator', 'AND') +$group.Add('conditions', $conditions) + +$conditionGroups = New-Object 'System.Collections.Generic.List[Object]' +$conditionGroups.Add($group) + +# One action: sync membership to the target group (full add + remove). +$actionConfig = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$actionConfig.Add('targetGroupDn', $TargetGroupDn) +$actionConfig.Add('syncMode', 'FullSync') +$actionConfig.Add('createIfMissing', $true) +$actionConfig.Add('groupScope', 'Global') +$actionConfig.Add('groupType', 'Security') + +$action = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$action.Add('actionType', 'SyncGroupMembership') +# configurationJson is a JSON *string*, so serialize the config dictionary. +$action.Add('configurationJson', ($actionConfig | ConvertTo-Json -Compress)) + +$actions = New-Object 'System.Collections.Generic.List[Object]' +$actions.Add($action) + +$ruleBody = New-Object 'System.Collections.Generic.Dictionary[String,Object]' +$ruleBody.Add('name', "Sales - $Department") +$ruleBody.Add('description', 'Created via the OrchestrAD REST API') +$ruleBody.Add('adConnectionId', $ConnectionId) +$ruleBody.Add('objectType', 'User') +$ruleBody.Add('executionMode', 'Apply') +$ruleBody.Add('groupJoinOperator', 'AND') +$ruleBody.Add('conditionGroups', $conditionGroups) +$ruleBody.Add('actions', $actions) + +# --- 4. Create the rule --------------------------------------------------------- + +$json = $ruleBody | ConvertTo-Json -Depth 10 +$rule = Invoke-RestMethod -Method 'Post' -Uri "$BaseUrl/api/v1/rules" ` + -Headers $headers -ContentType 'application/json' -Body $json -SkipCertificateCheck + +Write-Output ("Created rule '{0}' (id {1})." -f $rule.data.name, $rule.data.id) + +# --- 5. (Optional) run it now --------------------------------------------------- + +$run = Invoke-RestMethod -Method 'Post' -Uri "$BaseUrl/api/v1/rules/$($rule.data.id)/run" ` + -Headers $headers -SkipCertificateCheck +Write-Output ("Run status: {0}" -f $run.data.status) -- 2.52.0 From 4f949e6d8f432c208eae70ca2b90685adcf7318d Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 19:01:30 -0400 Subject: [PATCH 33/34] fix(auth): make API keys actually authenticate; add read/read-write scopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API keys were never validated β€” the middleware only checked session tokens, so an X-API-Key request always 401'd. Add auth.Service.ValidateAPIKey (looks up the key hash, enforces enabled/revoked/expiry, loads the user + roles, stamps last_used_utc) and route X-API-Key / bearer auth through it. Add per-key scopes (migration 006): "read" (GET/HEAD only) or "readwrite" (full access, default). The middleware rejects mutating requests from a read-scoped key with 403. Create accepts a scope; list and create responses include it; the UI create dialog has a scope selector and the list shows a scope chip. Disable/re-enable and revoke (permanent) were already correct. Verified live: RW key GET/POST ok; read key GET ok, POST 403; disableβ†’401, re-enableβ†’200; revokeβ†’401 and re-enable blocked. Co-Authored-By: Claude Opus 4.8 --- backend/internal/api/apikeys_handlers.go | 4 ++ backend/internal/api/middleware.go | 46 ++++++++++++---- backend/internal/auth/auth.go | 53 +++++++++++++++++++ .../db/migrations/006_api_key_scope.down.sql | 1 + .../db/migrations/006_api_key_scope.up.sql | 4 ++ backend/internal/services/apikey_service.go | 30 ++++++++--- .../app/(app)/api-keys/ApiKeyCreateDialog.tsx | 11 ++++ frontend/src/app/(app)/api-keys/page.tsx | 6 +++ frontend/src/lib/api/resources.ts | 2 +- frontend/src/lib/api/types.ts | 1 + 10 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 backend/internal/db/migrations/006_api_key_scope.down.sql create mode 100644 backend/internal/db/migrations/006_api_key_scope.up.sql diff --git a/backend/internal/api/apikeys_handlers.go b/backend/internal/api/apikeys_handlers.go index 86bbd23..7d8dcf9 100644 --- a/backend/internal/api/apikeys_handlers.go +++ b/backend/internal/api/apikeys_handlers.go @@ -27,6 +27,7 @@ func NewAPIKeysHandler(service *services.APIKeyService, auditService *audit.Serv type APIKeyRequest struct { Name string `json:"name"` UserID string `json:"userId,omitempty"` + Scope string `json:"scope,omitempty"` // "read" or "readwrite" (default) ExpiresAt *time.Time `json:"expiresAt,omitempty"` } @@ -36,6 +37,7 @@ type APIKeyResponse struct { UserID string `json:"userId"` Name string `json:"name"` KeyPrefix string `json:"keyPrefix"` + Scope string `json:"scope"` ExpiresAt *time.Time `json:"expiresAt,omitempty"` IsEnabled bool `json:"isEnabled"` LastUsedAt *time.Time `json:"lastUsedAt,omitempty"` @@ -102,6 +104,7 @@ func (h *APIKeysHandler) Create(w http.ResponseWriter, r *http.Request) { result, err := h.service.Create(services.CreateAPIKeyInput{ UserID: userID, Name: req.Name, + Scope: req.Scope, ExpiresAt: req.ExpiresAt, }) if err != nil { @@ -180,6 +183,7 @@ func apiKeyToResponse(k *services.APIKey) APIKeyResponse { UserID: k.UserID, Name: k.Name, KeyPrefix: k.KeyPrefix, + Scope: k.Scope, ExpiresAt: k.ExpiresAt, IsEnabled: k.IsEnabled, LastUsedAt: k.LastUsedAt, diff --git a/backend/internal/api/middleware.go b/backend/internal/api/middleware.go index 8b72c2f..18916fb 100644 --- a/backend/internal/api/middleware.go +++ b/backend/internal/api/middleware.go @@ -24,11 +24,16 @@ func AuthMiddleware(authService *auth.Service) func(http.Handler) http.Handler { return } - user, err := authService.ValidateSession(token) + user, scope, err := validateAuth(authService, r, token) if err != nil { WriteError(w, http.StatusUnauthorized, ErrCodeUnauthorized, "Invalid or expired token") return } + // A read-scoped API key may only perform safe (read) requests. + if scope == "read" && !isReadMethod(r.Method) { + WriteError(w, http.StatusForbidden, ErrCodeForbidden, "This API key is read-only") + return + } ctx := context.WithValue(r.Context(), userContextKey, user) next.ServeHTTP(w, r.WithContext(ctx)) @@ -42,7 +47,7 @@ func OptionalAuthMiddleware(authService *auth.Service) func(http.Handler) http.H return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := extractToken(r) if token != "" { - user, err := authService.ValidateSession(token) + user, _, err := validateAuth(authService, r, token) if err == nil { ctx := context.WithValue(r.Context(), userContextKey, user) r = r.WithContext(ctx) @@ -92,24 +97,47 @@ func GetUserFromContext(ctx context.Context) *models.User { return user } +// validateAuth resolves the caller to a user and, for API-key auth, the key's +// scope ("read"/"readwrite"; empty for a session, which is full access). A token +// from the X-API-Key header is validated as an API key; otherwise it is +// validated as a session token, falling back to API-key validation so a key +// sent as a bearer token also works. +func validateAuth(authService *auth.Service, r *http.Request, token string) (*models.User, string, error) { + if r.Header.Get("X-API-Key") != "" { + return authService.ValidateAPIKey(token) + } + user, err := authService.ValidateSession(token) + if err != nil { + if apiUser, scope, apiErr := authService.ValidateAPIKey(token); apiErr == nil { + return apiUser, scope, nil + } + return nil, "", err + } + return user, "", nil +} + +func isReadMethod(method string) bool { + return method == http.MethodGet || method == http.MethodHead || method == http.MethodOptions +} + func extractToken(r *http.Request) string { // Check Authorization header auth := r.Header.Get("Authorization") if strings.HasPrefix(auth, "Bearer ") { return auth[7:] } - + // Check X-API-Key header for API key auth if apiKey := r.Header.Get("X-API-Key"); apiKey != "" { return apiKey } - + // Check cookie (for browser sessions) cookie, err := r.Cookie("session") if err == nil && cookie.Value != "" { return cookie.Value } - + return "" } @@ -132,26 +160,26 @@ func CSRFMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, r) return } - + // Skip CSRF for API key auth if r.Header.Get("X-API-Key") != "" { next.ServeHTTP(w, r) return } - + // Skip CSRF for Bearer token auth (typically from NextAuth) if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") { next.ServeHTTP(w, r) return } - + // TODO: Validate CSRF token from header/body csrfToken := r.Header.Get("X-CSRF-Token") if csrfToken == "" { WriteError(w, http.StatusForbidden, ErrCodeForbidden, "CSRF token required") return } - + // TODO: Validate the token against stored session next.ServeHTTP(w, r) }) diff --git a/backend/internal/auth/auth.go b/backend/internal/auth/auth.go index 4db5d7c..0af787d 100644 --- a/backend/internal/auth/auth.go +++ b/backend/internal/auth/auth.go @@ -234,6 +234,59 @@ func (s *Service) ValidateSession(token string) (*models.User, error) { return user, nil } +// ValidateAPIKey resolves an API key (as sent in the X-API-Key header) to its +// owning user and scope ("read" or "readwrite"), enforcing the key's +// enabled/revoked/expiry state, and best-effort stamps last_used_utc. Mirrors +// ValidateSession for the API-key auth path. +func (s *Service) ValidateAPIKey(token string) (*models.User, string, error) { + keyHash := crypto.HashAPIKey(token) + + var id, userID string + var isEnabled int + var expiresStr, revokedStr, scopeStr sql.NullString + err := s.db.QueryRow(` + SELECT id, user_id, is_enabled, expires_utc, revoked_utc, scope + FROM api_keys WHERE key_hash = ? + `, keyHash).Scan(&id, &userID, &isEnabled, &expiresStr, &revokedStr, &scopeStr) + if err == sql.ErrNoRows { + return nil, "", ErrInvalidCredentials + } + if err != nil { + return nil, "", err + } + if revokedStr.Valid { + return nil, "", ErrSessionRevoked + } + if isEnabled == 0 { + return nil, "", ErrSessionRevoked + } + if expiresStr.Valid && expiresStr.String != "" { + if expires, perr := time.Parse(time.RFC3339, expiresStr.String); perr == nil && time.Now().UTC().After(expires) { + return nil, "", ErrSessionExpired + } + } + + user, err := s.userRepo.GetByID(userID) + if err != nil { + return nil, "", err + } + if user == nil { + return nil, "", ErrUserNotFound + } + if !user.IsActive { + return nil, "", ErrUserDisabled + } + + _, _ = s.db.Exec(`UPDATE api_keys SET last_used_utc = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), id) + + user.Roles, _ = s.GetUserRoles(user.ID) + scope := scopeStr.String + if scope == "" { + scope = "readwrite" + } + return user, scope, nil +} + // ChangePassword verifies the user's current password and, on success, // replaces it with newPassword while clearing the password_reset_required // flag atomically through UserRepository.UpdatePassword. diff --git a/backend/internal/db/migrations/006_api_key_scope.down.sql b/backend/internal/db/migrations/006_api_key_scope.down.sql new file mode 100644 index 0000000..9909434 --- /dev/null +++ b/backend/internal/db/migrations/006_api_key_scope.down.sql @@ -0,0 +1 @@ +ALTER TABLE api_keys DROP COLUMN scope; diff --git a/backend/internal/db/migrations/006_api_key_scope.up.sql b/backend/internal/db/migrations/006_api_key_scope.up.sql new file mode 100644 index 0000000..c6ce284 --- /dev/null +++ b/backend/internal/db/migrations/006_api_key_scope.up.sql @@ -0,0 +1,4 @@ +-- API keys carry a scope controlling what they may do: "read" (GET only) or +-- "readwrite" (full access). Existing keys default to readwrite to preserve +-- current behavior. +ALTER TABLE api_keys ADD COLUMN scope TEXT NOT NULL DEFAULT 'readwrite'; diff --git a/backend/internal/services/apikey_service.go b/backend/internal/services/apikey_service.go index 2ce935f..26353a5 100644 --- a/backend/internal/services/apikey_service.go +++ b/backend/internal/services/apikey_service.go @@ -25,12 +25,27 @@ func NewAPIKeyService(db *sql.DB, logger *logging.Logger) *APIKeyService { } } +// API key scopes. +const ( + APIKeyScopeRead = "read" // GET/HEAD only + APIKeyScopeReadWrite = "readwrite" // full access +) + +// NormalizeAPIKeyScope returns a valid scope, defaulting unknown/empty to readwrite. +func NormalizeAPIKeyScope(s string) string { + if s == APIKeyScopeRead { + return APIKeyScopeRead + } + return APIKeyScopeReadWrite +} + // APIKey represents an API key (public view) type APIKey struct { ID string UserID string Name string KeyPrefix string + Scope string ExpiresAt *time.Time IsEnabled bool LastUsedAt *time.Time @@ -48,6 +63,7 @@ type CreateAPIKeyResult struct { type CreateAPIKeyInput struct { UserID string Name string + Scope string ExpiresAt *time.Time } @@ -68,21 +84,22 @@ func (s *APIKeyService) Create(input CreateAPIKeyInput) (*CreateAPIKeyResult, er id := uuid.New().String() now := time.Now().UTC() + scope := NormalizeAPIKeyScope(input.Scope) _, err = s.db.Exec(` INSERT INTO api_keys ( id, user_id, name, key_prefix, key_hash, - expires_utc, is_enabled, created_utc - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + expires_utc, is_enabled, scope, created_utc + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, id, input.UserID, input.Name, prefix, keyHash, - formatTimePtr(input.ExpiresAt), 1, formatTime(now), + formatTimePtr(input.ExpiresAt), 1, scope, formatTime(now), ) if err != nil { return nil, fmt.Errorf("failed to create API key: %w", err) } - s.logger.Info("APIKeyService", "Created API key '%s' for user %s", input.Name, input.UserID) + s.logger.Info("APIKeyService", "Created API key '%s' (%s) for user %s", input.Name, scope, input.UserID) return &CreateAPIKeyResult{ APIKey: APIKey{ @@ -90,6 +107,7 @@ func (s *APIKeyService) Create(input CreateAPIKeyInput) (*CreateAPIKeyResult, er UserID: input.UserID, Name: input.Name, KeyPrefix: prefix, + Scope: scope, ExpiresAt: input.ExpiresAt, IsEnabled: true, CreatedAt: now, @@ -186,7 +204,7 @@ func (s *APIKeyService) SetEnabled(id string, enabled bool) error { func (s *APIKeyService) List(userID string) ([]APIKey, error) { rows, err := s.db.Query(` SELECT id, user_id, name, key_prefix, expires_utc, - is_enabled, last_used_utc, created_utc, revoked_utc + is_enabled, scope, last_used_utc, created_utc, revoked_utc FROM api_keys WHERE user_id = ? ORDER BY created_utc DESC `, userID) @@ -202,7 +220,7 @@ func (s *APIKeyService) List(userID string) ([]APIKey, error) { var expires, lastUsed, revoked sql.NullString if err := rows.Scan( &k.ID, &k.UserID, &k.Name, &k.KeyPrefix, &expires, - &isEnabled, &lastUsed, &k.CreatedAt, &revoked, + &isEnabled, &k.Scope, &lastUsed, &k.CreatedAt, &revoked, ); err != nil { return nil, err } diff --git a/frontend/src/app/(app)/api-keys/ApiKeyCreateDialog.tsx b/frontend/src/app/(app)/api-keys/ApiKeyCreateDialog.tsx index 67c45ba..4c9de53 100644 --- a/frontend/src/app/(app)/api-keys/ApiKeyCreateDialog.tsx +++ b/frontend/src/app/(app)/api-keys/ApiKeyCreateDialog.tsx @@ -8,6 +8,7 @@ import DialogActions from "@mui/material/DialogActions"; import DialogContent from "@mui/material/DialogContent"; import DialogTitle from "@mui/material/DialogTitle"; import IconButton from "@mui/material/IconButton"; +import MenuItem from "@mui/material/MenuItem"; import Stack from "@mui/material/Stack"; import TextField from "@mui/material/TextField"; import Tooltip from "@mui/material/Tooltip"; @@ -27,6 +28,7 @@ interface Props { export default function ApiKeyCreateDialog({ open, userId, onClose, onCreated }: Props) { const [name, setName] = useState(""); + const [scope, setScope] = useState("readwrite"); const [expiresAt, setExpiresAt] = useState(""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); @@ -36,6 +38,7 @@ export default function ApiKeyCreateDialog({ open, userId, onClose, onCreated }: useEffect(() => { if (!open) return; setName(""); + setScope("readwrite"); setExpiresAt(""); setError(null); setFullKey(null); @@ -50,6 +53,7 @@ export default function ApiKeyCreateDialog({ open, userId, onClose, onCreated }: const res = await ApiKeysApi.create({ name: name.trim(), userId, + scope, expiresAt: expiresAt ? new Date(expiresAt).toISOString() : undefined, }); setFullKey(res.key); @@ -84,6 +88,13 @@ export default function ApiKeyCreateDialog({ open, userId, onClose, onCreated }: label="Name" value={name} onChange={(e) => setName(e.target.value)} required fullWidth placeholder="e.g. CI/CD integration" /> + setScope(e.target.value)} fullWidth + helperText="Read = GET only. Read/Write = full access." + > + Read / Write (full access) + Read only + setExpiresAt(e.target.value)} diff --git a/frontend/src/app/(app)/api-keys/page.tsx b/frontend/src/app/(app)/api-keys/page.tsx index d9ff04e..6dc964d 100644 --- a/frontend/src/app/(app)/api-keys/page.tsx +++ b/frontend/src/app/(app)/api-keys/page.tsx @@ -141,6 +141,7 @@ export default function ApiKeysPage() { Name Prefix + Scope Status Enabled Created @@ -164,6 +165,11 @@ export default function ApiKeysPage() { {k.keyPrefix}… + + + {revoked ? : expired ? diff --git a/frontend/src/lib/api/resources.ts b/frontend/src/lib/api/resources.ts index f31d50a..93f2f61 100644 --- a/frontend/src/lib/api/resources.ts +++ b/frontend/src/lib/api/resources.ts @@ -174,7 +174,7 @@ export const UsersApi = { export const ApiKeysApi = { list: (params?: PageParams & { userId?: string }) => api.get("/api/v1/api-keys", { query: params as Record }).then((r) => r.data ?? []), - create: (body: { name: string; userId?: string; expiresAt?: string }) => + create: (body: { name: string; userId?: string; scope?: string; expiresAt?: string }) => api.post("/api/v1/api-keys", body).then((r) => r.data), revoke: (id: string) => api.post<{ revoked: boolean }>(`/api/v1/api-keys/${id}/revoke`).then((r) => r.data), enable: (id: string) => api.post<{ enabled: boolean }>(`/api/v1/api-keys/${id}/enable`).then((r) => r.data), diff --git a/frontend/src/lib/api/types.ts b/frontend/src/lib/api/types.ts index 28b822c..6d7614d 100644 --- a/frontend/src/lib/api/types.ts +++ b/frontend/src/lib/api/types.ts @@ -172,6 +172,7 @@ export interface APIKey { userId: string; name: string; keyPrefix: string; + scope: string; expiresAt?: string; isEnabled: boolean; lastUsedAt?: string; -- 2.52.0 From dc0df06b92753a09d5b0f92fa88d4e10f87c4725 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Wed, 2 Sep 2026 19:01:31 -0400 Subject: [PATCH 34/34] chore(logging): default max log file size to 5 MB Lower ORCHESTRAD_LOG_MAX_SIZE_MB default from 100 to 5 so log files roll sooner by default; README updated. Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- backend/internal/config/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b8e32af..d5f9638 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ Standard log format: | Setting | Env var | Default | | --- | --- | --- | -| Max file size (MB) | `ORCHESTRAD_LOG_MAX_SIZE_MB` | `100` | +| Max file size (MB) | `ORCHESTRAD_LOG_MAX_SIZE_MB` | `5` | | Rotated copies kept | `ORCHESTRAD_LOG_MAX_BACKUPS` | `3` | | Max age (days) | `ORCHESTRAD_LOG_MAX_AGE_DAYS` | `30` | diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 3becb0f..a836ac8 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -117,7 +117,7 @@ func Load() (*Config, error) { Logging: LoggingConfig{ Level: getEnv("ORCHESTRAD_LOG_LEVEL", "info"), FilePath: filepath.Join(dataPath, "logs", "orchestrad.log"), - MaxSizeMB: getEnvInt("ORCHESTRAD_LOG_MAX_SIZE_MB", 100), + MaxSizeMB: getEnvInt("ORCHESTRAD_LOG_MAX_SIZE_MB", 5), MaxBackups: getEnvInt("ORCHESTRAD_LOG_MAX_BACKUPS", 3), MaxAgeDays: getEnvInt("ORCHESTRAD_LOG_MAX_AGE_DAYS", 30), Compress: true, -- 2.52.0