From bbf33a4f60f12a56aaaa8b33d22b21700685a2cb Mon Sep 17 00:00:00 2001 From: James Brunton Date: Tue, 16 Dec 2025 14:27:39 +0000 Subject: [PATCH] Initial commit of self-hosted SSO --- .../SetupWizard/DesktopOAuthButtons.tsx | 59 ++++++++++++-- .../SetupWizard/SaaSLoginScreen.tsx | 5 +- .../SetupWizard/SelfHostedLoginScreen.tsx | 79 ++++++++++++++++++- .../SetupWizard/ServerSelection.tsx | 21 +++-- frontend/src/desktop/services/authService.ts | 18 +++++ .../desktop/services/connectionModeService.ts | 6 +- .../src/proprietary/routes/AuthCallback.tsx | 60 +++++++++++--- 7 files changed, 217 insertions(+), 31 deletions(-) diff --git a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx index 49b55a3869..d26711df47 100644 --- a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx +++ b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx @@ -7,12 +7,24 @@ import '@app/routes/authShared/auth.css'; export type OAuthProvider = 'google' | 'github' | 'keycloak' | 'azure' | 'apple' | 'oidc'; +export interface DesktopOAuthProvider { + id: string; + label?: string; + file?: string; + url?: string; +} + interface DesktopOAuthButtonsProps { onOAuthSuccess: (userInfo: UserInfo) => Promise; onError: (error: string) => void; isDisabled: boolean; serverUrl: string; - providers: OAuthProvider[]; + providers: DesktopOAuthProvider[]; + /** + * Optional override to handle provider click (used for self-hosted SSO flow). + * When provided, the caller is responsible for invoking onOAuthSuccess/onError. + */ + onProviderClick?: (provider: DesktopOAuthProvider) => Promise; } export const DesktopOAuthButtons: React.FC = ({ @@ -21,6 +33,7 @@ export const DesktopOAuthButtons: React.FC = ({ isDisabled, serverUrl, providers, + onProviderClick, }) => { const { t } = useTranslation(); const [oauthLoading, setOauthLoading] = useState(false); @@ -64,7 +77,25 @@ export const DesktopOAuthButtons: React.FC = ({ } }; - const providerConfig: Record = { + const handleCustomClick = async (provider: DesktopOAuthProvider) => { + if (!onProviderClick) { + return; + } + + try { + setOauthLoading(true); + await onProviderClick(provider); + } catch (error) { + const errorMessage = error instanceof Error + ? error.message + : t('setup.login.error.oauthFailed', 'OAuth login failed. Please try again.'); + onError(errorMessage); + } finally { + setOauthLoading(false); + } + }; + + const providerConfig: Record = { google: { label: 'Google', file: 'google.svg' }, github: { label: 'GitHub', file: 'github.svg' }, keycloak: { label: 'Keycloak', file: 'keycloak.svg' }, @@ -80,19 +111,31 @@ export const DesktopOAuthButtons: React.FC = ({ return (
{providers - .filter((providerId) => providerId in providerConfig) - .map((providerId) => { - const provider = providerConfig[providerId]; + .map((providerMeta) => { + const provider = + (providerMeta.id in providerConfig + ? providerConfig[providerMeta.id as OAuthProvider] + : { + label: providerMeta.label || providerMeta.id, + file: providerMeta.file || 'oidc.svg', + }); + return (