Fix saml config + org form permissions handling

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-31 15:44:29 +01:00
committed by Bryan Frimin
parent b24ef0c3aa
commit 7e22893fd2
10 changed files with 199 additions and 65 deletions

View File

@@ -1,7 +1,5 @@
import { useTranslate } from "@probo/i18n";
import { Button, Card, IconTrashCan } from "@probo/ui";
import { use } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
import { useNavigate } from "react-router";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { graphql } from "relay-runtime";
@@ -18,6 +16,7 @@ export const generalSettingsPageQuery = graphql`
... on Organization {
id
name @required(action: THROW)
canDelete: permission(action: "iam:organization:delete")
...OrganizationFormFragment
}
}
@@ -42,9 +41,6 @@ export function GeneralSettingsPage(props: {
const { __ } = useTranslate();
const navigate = useNavigate();
const { isAuthorized } = use(PermissionsContext);
const canDelete = isAuthorized("Organization", "deleteOrganization");
const { organization } = usePreloadedQuery<GeneralSettingsPageQuery>(
generalSettingsPageQuery,
queryRef,
@@ -80,7 +76,7 @@ export function GeneralSettingsPage(props: {
<div className="space-y-6">
<OrganizationForm fKey={organization} />
{canDelete && (
{organization.canDelete && (
<div className="space-y-4 mt-12">
<h2 className="text-base font-medium text-red-600">
{__("Danger Zone")}

View File

@@ -5,8 +5,7 @@ import {
type PreloadedQuery,
} from "react-relay";
import type { SAMLSettingsPageQuery } from "/__generated__/iam/SAMLSettingsPageQuery.graphql";
import { Suspense, use, useState } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
import { Suspense, useState } from "react";
import { Breadcrumb, Button, Dialog, useDialogRef } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { SAMLConfigurationList } from "./_components/SAMLConfigurationList";
@@ -21,7 +20,13 @@ import { SAMLDomainVerifyDialog } from "./_components/SAMLDomainVerifyDialog";
export const samlSettingsPageQuery = graphql`
query SAMLSettingsPageQuery($organizationId: ID!) {
organization: node(id: $organizationId) @required(action: THROW) {
...SAMLConfigurationListFragment
__typename
... on Organization {
canCreateSAMLConfiguration: permission(
action: "iam:saml-configuration:create"
)
...SAMLConfigurationListFragment
}
}
}
`;
@@ -38,9 +43,11 @@ export function SAMLSettingsPage(props: {
useState<string>();
const { __ } = useTranslate();
const { isAuthorized } = use(PermissionsContext);
const { organization } = usePreloadedQuery(samlSettingsPageQuery, queryRef);
if (organization.__typename !== "Organization") {
throw new Error("invalid node type");
}
const [formQueryRef, loadFormQuery] =
useQueryLoader<EditSAMLConfigurationFormQuery>(samlConfigurationFormQuery);
@@ -70,7 +77,7 @@ export function SAMLSettingsPage(props: {
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-base font-medium">{__("SAML Single Sign-On")}</h2>
{isAuthorized("Organization", "createSAMLConfiguration") && (
{organization.canCreateSAMLConfiguration && (
<Button onClick={() => handleOpenFormDialog()}>
{__("Add Configuration")}
</Button>

View File

@@ -44,6 +44,7 @@ const inviteMutation = graphql`
expiresAt
acceptedAt
createdAt
canDelete: permission(action: "iam:invitation:delete")
}
}
}

View File

@@ -13,8 +13,7 @@ import {
Textarea,
useDialogRef,
} from "@probo/ui";
import { use, useState, type ChangeEventHandler } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
import { useState, type ChangeEventHandler } from "react";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { graphql } from "relay-runtime";
@@ -33,6 +32,7 @@ const fragment = graphql`
websiteUrl
email
headquarterAddress
canUpdate: permission(action: "iam:organization:update")
}
`;
@@ -83,18 +83,13 @@ export function OrganizationForm(props: {
const { __ } = useTranslate();
const deleteDialogRef = useDialogRef();
const { isAuthorized } = use(PermissionsContext);
const canUpdate = isAuthorized("Organization", "updateOrganization");
const [logoPreview, setLogoPreview] = useState<string | null>(null);
const [horizontalLogoPreview, setHorizontalLogoPreview] = useState<
string | null
>(null);
const organization = useFragment<OrganizationFormFragment$key>(
fragment,
fKey,
);
const { canUpdate, ...organization } =
useFragment<OrganizationFormFragment$key>(fragment, fKey);
const [updateOrganization, isUpdatingOrganization] = useMutationWithToasts(
updateOrganizationMutation,

View File

@@ -1,6 +1,4 @@
import { useTranslate } from "@probo/i18n";
import { use } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
import {
Button,
Card,
@@ -37,6 +35,8 @@ const fragment = graphql`
domainVerificationToken
domainVerifiedAt
testLoginUrl
canUpdate: permission(action: "iam:saml-configuration:update")
canDelete: permission(action: "iam:saml-configuration:delete")
}
}
}
@@ -63,7 +63,6 @@ export function SAMLConfigurationList(props: {
const organizationId = useOrganizationId();
const { __ } = useTranslate();
const { isAuthorized } = use(PermissionsContext);
const confirm = useConfirm();
const [isCopied, copy] = useCopy();
@@ -197,10 +196,7 @@ export function SAMLConfigurationList(props: {
<div className="flex gap-2 justify-end">
{config.domainVerifiedAt ? (
<>
{isAuthorized(
"SAMLConfiguration",
"updateSAMLConfiguration",
) && (
{config.canUpdate && (
<Button
variant="secondary"
onClick={() => onEdit(config.id)}
@@ -208,7 +204,7 @@ export function SAMLConfigurationList(props: {
{__("Edit")}
</Button>
)}
{isAuthorized("Organization", "deleteOrganization") && (
{config.canDelete && (
<Button
variant="danger"
onClick={() => handleDelete(config)}
@@ -219,18 +215,17 @@ export function SAMLConfigurationList(props: {
</>
) : (
<>
{isAuthorized("Organization", "verifyDomain") &&
!!config.domainVerificationToken && (
<Button
variant="primary"
onClick={() =>
onVerifyDomain(config.domainVerificationToken!)
}
>
{__("Verify Domain")}
</Button>
)}
{isAuthorized("Organization", "deleteOrganization") && (
{config.canUpdate && !!config.domainVerificationToken && (
<Button
variant="primary"
onClick={() =>
onVerifyDomain(config.domainVerificationToken!)
}
>
{__("Verify Domain")}
</Button>
)}
{config.canDelete && (
<Button
variant="danger"
onClick={() => handleDelete(config)}