feat: add Keycloak authentication for dashboard and API endpoints (#2)
Dashboard: OAuth2/OIDC login flow with PKCE, session-based auth middleware protecting all server function endpoints, check-auth server function for frontend auth state, login page gate in AppShell, user info in sidebar. Agent API: JWT validation middleware using Keycloak JWKS endpoint, conditionally enabled when KEYCLOAK_URL and KEYCLOAK_REALM are set. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com> Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
@@ -1,9 +1,15 @@
|
||||
use axum::routing::get;
|
||||
use axum::{middleware, Extension};
|
||||
use dioxus::prelude::*;
|
||||
use time::Duration;
|
||||
use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer};
|
||||
|
||||
use super::config;
|
||||
use super::database::Database;
|
||||
use super::error::DashboardError;
|
||||
use super::keycloak_config::KeycloakConfig;
|
||||
use super::server_state::{ServerState, ServerStateInner};
|
||||
use super::{auth_callback, auth_login, logout, require_auth, PendingOAuthStore};
|
||||
|
||||
pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> {
|
||||
tokio::runtime::Runtime::new()
|
||||
@@ -12,15 +18,29 @@ pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
let config = config::load_config()?;
|
||||
let keycloak: &'static KeycloakConfig =
|
||||
Box::leak(Box::new(KeycloakConfig::from_env()?));
|
||||
let db = Database::connect(&config.mongodb_uri, &config.mongodb_database).await?;
|
||||
|
||||
tracing::info!("Keycloak configured for realm '{}'", keycloak.realm);
|
||||
|
||||
let server_state: ServerState = ServerStateInner {
|
||||
agent_api_url: config.agent_api_url.clone(),
|
||||
db,
|
||||
config,
|
||||
keycloak,
|
||||
}
|
||||
.into();
|
||||
|
||||
// Session layer
|
||||
let key = Key::generate();
|
||||
let store = MemoryStore::default();
|
||||
let session = SessionManagerLayer::new(store)
|
||||
.with_secure(false)
|
||||
.with_same_site(tower_sessions::cookie::SameSite::Lax)
|
||||
.with_expiry(tower_sessions::Expiry::OnInactivity(Duration::hours(24)))
|
||||
.with_signed(key);
|
||||
|
||||
let addr = dioxus_cli_config::fullstack_address_or_localhost();
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
@@ -29,8 +49,14 @@ pub fn server_start(app: fn() -> Element) -> Result<(), DashboardError> {
|
||||
tracing::info!("Dashboard server listening on {addr}");
|
||||
|
||||
let router = axum::Router::new()
|
||||
.route("/auth", get(auth_login))
|
||||
.route("/auth/callback", get(auth_callback))
|
||||
.route("/logout", get(logout))
|
||||
.serve_dioxus_application(ServeConfig::new(), app)
|
||||
.layer(axum::Extension(server_state));
|
||||
.layer(Extension(PendingOAuthStore::default()))
|
||||
.layer(Extension(server_state))
|
||||
.layer(middleware::from_fn(require_auth))
|
||||
.layer(session);
|
||||
|
||||
axum::serve(listener, router.into_make_service())
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user