Files
harmony/fleet/harmony-fleet-operator/Cargo.toml
Reda Tarzalt 96e7d43b2f
Some checks failed
Run Check Script / check (pull_request) Failing after 38s
add auth to frontend through lib (#284)
Adds OIDC login support to the harmony-fleet-operator web dashboard using Zitadel SSO.

pkce was the recommended option for this since we don't need to hold on to any secret. We compute a value on server before sending the data to Zitadel who validates authenticity by recomputing the hash and comparing the two values.

pkce Auth flow

 1. User visits a protected dashboard route, like /devices.
 2. If no valid harmony_fleet_session cookie exists, the app redirects to /login.
 3. /login creates:
     - random state
     - random pkce_code_verifier
     - derived code_challenge = base64url(sha256(pkce_code_verifier))
 4. The app stores state and pkce_code_verifier in a temporary HTTP-only login-attempt cookie.
 5. The browser is redirected to Zitadel’s authorize endpoint with:
     - client_id
     - redirect_uri
     - scope
     - state
     - code_challenge
     - code_challenge_method=S256
 6. After SSO login, Zitadel redirects back to /auth/callback?code=...&state=....
 7. The callback handler:
     - parses the raw query into a strict success/failure enum
     - reads the temporary login-attempt cookie
     - validates returned state
     - exchanges code + pkce_code_verifier for tokens
     - validates the returned ID token using OIDC discovery/JWKS
     - creates a local harmony_fleet_session cookie
     - redirects to /
 8. Protected routes validate the local dashboard session cookie on each request.
 9. /logout clears the dashboard session cookie and redirects to /login.

---

 Auth middleware responses depending on request type:

 - normal browser request: redirect to /login
 - SSE request: 401 authentication required
 - HTMX request: 401 with HX-Redirect: /login (HTMX redirect is more idiomatic than through Axum for this)

Reviewed-on: #284
Reviewed-by: johnride <jg@nationtech.io>
Co-authored-by: Reda Tarzalt <tarzaltreda@gmail.com>
Co-committed-by: Reda Tarzalt <tarzaltreda@gmail.com>
2026-05-19 20:37:08 +00:00

46 lines
1.6 KiB
TOML

[package]
name = "harmony-fleet-operator"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
build = "build.rs"
[features]
default = []
# Server-side dashboard (axum + Maud + HTMX). Tailwind CSS is embedded at
# build time when the standalone `tailwindcss` CLI is on PATH; otherwise
# the bundled CSS is empty and `--css-from <path>` must be used at runtime
# (the sidecar-watch dev workflow does this).
web-frontend = ["dep:axum", "dep:axum-extra", "dep:maud", "dep:tokio-stream", "harmony_zitadel_auth/axum"]
[dependencies]
harmony = { path = "../../harmony", features = ["podman"] }
harmony-fleet-auth = { path = "../harmony-fleet-auth" }
harmony-reconciler-contracts = { path = "../../harmony-reconciler-contracts" }
harmony_zitadel_auth = { path = "../../harmony_zitadel_auth" }
toml = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
kube = { workspace = true, features = ["runtime", "derive"] }
k8s-openapi.workspace = true
async-nats = { workspace = true }
serde.workspace = true
serde_json.workspace = true
schemars = "0.8.22"
tokio.workspace = true
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
anyhow.workspace = true
clap.workspace = true
futures-util = { workspace = true }
thiserror.workspace = true
async-trait.workspace = true
url.workspace = true
base64.workspace = true
reqwest.workspace = true
axum = { version = "0.8", optional = true }
axum-extra = { version = "0.10", features = ["cookie", "cookie-private"], optional = true }
maud = { version = "0.27", features = ["axum"], optional = true }
tokio-stream = { version = "0.1", optional = true }
dotenvy = "0.15"