diff --git a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx index a7c5d5c34..df66ab13b 100644 --- a/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx +++ b/frontend/src/desktop/components/SetupWizard/DesktopOAuthButtons.tsx @@ -4,7 +4,7 @@ import { authService, UserInfo } from '@app/services/authService'; import { buildOAuthCallbackHtml } from '@app/utils/oauthCallbackHtml'; import { BASE_PATH } from '@app/constants/app'; import { STIRLING_SAAS_URL } from '@app/constants/connection'; -import '@app/routes/authShared/auth.css'; +import '@app/components/SetupWizard/desktopOAuth.css'; type KnownProviderId = 'google' | 'github' | 'keycloak' | 'azure' | 'apple' | 'oidc'; export type OAuthProviderId = KnownProviderId | string; @@ -95,8 +95,9 @@ export const DesktopOAuthButtons: React.FC = ({ return null; } + // Desktop always uses its own styling classes (independent of web) return ( -
+
{providers .filter((providerConfigEntry) => providerConfigEntry && providerConfigEntry.id) .map((providerEntry) => { @@ -114,15 +115,19 @@ export const DesktopOAuthButtons: React.FC = ({ key={providerEntry.id} onClick={() => handleOAuthLogin(providerEntry)} disabled={isDisabled || oauthLoading} - className="oauth-button-vertical" + className="oauth-button-vertical-desktop" title={label} > - {label} - {label} + + + {label} + + {label} + ); })} diff --git a/frontend/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx b/frontend/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx index dd0731eb2..9253a80f5 100644 --- a/frontend/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx +++ b/frontend/src/desktop/components/SetupWizard/SelfHostedLoginScreen.tsx @@ -13,6 +13,7 @@ import '@app/routes/authShared/auth.css'; interface SelfHostedLoginScreenProps { serverUrl: string; enabledOAuthProviders?: SSOProviderConfig[]; + loginMethod?: string; onLogin: (username: string, password: string) => Promise; onOAuthSuccess: (userInfo: UserInfo) => Promise; mfaCode: string; @@ -25,6 +26,7 @@ interface SelfHostedLoginScreenProps { export const SelfHostedLoginScreen: React.FC = ({ serverUrl, enabledOAuthProviders, + loginMethod = 'all', onLogin, onOAuthSuccess, mfaCode, @@ -38,6 +40,9 @@ export const SelfHostedLoginScreen: React.FC = ({ const [password, setPassword] = useState(''); const [validationError, setValidationError] = useState(null); + // Check if username/password authentication is allowed + const isUserPassAllowed = loginMethod === 'all' || loginMethod === 'normal'; + const handleSubmit = async () => { // Validation if (!username.trim()) { @@ -69,7 +74,7 @@ export const SelfHostedLoginScreen: React.FC = ({ <> @@ -90,36 +95,42 @@ export const SelfHostedLoginScreen: React.FC = ({ providers={enabledOAuthProviders} /> - + {/* Only show divider if username/password auth is also allowed */} + {isUserPassAllowed && ( + + )} )} - { - setUsername(value); - setValidationError(null); - }} - setPassword={(value) => { - setPassword(value); - setValidationError(null); - }} - mfaCode={mfaCode} - setMfaCode={(value) => { - setMfaCode(value); - setValidationError(null); - }} - showMfaField={requiresMfa || Boolean(mfaCode)} - requiresMfa={requiresMfa} - onSubmit={handleSubmit} - isSubmitting={loading} - submitButtonText={t('setup.login.submit', 'Login')} - /> + {/* Only show email/password form if username/password auth is allowed */} + {isUserPassAllowed && ( + { + setUsername(value); + setValidationError(null); + }} + setPassword={(value) => { + setPassword(value); + setValidationError(null); + }} + mfaCode={mfaCode} + setMfaCode={(value) => { + setMfaCode(value); + setValidationError(null); + }} + showMfaField={requiresMfa || Boolean(mfaCode)} + requiresMfa={requiresMfa} + onSubmit={handleSubmit} + isSubmitting={loading} + submitButtonText={t('setup.login.submit', 'Login')} + /> + )} ); }; diff --git a/frontend/src/desktop/components/SetupWizard/ServerSelection.tsx b/frontend/src/desktop/components/SetupWizard/ServerSelection.tsx index 497875091..90ce22f1d 100644 --- a/frontend/src/desktop/components/SetupWizard/ServerSelection.tsx +++ b/frontend/src/desktop/components/SetupWizard/ServerSelection.tsx @@ -73,6 +73,7 @@ export const ServerSelection: React.FC = ({ onSelect, load // Fetch OAuth providers and check if login is enabled const enabledProviders: SSOProviderConfig[] = []; + let loginMethod = 'all'; // Default to 'all' (allows both SSO and username/password) try { console.log('[ServerSelection] Fetching login configuration...'); const response = await fetch(`${url}/api/v1/proprietary/ui-data/login`); @@ -108,6 +109,10 @@ export const ServerSelection: React.FC = ({ onSelect, load return; } + // Extract loginMethod from response + loginMethod = data.loginMethod || 'all'; + console.log('[ServerSelection] Login method:', loginMethod); + // Extract provider IDs from authorization URLs // Example: "/oauth2/authorization/google" → "google" const providerEntries = Object.entries(data.providerList || {}); @@ -149,11 +154,12 @@ export const ServerSelection: React.FC = ({ onSelect, load return; } - // Connection successful - pass URL and OAuth providers + // Connection successful - pass URL, OAuth providers, and login method console.log('[ServerSelection] ✅ Server selection complete, proceeding to login'); onSelect({ url, enabledOAuthProviders: enabledProviders.length > 0 ? enabledProviders : undefined, + loginMethod, }); } catch (error) { console.error('[ServerSelection] ❌ Unexpected error during connection test:', error); diff --git a/frontend/src/desktop/components/SetupWizard/desktopOAuth.css b/frontend/src/desktop/components/SetupWizard/desktopOAuth.css new file mode 100644 index 000000000..20cea0c5c --- /dev/null +++ b/frontend/src/desktop/components/SetupWizard/desktopOAuth.css @@ -0,0 +1,96 @@ +/* Desktop-specific OAuth button styles for self-hosted server connections */ +/* These styles are isolated from the web SSO buttons to prevent conflicts */ + +.oauth-container-vertical-desktop { + display: flex; + flex-direction: column; + gap: 0.875rem; /* 14px */ + align-items: stretch; +} + +.oauth-button-vertical-desktop { + width: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 0.75rem 1rem; /* 12px 16px */ + border: 1px solid #d1d5db; + border-radius: 0.75rem; /* 12px */ + background-color: var(--auth-card-bg-light-only); + font-size: 1rem; /* 16px */ + font-weight: 500; + color: var(--auth-text-primary-light-only); + cursor: pointer; + gap: 0.75rem; /* 12px */ + font-family: inherit; + transition: background-color 0.2s ease; +} + +.oauth-button-vertical-desktop:hover:not(:disabled) { + background-color: #f3f4f6; +} + +.oauth-button-vertical-desktop:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.oauth-button-vertical-desktop:focus-visible { + outline: 2px solid var(--auth-border-focus-light-only); + outline-offset: 2px; +} + +/* Fix Mantine Button internal spans */ +.oauth-button-vertical-desktop .mantine-Button-inner { + width: 100%; + display: flex; + align-items: center; + justify-content: flex-start; +} + +.oauth-button-vertical-desktop .mantine-Button-label { + width: 100%; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.75rem; + overflow: visible; +} + +.oauth-button-left-desktop { + display: flex; + align-items: center; + gap: 0.75rem; + min-width: 0; + flex: 1 1 auto; +} + +.oauth-button-text-desktop { + font-size: 1rem; + font-weight: 500; + color: inherit; + line-height: 1.2; + display: inline-flex; + align-items: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.oauth-icon-wrapper-desktop { + width: 1.25rem; /* 20px */ + height: 1.25rem; /* 20px */ + border-radius: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; +} + +.oauth-icon-tiny-desktop { + width: 1.25rem; /* 20px */ + height: 1.25rem; /* 20px */ + display: block; + flex-shrink: 0; +} diff --git a/frontend/src/desktop/components/SetupWizard/index.tsx b/frontend/src/desktop/components/SetupWizard/index.tsx index cbe79a4aa..e8b435cc8 100644 --- a/frontend/src/desktop/components/SetupWizard/index.tsx +++ b/frontend/src/desktop/components/SetupWizard/index.tsx @@ -327,6 +327,7 @@ export const SetupWizard: React.FC = ({ onComplete }) => {