From 7b2472fd9535a79295a47b3524bebc49d1799425 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 23 Feb 2026 13:37:17 +0100 Subject: [PATCH 1/5] feat(chat): replace built-in chat with LibreChat SSO integration Replaces the custom chat page with an external LibreChat instance that shares Keycloak SSO for seamless auto-login. Removes Tools and Knowledge Base pages as these are now handled by LibreChat's built-in capabilities. - Add LibreChat service to docker-compose with Ollama backend config - Add Keycloak OIDC client (certifai-librechat) with prompt=none for silent SSO - Create librechat.yaml with CERTifAI branding, Ollama endpoint, and custom page title/logo - Change sidebar Chat link to external URL (opens LibreChat in new tab) - Remove chat page, tools page, knowledge base page and all related components (chat_sidebar, chat_bubble, chat_input_bar, etc.) - Remove tool_card, file_row components and tool/knowledge models - Remove chat_stream SSE handler (no longer needed) - Clean up i18n files: remove chat, tools, knowledge sections - Dashboard article summarization via Ollama remains intact Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5503a04..1d8b2ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,4 +95,4 @@ services: - librechat-data:/app/data volumes: - librechat-data: \ No newline at end of file + librechat-data: -- 2.49.1 From 9aa79154151ccca4274684b921d9ff2517d7d3f0 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 23 Feb 2026 21:51:46 +0100 Subject: [PATCH 2/5] fix(librechat): remove prompt=none for local dev compatibility prompt=none causes silent failure when no Keycloak session exists yet. Standard OIDC flow still provides seamless SSO when the user has an active Keycloak session from the dashboard. Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1d8b2ef..d13949e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,7 +70,6 @@ services: OPENID_CALLBACK_URL: /oauth/openid/callback OPENID_SCOPE: openid profile email OPENID_BUTTON_LABEL: Login with CERTifAI - OPENID_AUTH_EXTRA_PARAMS: prompt=none # Disable local auth (SSO only) ALLOW_EMAIL_LOGIN: "false" ALLOW_REGISTRATION: "false" -- 2.49.1 From 7f13273dedf73f5f6f9957bf871471727324076d Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Mon, 23 Feb 2026 22:27:07 +0100 Subject: [PATCH 3/5] feat(librechat): add OIDC HTTP patch and prompt=none for seamless SSO Switch to host networking so LibreChat can reach Keycloak on localhost. Patch openidStrategy.js to allow HTTP OIDC issuers for local dev (openid-client v6 enforces HTTPS by default). Add support for OPENID_AUTH_EXTRA_PARAMS env var and set prompt=none for automatic SSO login when a Keycloak session exists. Co-Authored-By: Claude Opus 4.6 --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index d13949e..1d8b2ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,6 +70,7 @@ services: OPENID_CALLBACK_URL: /oauth/openid/callback OPENID_SCOPE: openid profile email OPENID_BUTTON_LABEL: Login with CERTifAI + OPENID_AUTH_EXTRA_PARAMS: prompt=none # Disable local auth (SSO only) ALLOW_EMAIL_LOGIN: "false" ALLOW_REGISTRATION: "false" -- 2.49.1 From 5c7ab71edc55e1e41f37d86741d04cf5e5ec3e36 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 24 Feb 2026 12:32:57 +0100 Subject: [PATCH 4/5] feat(sidebar): read LibreChat URL from LIBRECHAT_URL env var Replace hardcoded localhost:3080 chat link with configurable LIBRECHAT_URL environment variable, passed through AuthInfo to the sidebar component. Co-Authored-By: Claude Opus 4.6 --- src/components/app_shell.rs | 1 + src/components/sidebar.rs | 7 ++++--- src/infrastructure/auth_check.rs | 19 ++++++++++++------- src/models/user.rs | 2 ++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/components/app_shell.rs b/src/components/app_shell.rs index c129558..37e084b 100644 --- a/src/components/app_shell.rs +++ b/src/components/app_shell.rs @@ -65,6 +65,7 @@ pub fn AppShell() -> Element { email: info.email, name: info.name, avatar_url: info.avatar_url, + librechat_url: info.librechat_url, class: sidebar_cls, on_nav: move |_| mobile_menu_open.set(false), } diff --git a/src/components/sidebar.rs b/src/components/sidebar.rs index 2327f16..d8beba0 100644 --- a/src/components/sidebar.rs +++ b/src/components/sidebar.rs @@ -13,7 +13,7 @@ enum NavTarget { /// Internal Dioxus route (rendered as `Link { to: route }`). Internal(Route), /// External URL opened in a new tab (rendered as ``). - External(&'static str), + External(String), } /// Navigation entry for the sidebar. @@ -43,6 +43,7 @@ pub fn Sidebar( name: String, email: String, avatar_url: String, + #[props(default = "http://localhost:3080".to_string())] librechat_url: String, #[props(default = "sidebar".to_string())] class: String, #[props(default)] on_nav: EventHandler<()>, ) -> Element { @@ -66,7 +67,7 @@ pub fn Sidebar( key: "chat", label: t(locale_val, "nav.chat"), // Opens LibreChat in a new tab; SSO via shared Keycloak realm. - target: NavTarget::External("http://localhost:3080"), + target: NavTarget::External(librechat_url.clone()), icon: rsx! { Icon { icon: BsChatDots, width: 18, height: 18 } }, }, NavItem { @@ -124,7 +125,7 @@ pub fn Sidebar( } } NavTarget::External(url) => { - let url = *url; + let url = url.clone(); rsx! { a { href: url, diff --git a/src/infrastructure/auth_check.rs b/src/infrastructure/auth_check.rs index 00b6c6c..1d8e46d 100644 --- a/src/infrastructure/auth_check.rs +++ b/src/infrastructure/auth_check.rs @@ -24,13 +24,18 @@ pub async fn check_auth() -> Result { .map_err(|e| ServerFnError::new(format!("session read failed: {e}")))?; match user_state { - Some(u) => Ok(AuthInfo { - authenticated: true, - sub: u.sub, - email: u.user.email, - name: u.user.name, - avatar_url: u.user.avatar_url, - }), + Some(u) => { + let librechat_url = std::env::var("LIBRECHAT_URL") + .unwrap_or_else(|_| "http://localhost:3080".into()); + Ok(AuthInfo { + authenticated: true, + sub: u.sub, + email: u.user.email, + name: u.user.name, + avatar_url: u.user.avatar_url, + librechat_url, + }) + } None => Ok(AuthInfo::default()), } } diff --git a/src/models/user.rs b/src/models/user.rs index e1f1883..a3367bd 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -22,6 +22,8 @@ pub struct AuthInfo { pub name: String, /// Avatar URL (from Keycloak picture claim) pub avatar_url: String, + /// LibreChat instance URL for the sidebar chat link + pub librechat_url: String, } /// Per-user LLM provider configuration stored in MongoDB. -- 2.49.1 From 0d86a34ab18c7e7c174dea98a5b9a016aba4ad17 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar Date: Tue, 24 Feb 2026 12:42:34 +0100 Subject: [PATCH 5/5] fix(fmt): fmt --- src/infrastructure/auth_check.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/auth_check.rs b/src/infrastructure/auth_check.rs index 1d8e46d..6bbb8d8 100644 --- a/src/infrastructure/auth_check.rs +++ b/src/infrastructure/auth_check.rs @@ -25,8 +25,8 @@ pub async fn check_auth() -> Result { match user_state { Some(u) => { - let librechat_url = std::env::var("LIBRECHAT_URL") - .unwrap_or_else(|_| "http://localhost:3080".into()); + let librechat_url = + std::env::var("LIBRECHAT_URL").unwrap_or_else(|_| "http://localhost:3080".into()); Ok(AuthInfo { authenticated: true, sub: u.sub, -- 2.49.1