- {accountContextMenuIsVisible && (
+ {showMenu && (
{
if (githubAuthUrl) {
// Always start the OIDC flow, let the backend handle TOS check
@@ -56,6 +61,13 @@ export function AuthModal({
}
};
+ const handleEnterpriseSsoAuth = () => {
+ if (enterpriseSsoUrl) {
+ // Always start the OIDC flow, let the backend handle TOS check
+ window.location.href = enterpriseSsoUrl;
+ }
+ };
+
// Only show buttons if providers are configured and include the specific provider
const showGithub =
providersConfigured &&
@@ -69,6 +81,10 @@ export function AuthModal({
providersConfigured &&
providersConfigured.length > 0 &&
providersConfigured.includes("bitbucket");
+ const showEnterpriseSso =
+ providersConfigured &&
+ providersConfigured.length > 0 &&
+ providersConfigured.includes("enterprise_sso");
// Check if no providers are configured
const noProvidersConfigured =
@@ -126,6 +142,17 @@ export function AuthModal({
{t(I18nKey.BITBUCKET$CONNECT_TO_BITBUCKET)}
)}
+
+ {showEnterpriseSso && (
+
+ {t(I18nKey.ENTERPRISE_SSO$CONNECT_TO_ENTERPRISE_SSO)}
+
+ )}
>
)}
diff --git a/frontend/src/context/conversation-subscriptions-provider.tsx b/frontend/src/context/conversation-subscriptions-provider.tsx
index 93adad525b..36ebe3e190 100644
--- a/frontend/src/context/conversation-subscriptions-provider.tsx
+++ b/frontend/src/context/conversation-subscriptions-provider.tsx
@@ -31,7 +31,7 @@ interface ConversationSubscriptionsContextType {
subscribeToConversation: (options: {
conversationId: string;
sessionApiKey: string | null;
- providersSet: ("github" | "gitlab" | "bitbucket")[];
+ providersSet: ("github" | "gitlab" | "bitbucket" | "enterprise_sso")[];
baseUrl: string;
onEvent?: (event: unknown, conversationId: string) => void;
}) => void;
@@ -135,7 +135,7 @@ export function ConversationSubscriptionsProvider({
(options: {
conversationId: string;
sessionApiKey: string | null;
- providersSet: ("github" | "gitlab" | "bitbucket")[];
+ providersSet: ("github" | "gitlab" | "bitbucket" | "enterprise_sso")[];
baseUrl: string;
onEvent?: (event: unknown, conversationId: string) => void;
}) => {
@@ -226,6 +226,7 @@ export function ConversationSubscriptionsProvider({
});
socket.on("connect_error", (error) => {
+ // eslint-disable-next-line no-console
console.warn(
`Socket for conversation ${conversationId} CONNECTION ERROR:`,
error,
@@ -233,6 +234,7 @@ export function ConversationSubscriptionsProvider({
});
socket.on("disconnect", (reason) => {
+ // eslint-disable-next-line no-console
console.warn(
`Socket for conversation ${conversationId} DISCONNECTED! Reason:`,
reason,
diff --git a/frontend/src/hooks/query/use-get-secrets.ts b/frontend/src/hooks/query/use-get-secrets.ts
index e031222d58..05bae09ef4 100644
--- a/frontend/src/hooks/query/use-get-secrets.ts
+++ b/frontend/src/hooks/query/use-get-secrets.ts
@@ -1,17 +1,17 @@
import { useQuery } from "@tanstack/react-query";
import { SecretsService } from "#/api/secrets-service";
-import { useUserProviders } from "../use-user-providers";
import { useConfig } from "./use-config";
+import { useIsAuthed } from "#/hooks/query/use-is-authed";
export const useGetSecrets = () => {
const { data: config } = useConfig();
- const { providers } = useUserProviders();
+ const { data: isAuthed } = useIsAuthed();
const isOss = config?.APP_MODE === "oss";
return useQuery({
queryKey: ["secrets"],
queryFn: SecretsService.getSecrets,
- enabled: isOss || providers.length > 0,
+ enabled: isOss || isAuthed, // Enable regardless of providers
});
};
diff --git a/frontend/src/hooks/query/use-git-user.ts b/frontend/src/hooks/query/use-git-user.ts
index b1b60a40f2..dcb5750a6b 100644
--- a/frontend/src/hooks/query/use-git-user.ts
+++ b/frontend/src/hooks/query/use-git-user.ts
@@ -3,16 +3,16 @@ import React from "react";
import posthog from "posthog-js";
import { useConfig } from "./use-config";
import OpenHands from "#/api/open-hands";
-import { useUserProviders } from "../use-user-providers";
+import { useIsAuthed } from "#/hooks/query/use-is-authed";
export const useGitUser = () => {
- const { providers } = useUserProviders();
const { data: config } = useConfig();
+ const { data: isAuthed } = useIsAuthed();
const user = useQuery({
queryKey: ["user"],
queryFn: OpenHands.getGitUser,
- enabled: !!config?.APP_MODE && providers.length > 0,
+ enabled: !!config?.APP_MODE && isAuthed, // Enable regardless of providers
retry: false,
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 15, // 15 minutes
diff --git a/frontend/src/hooks/use-auto-login.ts b/frontend/src/hooks/use-auto-login.ts
index 2dfab7fd1f..83e4293b9a 100644
--- a/frontend/src/hooks/use-auto-login.ts
+++ b/frontend/src/hooks/use-auto-login.ts
@@ -31,6 +31,11 @@ export const useAutoLogin = () => {
identityProvider: "bitbucket",
});
+ const enterpriseSsoUrl = useAuthUrl({
+ appMode: config?.APP_MODE || null,
+ identityProvider: "enterprise_sso",
+ });
+
useEffect(() => {
// Only auto-login in SAAS mode
if (config?.APP_MODE !== "saas") {
@@ -60,6 +65,8 @@ export const useAutoLogin = () => {
authUrl = gitlabAuthUrl;
} else if (loginMethod === LoginMethod.BITBUCKET) {
authUrl = bitbucketAuthUrl;
+ } else if (loginMethod === LoginMethod.ENTERPRISE_SSO) {
+ authUrl = enterpriseSsoUrl;
}
// If we have an auth URL, redirect to it
@@ -80,5 +87,6 @@ export const useAutoLogin = () => {
githubAuthUrl,
gitlabAuthUrl,
bitbucketAuthUrl,
+ enterpriseSsoUrl,
]);
};
diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts
index 7c4b67ef6e..fe8421432c 100644
--- a/frontend/src/i18n/declaration.ts
+++ b/frontend/src/i18n/declaration.ts
@@ -559,6 +559,7 @@ export enum I18nKey {
GITHUB$CONNECT_TO_GITHUB = "GITHUB$CONNECT_TO_GITHUB",
GITLAB$CONNECT_TO_GITLAB = "GITLAB$CONNECT_TO_GITLAB",
BITBUCKET$CONNECT_TO_BITBUCKET = "BITBUCKET$CONNECT_TO_BITBUCKET",
+ ENTERPRISE_SSO$CONNECT_TO_ENTERPRISE_SSO = "ENTERPRISE_SSO$CONNECT_TO_ENTERPRISE_SSO",
AUTH$SIGN_IN_WITH_IDENTITY_PROVIDER = "AUTH$SIGN_IN_WITH_IDENTITY_PROVIDER",
WAITLIST$JOIN_WAITLIST = "WAITLIST$JOIN_WAITLIST",
ACCOUNT_SETTINGS$ADDITIONAL_SETTINGS = "ACCOUNT_SETTINGS$ADDITIONAL_SETTINGS",
diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json
index 99e0aa9dc3..6906782a63 100644
--- a/frontend/src/i18n/translation.json
+++ b/frontend/src/i18n/translation.json
@@ -8943,6 +8943,22 @@
"tr": "Bitbucket'a bağlan",
"uk": "Увійти за допомогою Bitbucket"
},
+ "ENTERPRISE_SSO$CONNECT_TO_ENTERPRISE_SSO": {
+ "en": "Login with Enterprise SSO",
+ "ja": "エンタープライズSSOでログイン",
+ "zh-CN": "使用企业SSO登录",
+ "zh-TW": "使用企業SSO登入",
+ "ko-KR": "엔터프라이즈 SSO로 로그인",
+ "de": "Mit Enterprise SSO anmelden",
+ "no": "Logg inn med Enterprise SSO",
+ "it": "Accedi con Enterprise SSO",
+ "pt": "Entrar com Enterprise SSO",
+ "es": "Iniciar sesión con Enterprise SSO",
+ "ar": "تسجيل الدخول باستخدام Enterprise SSO",
+ "fr": "Se connecter avec Enterprise SSO",
+ "tr": "Enterprise SSO ile giriş yap",
+ "uk": "Увійти за допомогою Enterprise SSO"
+ },
"AUTH$SIGN_IN_WITH_IDENTITY_PROVIDER": {
"en": "Log in to OpenHands",
"ja": "IDプロバイダーでサインイン",
diff --git a/frontend/src/types/settings.ts b/frontend/src/types/settings.ts
index a083429b38..03f72a4a85 100644
--- a/frontend/src/types/settings.ts
+++ b/frontend/src/types/settings.ts
@@ -2,6 +2,7 @@ export const ProviderOptions = {
github: "github",
gitlab: "gitlab",
bitbucket: "bitbucket",
+ enterprise_sso: "enterprise_sso",
} as const;
export type Provider = keyof typeof ProviderOptions;
diff --git a/frontend/src/utils/local-storage.ts b/frontend/src/utils/local-storage.ts
index d6ab3e29d2..ffdf14a164 100644
--- a/frontend/src/utils/local-storage.ts
+++ b/frontend/src/utils/local-storage.ts
@@ -8,6 +8,7 @@ export enum LoginMethod {
GITHUB = "github",
GITLAB = "gitlab",
BITBUCKET = "bitbucket",
+ ENTERPRISE_SSO = "enterprise_sso",
}
/**
diff --git a/openhands/integrations/service_types.py b/openhands/integrations/service_types.py
index 75a08538d5..8bde8822a9 100644
--- a/openhands/integrations/service_types.py
+++ b/openhands/integrations/service_types.py
@@ -18,6 +18,7 @@ class ProviderType(Enum):
GITHUB = 'github'
GITLAB = 'gitlab'
BITBUCKET = 'bitbucket'
+ ENTERPRISE_SSO = 'enterprise_sso'
class TaskType(str, Enum):
diff --git a/openhands/server/app.py b/openhands/server/app.py
index f1c0ad7073..bae0e70fde 100644
--- a/openhands/server/app.py
+++ b/openhands/server/app.py
@@ -28,7 +28,8 @@ from openhands.server.routes.secrets import app as secrets_router
from openhands.server.routes.security import app as security_api_router
from openhands.server.routes.settings import app as settings_router
from openhands.server.routes.trajectory import app as trajectory_router
-from openhands.server.shared import conversation_manager
+from openhands.server.shared import conversation_manager, server_config
+from openhands.server.types import AppMode
mcp_app = mcp_server.http_app(path='/mcp')
@@ -68,6 +69,7 @@ app.include_router(conversation_api_router)
app.include_router(manage_conversation_api_router)
app.include_router(settings_router)
app.include_router(secrets_router)
-app.include_router(git_api_router)
+if server_config.app_mode == AppMode.OSS:
+ app.include_router(git_api_router)
app.include_router(trajectory_router)
add_health_endpoints(app)
diff --git a/poetry.lock b/poetry.lock
index d1ca9adaa7..dfdc5b3a40 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "aiofiles"
@@ -3770,6 +3770,22 @@ http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
+[[package]]
+name = "httpx-aiohttp"
+version = "0.1.8"
+description = "Aiohttp transport for HTTPX"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "httpx_aiohttp-0.1.8-py3-none-any.whl", hash = "sha256:b7bd958d1331f3759a38a0ba22ad29832cb63ca69498c17735228055bf78fa7e"},
+ {file = "httpx_aiohttp-0.1.8.tar.gz", hash = "sha256:756c5e74cdb568c3248ba63fe82bfe8bbe64b928728720f7eaac64b3cf46f308"},
+]
+
+[package.dependencies]
+aiohttp = ">=3.10.0,<4"
+httpx = ">=0.27.0"
+
[[package]]
name = "httpx-sse"
version = "0.4.0"
@@ -5136,11 +5152,8 @@ files = [
{file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"},
{file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"},
- {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"},
- {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"},
{file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"},
- {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"},
{file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"},
{file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"},
{file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"},
@@ -11753,4 +11766,4 @@ third-party-runtimes = ["daytona", "e2b", "modal", "runloop-api-client"]
[metadata]
lock-version = "2.1"
python-versions = "^3.12,<3.14"
-content-hash = "d957f92f0d194e78b1cbf4b5a31c28df83e34e508d2c9810de96204db8e8f158"
+content-hash = "8568c6ec2e11d4fcb23e206a24896b4d2d50e694c04011b668148f484e95b406"
diff --git a/pyproject.toml b/pyproject.toml
index 71f041b094..c18b3d20a4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -99,6 +99,7 @@ e2b = { version = ">=1.0.5,<1.8.0", optional = true }
modal = { version = ">=0.66.26,<1.2.0", optional = true }
runloop-api-client = { version = "0.50.0", optional = true }
daytona = { version = "0.24.2", optional = true }
+httpx-aiohttp = "^0.1.8"
[tool.poetry.extras]
third_party_runtimes = [ "e2b", "modal", "runloop-api-client", "daytona" ]