diff --git a/messages/en-US.json b/messages/en-US.json index bdecc6e8..68572da4 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -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", diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 88cd2df2..ea7e3eeb 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -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({ diff --git a/src/app/[orgId]/settings/general/page.tsx b/src/app/[orgId]/settings/general/page.tsx index a210364f..1d2508f0 100644 --- a/src/app/[orgId]/settings/general/page.tsx +++ b/src/app/[orgId]/settings/general/page.tsx @@ -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(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")} /> + +

{t("securityPolicyChangeDescription")}

+ + } + buttonText={t("saveSettings")} + onConfirm={() => performSave(form.getValues())} + string={t("securityPolicyChangeConfirmMessage")} + title={t("securityPolicyChangeWarning")} + warningText={t("securityPolicyChangeWarningText")} + /> diff --git a/src/components/ConfirmDeleteDialog.tsx b/src/components/ConfirmDeleteDialog.tsx index 499d0887..8dd039ab 100644 --- a/src/components/ConfirmDeleteDialog.tsx +++ b/src/components/ConfirmDeleteDialog.tsx @@ -54,6 +54,7 @@ type InviteUserFormProps = { dialog: React.ReactNode; buttonText: string; onConfirm: () => Promise; + 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) { 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({
{dialog} -
- {t("cannotbeUndone")} +
+ {warningText || t("cannotbeUndone")}