chore: update sso deletion backport

This commit is contained in:
Tiago Farto
2026-05-15 11:55:14 +00:00
parent 0834f0a849
commit 039de42345
6 changed files with 32 additions and 17 deletions
@@ -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<DeleteAccountProps>) => {
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}
/>
<p className="text-sm text-slate-700">
<strong>{t("environments.settings.profile.warning_cannot_undo")}</strong>
@@ -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}
/>
</SettingsCard>
<IdBadge id={user.id} label={t("common.profile_id")} variant="column" />
@@ -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<DeleteAccountModalProps>) => {
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<HTMLInputElement>) => {
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 && (
<p className="mt-2 text-sm text-slate-600">
{t("environments.settings.profile.sso_identity_confirmation_may_be_required_for_deletion")}
</p>
@@ -60,13 +60,6 @@ const NEXT_AUTH_PROVIDER_BY_IDENTITY_PROVIDER = {
saml: "saml",
} as const satisfies Record<TSsoIdentityProvider, string>;
const INTERACTIVE_SSO_CONFIRMATION_PROVIDERS = new Set<TSsoIdentityProvider>([
"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) {
@@ -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<RemovedFromOrganizationProps>) => {
const { t } = useTranslation();
@@ -35,6 +37,7 @@ export const RemovedFromOrganization = ({
user={user}
isFormbricksCloud={isFormbricksCloud}
organizationsWithSingleOwner={[]}
isSsoIdentityConfirmationDisabled={isSsoIdentityConfirmationDisabled}
/>
<Button
onClick={() => {
@@ -2,7 +2,7 @@ import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { AuthenticationError } from "@formbricks/types/errors";
import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { DISABLE_ACCOUNT_DELETION_SSO_CONFIRMATION, IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { gethasNoOrganizations } from "@/lib/instance/service";
import { getOrganizationsByUserId } from "@/lib/organization/service";
import { getUser } from "@/lib/user/service";
@@ -43,6 +43,7 @@ export const CreateOrganizationPage = async () => {
<RemovedFromOrganization
user={user}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
isSsoIdentityConfirmationDisabled={DISABLE_ACCOUNT_DELETION_SSO_CONFIRMATION}
requiresPasswordConfirmation={requiresPasswordConfirmationForAccountDeletion(user)}
/>
);