diff --git a/web/src/api/users/api.ts b/web/src/api/users/api.ts index 0876ffe..581921c 100644 --- a/web/src/api/users/api.ts +++ b/web/src/api/users/api.ts @@ -103,6 +103,8 @@ export interface User { account_permissions: Record created_at: number; updated_at: number; + sso_id?: string | null; + sso_provider?: string | null; } type Theme = 'dark' | 'light' diff --git a/web/src/components/sign-out-dialog.tsx b/web/src/components/sign-out-dialog.tsx index f9812be..cb359c3 100644 --- a/web/src/components/sign-out-dialog.tsx +++ b/web/src/components/sign-out-dialog.tsx @@ -21,6 +21,9 @@ import { useNavigate, useLocation } from '@tanstack/react-router' import { ConfirmDialog } from '@/components/confirm-dialog' import { resetToken } from '@/stores/authStore' import { useTranslation } from 'react-i18next' +import { useCurrentUser } from '@/hooks/use-current-user' +import { useEdition } from '@/hooks/use-edition' +import { useState } from 'react' interface SignOutDialogProps { open: boolean @@ -31,9 +34,17 @@ export function SignOutDialog({ open, onOpenChange }: SignOutDialogProps) { const navigate = useNavigate() const location = useLocation() const { t } = useTranslation() - const handleSignOut = () => { - resetToken() - const currentPath = location.href + const { user } = useCurrentUser() + const { isPro, features } = useEdition() + const [isLoading, setIsLoading] = useState(false) + + const isSsoUser = + isPro && + features.includes('sso') && + !!user?.sso_provider && + user.sso_provider !== '' + + const goToSignIn = (currentPath: string) => { navigate({ to: '/sign-in', search: { redirect: currentPath }, @@ -41,19 +52,73 @@ export function SignOutDialog({ open, onOpenChange }: SignOutDialogProps) { }) } + const localSignOut = () => { + resetToken() + goToSignIn(location.href) + } + + const handleConfirm = () => { + if (!isSsoUser) { + localSignOut() + return + } + setIsLoading(true) + // Only sign out of bichon; keep the SSO session for one-click sign-in. + fetch('/api/auth/oidc/local-logout', { redirect: 'follow' }) + .catch(() => {}) + .finally(() => { + setIsLoading(false) + localSignOut() + }) + } + return ( + > + {isSsoUser && ( +
+ +

+ {t( + 'sign_out.sso_warning', + 'This will end your SSO session (e.g. Keycloak) and sign you out of all applications using it.' + )} +

+
+ )} +
) } diff --git a/web/src/features/auth/user-auth-form.tsx b/web/src/features/auth/user-auth-form.tsx index 3b2e340..7cd77db 100644 --- a/web/src/features/auth/user-auth-form.tsx +++ b/web/src/features/auth/user-auth-form.tsx @@ -53,11 +53,12 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { const { setTheme } = useTheme(); const navigate = useNavigate() const { t } = useTranslation() - const { isPro } = useEdition() - const { search } = useLocation(); const redirect = toSearchParams(search).get('redirect') || '/'; + const { isPro, features } = useEdition() + const ssoEnabled = isPro && features.includes('sso') + const formSchema = getFormSchema(t) const form = useForm({ resolver: zodResolver(formSchema), @@ -159,7 +160,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) { {t('auth.login')} - {isPro && ( + {ssoEnabled && (