From 039de42345a996746abd22d5802be007b106f039 Mon Sep 17 00:00:00 2001 From: Tiago Farto Date: Fri, 15 May 2026 11:55:14 +0000 Subject: [PATCH] chore: update sso deletion backport --- .../profile/components/DeleteAccount.tsx | 3 +++ .../settings/(account)/profile/page.tsx | 8 +++++++- .../components/DeleteAccountModal/index.tsx | 17 ++++++++++++++--- .../account/lib/account-deletion-sso-reauth.ts | 15 +++------------ .../components/removed-from-organization.tsx | 3 +++ .../modules/setup/organization/create/page.tsx | 3 ++- 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx index 85545a817a..c17fc8c7e2 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/components/DeleteAccount.tsx @@ -22,6 +22,7 @@ interface DeleteAccountProps { accountDeletionError?: string | string[]; isMultiOrgEnabled: boolean; requiresPasswordConfirmation: boolean; + isSsoIdentityConfirmationDisabled: boolean; } export const DeleteAccount = ({ @@ -32,6 +33,7 @@ export const DeleteAccount = ({ accountDeletionError, isMultiOrgEnabled, requiresPasswordConfirmation, + isSsoIdentityConfirmationDisabled, }: Readonly) => { const [isModalOpen, setModalOpen] = useState(false); const isDeleteDisabled = !isMultiOrgEnabled && organizationsWithSingleOwner.length > 0; @@ -73,6 +75,7 @@ export const DeleteAccount = ({ user={user} isFormbricksCloud={IS_FORMBRICKS_CLOUD} organizationsWithSingleOwner={organizationsWithSingleOwner} + isSsoIdentityConfirmationDisabled={isSsoIdentityConfirmationDisabled} />

{t("environments.settings.profile.warning_cannot_undo")} diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx index 2e0c019805..999a31b201 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/page.tsx @@ -1,7 +1,12 @@ import { AuthenticationError } from "@formbricks/types/errors"; import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar"; import { AccountSecurity } from "@/app/(app)/environments/[environmentId]/settings/(account)/profile/components/AccountSecurity"; -import { EMAIL_VERIFICATION_DISABLED, IS_FORMBRICKS_CLOUD, PASSWORD_RESET_DISABLED } from "@/lib/constants"; +import { + DISABLE_ACCOUNT_DELETION_SSO_CONFIRMATION, + EMAIL_VERIFICATION_DISABLED, + IS_FORMBRICKS_CLOUD, + PASSWORD_RESET_DISABLED, +} from "@/lib/constants"; import { getOrganizationsWhereUserIsSingleOwner } from "@/lib/organization/service"; import { getUser } from "@/lib/user/service"; import { getTranslate } from "@/lingodotdev/server"; @@ -98,6 +103,7 @@ const Page = async (props: { isMultiOrgEnabled={isMultiOrgEnabled} accountDeletionError={searchParams.accountDeletionError} requiresPasswordConfirmation={requiresPasswordConfirmation} + isSsoIdentityConfirmationDisabled={DISABLE_ACCOUNT_DELETION_SSO_CONFIRMATION} /> diff --git a/apps/web/modules/account/components/DeleteAccountModal/index.tsx b/apps/web/modules/account/components/DeleteAccountModal/index.tsx index 7620213a09..14d097a841 100644 --- a/apps/web/modules/account/components/DeleteAccountModal/index.tsx +++ b/apps/web/modules/account/components/DeleteAccountModal/index.tsx @@ -7,7 +7,6 @@ import { Trans, useTranslation } from "react-i18next"; import { logger } from "@formbricks/logger"; import { TOrganization } from "@formbricks/types/organizations"; import { TUser } from "@formbricks/types/user"; -import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage"; import { getFormattedErrorMessage } from "@/lib/utils/helper"; import { ACCOUNT_DELETION_CONFIRMATION_REQUIRED_ERROR_CODE, @@ -16,6 +15,7 @@ import { DELETE_ACCOUNT_WRONG_PASSWORD_ERROR, FORMBRICKS_CLOUD_ACCOUNT_DELETION_SURVEY_URL, } from "@/modules/account/constants"; +import { useSignOut } from "@/modules/auth/hooks/use-sign-out"; import { DeleteDialog } from "@/modules/ui/components/delete-dialog"; import { Input } from "@/modules/ui/components/input"; import { PasswordInput } from "@/modules/ui/components/password-input"; @@ -28,6 +28,7 @@ interface DeleteAccountModalProps { user: TUser; isFormbricksCloud: boolean; organizationsWithSingleOwner: TOrganization[]; + isSsoIdentityConfirmationDisabled: boolean; } export const DeleteAccountModal = ({ @@ -37,11 +38,13 @@ export const DeleteAccountModal = ({ user, isFormbricksCloud, organizationsWithSingleOwner, + isSsoIdentityConfirmationDisabled, }: Readonly) => { const { t } = useTranslation(); const [deleting, setDeleting] = useState(false); const [inputValue, setInputValue] = useState(""); const [password, setPassword] = useState(""); + const { signOut: signOutWithAudit } = useSignOut({ id: user.id, email: user.email }); const handleInputChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; @@ -121,7 +124,15 @@ export const DeleteAccountModal = ({ return; } - globalThis.localStorage.removeItem(FORMBRICKS_ENVIRONMENT_ID_LS); + try { + await signOutWithAudit({ + clearEnvironmentId: true, + reason: "account_deletion", + redirect: false, + }); + } catch (error) { + logger.error({ error }, "Failed to sign out after account deletion"); + } if (isFormbricksCloud) { globalThis.location.replace(FORMBRICKS_CLOUD_ACCOUNT_DELETION_SURVEY_URL); @@ -192,7 +203,7 @@ export const DeleteAccountModal = ({ id="deleteAccountConfirmation" name="deleteAccountConfirmation" /> - {!requiresPasswordConfirmation && ( + {!requiresPasswordConfirmation && !isSsoIdentityConfirmationDisabled && (

{t("environments.settings.profile.sso_identity_confirmation_may_be_required_for_deletion")}

diff --git a/apps/web/modules/account/lib/account-deletion-sso-reauth.ts b/apps/web/modules/account/lib/account-deletion-sso-reauth.ts index fedc1c1857..15a5e927b2 100644 --- a/apps/web/modules/account/lib/account-deletion-sso-reauth.ts +++ b/apps/web/modules/account/lib/account-deletion-sso-reauth.ts @@ -60,13 +60,6 @@ const NEXT_AUTH_PROVIDER_BY_IDENTITY_PROVIDER = { saml: "saml", } as const satisfies Record; -const INTERACTIVE_SSO_CONFIRMATION_PROVIDERS = new Set([ - "azuread", - "google", - "openid", - "saml", -]); - const getAccountDeletionSsoReauthIntentKey = (intentId: string) => createCacheKey.custom("account_deletion", "sso_reauth_intent", intentId); @@ -95,7 +88,7 @@ const getAccountDeletionSsoReauthAuthorizationParams = ( // the account after verifying the signed deletion intent, making the inbox the confirmation factor. if (provider === "saml") { return { - ...(INTERACTIVE_SSO_CONFIRMATION_PROVIDERS.has(provider) ? { forceAuthn: "true" } : {}), + forceAuthn: "true", product: SAML_PRODUCT, provider: "saml", tenant: SAML_TENANT, @@ -111,12 +104,10 @@ const getAccountDeletionSsoReauthAuthorizationParams = ( return { login_hint: email, - ...(INTERACTIVE_SSO_CONFIRMATION_PROVIDERS.has(provider) ? { prompt: "login" } : {}), + prompt: "login", }; }; -const getAccountDeletionSsoReauthErrorCode = () => ACCOUNT_DELETION_SSO_REAUTH_FAILED_ERROR_CODE; - const createAccountDeletionSsoReauthCallbackUrl = (intentToken: string) => { const callbackUrl = new URL(ACCOUNT_DELETION_SSO_REAUTH_CALLBACK_PATH, WEBAPP_URL); callbackUrl.searchParams.set("intent", intentToken); @@ -159,7 +150,7 @@ export const getAccountDeletionSsoReauthFailureRedirectUrl = ({ const redirectUrl = new URL(validatedReturnToUrl); redirectUrl.searchParams.set( ACCOUNT_DELETION_SSO_REAUTH_ERROR_QUERY_PARAM, - getAccountDeletionSsoReauthErrorCode() + ACCOUNT_DELETION_SSO_REAUTH_FAILED_ERROR_CODE ); return redirectUrl.toString(); } catch (redirectError) { diff --git a/apps/web/modules/setup/organization/create/components/removed-from-organization.tsx b/apps/web/modules/setup/organization/create/components/removed-from-organization.tsx index 9d37344834..d74044987e 100644 --- a/apps/web/modules/setup/organization/create/components/removed-from-organization.tsx +++ b/apps/web/modules/setup/organization/create/components/removed-from-organization.tsx @@ -9,6 +9,7 @@ import { Button } from "@/modules/ui/components/button"; interface RemovedFromOrganizationProps { isFormbricksCloud: boolean; + isSsoIdentityConfirmationDisabled: boolean; requiresPasswordConfirmation: boolean; user: TUser; } @@ -16,6 +17,7 @@ interface RemovedFromOrganizationProps { export const RemovedFromOrganization = ({ user, isFormbricksCloud, + isSsoIdentityConfirmationDisabled, requiresPasswordConfirmation, }: Readonly) => { const { t } = useTranslation(); @@ -35,6 +37,7 @@ export const RemovedFromOrganization = ({ user={user} isFormbricksCloud={isFormbricksCloud} organizationsWithSingleOwner={[]} + isSsoIdentityConfirmationDisabled={isSsoIdentityConfirmationDisabled} />