add confirm dialog to update security settings

This commit is contained in:
miloschwartz
2025-10-24 17:30:39 -07:00
parent 460df46abc
commit 9ce81b34c9
4 changed files with 61 additions and 20 deletions

View File

@@ -1774,6 +1774,10 @@
"180Days": "180 days",
"1Year": "1 year",
"subscriptionBadge": "Subscription Required",
"securityPolicyChangeWarning": "Security Policy Change Warning",
"securityPolicyChangeDescription": "You are about to change security policy settings. After saving, you may need to reauthenticate to comply with these policy updates. All users who are not compliant will also need to reauthenticate.",
"securityPolicyChangeConfirmMessage": "I confirm",
"securityPolicyChangeWarningText": "This will affect all users in the organization",
"authPageErrorUpdateMessage": "An error occurred while updating the auth page settings",
"authPageUpdated": "Auth page updated successfully",
"healthCheckNotAvailable": "Local",

View File

@@ -86,20 +86,6 @@ export async function updateOrg(
parsedBody.data.passwordExpiryDays = undefined;
}
if (
req.user &&
req.user.type === UserType.Internal &&
parsedBody.data.requireTwoFactor === true &&
!req.user.twoFactorEnabled
) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"You must enable two-factor authentication for your account before enforcing it for all users"
)
);
}
const updatedOrg = await db
.update(orgs)
.set({

View File

@@ -108,6 +108,7 @@ export default function GeneralPage() {
const [loadingDelete, setLoadingDelete] = useState(false);
const [loadingSave, setLoadingSave] = useState(false);
const [isSecurityPolicyConfirmOpen, setIsSecurityPolicyConfirmOpen] = useState(false);
const authPageSettingsRef = useRef<AuthPageSettingsRef>(null);
const form = useForm({
@@ -122,6 +123,23 @@ export default function GeneralPage() {
mode: "onChange"
});
// Track initial security policy values
const initialSecurityValues = {
requireTwoFactor: org?.org.requireTwoFactor || false,
maxSessionLengthHours: org?.org.maxSessionLengthHours || null,
passwordExpiryDays: org?.org.passwordExpiryDays || null
};
// Check if security policies have changed
const hasSecurityPolicyChanged = () => {
const currentValues = form.getValues();
return (
currentValues.requireTwoFactor !== initialSecurityValues.requireTwoFactor ||
currentValues.maxSessionLengthHours !== initialSecurityValues.maxSessionLengthHours ||
currentValues.passwordExpiryDays !== initialSecurityValues.passwordExpiryDays
);
};
async function deleteOrg() {
setLoadingDelete(true);
try {
@@ -174,6 +192,16 @@ export default function GeneralPage() {
}
async function onSubmit(data: GeneralFormValues) {
// Check if security policies have changed
if (hasSecurityPolicyChanged()) {
setIsSecurityPolicyConfirmOpen(true);
return;
}
await performSave(data);
}
async function performSave(data: GeneralFormValues) {
setLoadingSave(true);
try {
@@ -231,6 +259,20 @@ export default function GeneralPage() {
string={org?.org.name || ""}
title={t("orgDelete")}
/>
<ConfirmDeleteDialog
open={isSecurityPolicyConfirmOpen}
setOpen={setIsSecurityPolicyConfirmOpen}
dialog={
<div>
<p>{t("securityPolicyChangeDescription")}</p>
</div>
}
buttonText={t("saveSettings")}
onConfirm={() => performSave(form.getValues())}
string={t("securityPolicyChangeConfirmMessage")}
title={t("securityPolicyChangeWarning")}
warningText={t("securityPolicyChangeWarningText")}
/>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>

View File

@@ -54,6 +54,7 @@ type InviteUserFormProps = {
dialog: React.ReactNode;
buttonText: string;
onConfirm: () => Promise<void>;
warningText?: string;
};
export default function InviteUserForm({
@@ -63,7 +64,8 @@ export default function InviteUserForm({
title,
onConfirm,
buttonText,
dialog
dialog,
warningText
}: InviteUserFormProps) {
const [loading, setLoading] = useState(false);
@@ -86,13 +88,20 @@ export default function InviteUserForm({
function reset() {
form.reset();
setLoading(false);
}
async function onSubmit(values: z.infer<typeof formSchema>) {
setLoading(true);
await onConfirm();
reset();
try {
await onConfirm();
setOpen(false);
reset();
} catch (error) {
// Handle error if needed
console.error("Confirmation failed:", error);
} finally {
setLoading(false);
}
}
return (
@@ -111,8 +120,8 @@ export default function InviteUserForm({
<CredenzaBody>
<div className="mb-4 break-all overflow-hidden">
{dialog}
<div className="mt-2 mb-6 font-bold text-red-700">
{t("cannotbeUndone")}
<div className="mt-2 mb-6 font-bold text-destructive">
{warningText || t("cannotbeUndone")}
</div>
<div>