mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-02 01:09:03 +02:00
show warning when changing idp identifier mapping
This commit is contained in:
@@ -1176,6 +1176,10 @@
|
|||||||
"idpJmespathAboutDescriptionLink": "Learn more about JMESPath",
|
"idpJmespathAboutDescriptionLink": "Learn more about JMESPath",
|
||||||
"idpJmespathLabel": "Identifier Path",
|
"idpJmespathLabel": "Identifier Path",
|
||||||
"idpJmespathLabelDescription": "The path to the user identifier in the ID token",
|
"idpJmespathLabelDescription": "The path to the user identifier in the ID token",
|
||||||
|
"idpIdentifierChangeTitle": "Identifier Path Change Warning",
|
||||||
|
"idpIdentifierChangeDescription": "You are about to change the identifier path. This will affect how existing users are mapped. Users who previously signed in through this identity provider may no longer be recognized as the same users.",
|
||||||
|
"idpIdentifierChangeConfirmMessage": "I confirm",
|
||||||
|
"idpIdentifierChangeWarningText": "This will affect how existing users are mapped",
|
||||||
"idpJmespathEmailPathOptional": "Email Path (Optional)",
|
"idpJmespathEmailPathOptional": "Email Path (Optional)",
|
||||||
"idpJmespathEmailPathOptionalDescription": "The path to the user's email in the ID token",
|
"idpJmespathEmailPathOptionalDescription": "The path to the user's email in the ID token",
|
||||||
"idpJmespathNamePathOptional": "Name Path (Optional)",
|
"idpJmespathNamePathOptional": "Name Path (Optional)",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { AxiosResponse } from "axios";
|
|||||||
import { ListRolesResponse } from "@server/routers/role";
|
import { ListRolesResponse } from "@server/routers/role";
|
||||||
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
|
import AutoProvisionConfigWidget from "@app/components/AutoProvisionConfigWidget";
|
||||||
import IdpAutoProvisionUsersDescription from "@app/components/IdpAutoProvisionUsersDescription";
|
import IdpAutoProvisionUsersDescription from "@app/components/IdpAutoProvisionUsersDescription";
|
||||||
|
import IdpIdentifierChangeDialog from "@app/components/IdpIdentifierChangeDialog";
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import {
|
import {
|
||||||
@@ -75,6 +76,12 @@ export default function GeneralPage() {
|
|||||||
>([createMappingBuilderRule()]);
|
>([createMappingBuilderRule()]);
|
||||||
const [rawRoleExpression, setRawRoleExpression] = useState("");
|
const [rawRoleExpression, setRawRoleExpression] = useState("");
|
||||||
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
||||||
|
const [originalIdentifierPath, setOriginalIdentifierPath] = useState("");
|
||||||
|
const [identifierConfirmOpen, setIdentifierConfirmOpen] = useState(false);
|
||||||
|
const [pendingPayload, setPendingPayload] = useState<Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> | null>(null);
|
||||||
|
|
||||||
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
||||||
const [redirectUrl, setRedirectUrl] = useState(
|
const [redirectUrl, setRedirectUrl] = useState(
|
||||||
@@ -184,6 +191,9 @@ export default function GeneralPage() {
|
|||||||
const data = res.data.data;
|
const data = res.data.data;
|
||||||
const roleMapping = data.idpOrg.roleMapping;
|
const roleMapping = data.idpOrg.roleMapping;
|
||||||
const idpVariant = data.idpOidcConfig?.variant || "oidc";
|
const idpVariant = data.idpOidcConfig?.variant || "oidc";
|
||||||
|
setOriginalIdentifierPath(
|
||||||
|
data.idpOidcConfig?.identifierPath ?? "sub"
|
||||||
|
);
|
||||||
setRedirectUrl(res.data.data.redirectUrl);
|
setRedirectUrl(res.data.data.redirectUrl);
|
||||||
|
|
||||||
// Set the variant
|
// Set the variant
|
||||||
@@ -378,18 +388,56 @@ export default function GeneralPage() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.post(
|
const nextIdentifierPath =
|
||||||
`/org/${orgId}/idp/${idpId}/oidc`,
|
variant === "oidc"
|
||||||
payload
|
? (data as OidcFormValues).identifierPath
|
||||||
);
|
: undefined;
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (
|
||||||
toast({
|
typeof nextIdentifierPath === "string" &&
|
||||||
title: t("success"),
|
nextIdentifierPath !== originalIdentifierPath
|
||||||
description: t("idpUpdatedDescription")
|
) {
|
||||||
});
|
setPendingPayload(payload);
|
||||||
router.refresh();
|
setIdentifierConfirmOpen(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await persistIdp(payload);
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistIdp(payload: Record<string, unknown>) {
|
||||||
|
const res = await api.post(`/org/${orgId}/idp/${idpId}/oidc`, payload);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
if (typeof payload.identifierPath === "string") {
|
||||||
|
setOriginalIdentifierPath(payload.identifierPath);
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: t("success"),
|
||||||
|
description: t("idpUpdatedDescription")
|
||||||
|
});
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmIdentifierChange() {
|
||||||
|
if (!pendingPayload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await persistIdp(pendingPayload);
|
||||||
|
setPendingPayload(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
title: t("error"),
|
title: t("error"),
|
||||||
@@ -407,6 +455,16 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<IdpIdentifierChangeDialog
|
||||||
|
open={identifierConfirmOpen}
|
||||||
|
setOpen={(open) => {
|
||||||
|
setIdentifierConfirmOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setPendingPayload(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onConfirm={confirmIdentifierChange}
|
||||||
|
/>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import {
|
|||||||
} from "@app/components/InfoSection";
|
} from "@app/components/InfoSection";
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
import IdpTypeBadge from "@app/components/IdpTypeBadge";
|
import IdpTypeBadge from "@app/components/IdpTypeBadge";
|
||||||
|
import IdpIdentifierChangeDialog from "@app/components/IdpIdentifierChangeDialog";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function GeneralPage() {
|
export default function GeneralPage() {
|
||||||
@@ -51,6 +52,12 @@ export default function GeneralPage() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [initialLoading, setInitialLoading] = useState(true);
|
const [initialLoading, setInitialLoading] = useState(true);
|
||||||
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
|
||||||
|
const [originalIdentifierPath, setOriginalIdentifierPath] = useState("");
|
||||||
|
const [identifierConfirmOpen, setIdentifierConfirmOpen] = useState(false);
|
||||||
|
const [pendingPayload, setPendingPayload] = useState<Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> | null>(null);
|
||||||
|
|
||||||
const redirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
const redirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
@@ -141,6 +148,9 @@ export default function GeneralPage() {
|
|||||||
| "google"
|
| "google"
|
||||||
| "azure") || "oidc";
|
| "azure") || "oidc";
|
||||||
setVariant(idpVariant);
|
setVariant(idpVariant);
|
||||||
|
setOriginalIdentifierPath(
|
||||||
|
data.idpOidcConfig?.identifierPath ?? "sub"
|
||||||
|
);
|
||||||
|
|
||||||
let tenantId = "";
|
let tenantId = "";
|
||||||
if (idpVariant === "azure" && data.idpOidcConfig?.authUrl) {
|
if (idpVariant === "azure" && data.idpOidcConfig?.authUrl) {
|
||||||
@@ -258,15 +268,56 @@ export default function GeneralPage() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api.post(`/idp/${idpId}/oidc`, payload);
|
const nextIdentifierPath =
|
||||||
|
variant === "oidc"
|
||||||
|
? (data as OidcFormValues).identifierPath
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (res.status === 200) {
|
if (
|
||||||
toast({
|
typeof nextIdentifierPath === "string" &&
|
||||||
title: t("success"),
|
nextIdentifierPath !== originalIdentifierPath
|
||||||
description: t("idpUpdatedDescription")
|
) {
|
||||||
});
|
setPendingPayload(payload);
|
||||||
router.refresh();
|
setIdentifierConfirmOpen(true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await persistIdp(payload);
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: formatAxiosError(e),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistIdp(payload: Record<string, unknown>) {
|
||||||
|
const res = await api.post(`/idp/${idpId}/oidc`, payload);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
if (typeof payload.identifierPath === "string") {
|
||||||
|
setOriginalIdentifierPath(payload.identifierPath);
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: t("success"),
|
||||||
|
description: t("idpUpdatedDescription")
|
||||||
|
});
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmIdentifierChange() {
|
||||||
|
if (!pendingPayload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await persistIdp(pendingPayload);
|
||||||
|
setPendingPayload(null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
title: t("error"),
|
title: t("error"),
|
||||||
@@ -284,6 +335,16 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<IdpIdentifierChangeDialog
|
||||||
|
open={identifierConfirmOpen}
|
||||||
|
setOpen={(open) => {
|
||||||
|
setIdentifierConfirmOpen(open);
|
||||||
|
if (!open) {
|
||||||
|
setPendingPayload(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onConfirm={confirmIdentifierChange}
|
||||||
|
/>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
type IdpIdentifierChangeDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
onConfirm: () => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function IdpIdentifierChangeDialog({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
onConfirm
|
||||||
|
}: IdpIdentifierChangeDialogProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
dialog={
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p>{t("idpIdentifierChangeDescription")}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
buttonText={t("saveGeneralSettings")}
|
||||||
|
onConfirm={onConfirm}
|
||||||
|
string={t("idpIdentifierChangeConfirmMessage")}
|
||||||
|
title={t("idpIdentifierChangeTitle")}
|
||||||
|
warningText={t("idpIdentifierChangeWarningText")}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user