Disable activation on scim profiles

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-02-18 17:14:43 +04:00
parent 13aef29ddf
commit 9941a6a66a
12 changed files with 163 additions and 287 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<c5c251783bc35aea15dec0c06f6ef1fb>> * @generated SignedSource<<a32e2b4330ac1d54762817127289b51d>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -301,6 +301,13 @@ return {
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -333,25 +340,6 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
},
{ {
"alias": "lastInvitation", "alias": "lastInvitation",
"args": (v14/*: any*/), "args": (v14/*: any*/),
@@ -377,20 +365,6 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v12/*: any*/), (v12/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"storageKey": null
},
(v15/*: any*/), (v15/*: any*/),
(v11/*: any*/) (v11/*: any*/)
], ],
@@ -524,12 +498,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "f25ca8641478e79fb2ccf6fa825fe042", "cacheID": "15a5dd79df1b16aa1a66ccb8024f9627",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeopleListFragment_RefetchQuery", "name": "PeopleListFragment_RefetchQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n identity {\n email\n id\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n expiresAt\n acceptedAt\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n" "text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: ProfileOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n profiles(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n emailAddress\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<91bec60a40a9170868272814635ea3fa>> * @generated SignedSource<<f8383da9cb37af6caca48c69a809346c>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,18 +17,14 @@ export type PeopleListItemFragment$data = {
readonly canInvite: boolean; readonly canInvite: boolean;
readonly canUpdate: boolean; readonly canUpdate: boolean;
readonly createdAt: string; readonly createdAt: string;
readonly emailAddress: string;
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
readonly identity: {
readonly email: string;
};
readonly lastInvitation: { readonly lastInvitation: {
readonly __id: string; readonly __id: string;
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly acceptedAt: string | null | undefined;
readonly createdAt: string; readonly createdAt: string;
readonly expiresAt: string;
readonly id: string; readonly id: string;
}; };
}>; }>;
@@ -101,6 +97,13 @@ return {
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -137,28 +140,6 @@ return {
}, },
"action": "THROW" "action": "THROW"
}, },
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
}
],
"storageKey": null
},
"action": "THROW"
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -195,20 +176,6 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v0/*: any*/), (v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"storageKey": null
},
(v1/*: any*/), (v1/*: any*/),
{ {
"alias": null, "alias": null,
@@ -318,6 +285,6 @@ return {
}; };
})(); })();
(node as any).hash = "b276a06948260a9d89947b386c6392d1"; (node as any).hash = "f10aa3067eeb7fc95eb7c7a893e7dd6e";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<e32759f25b173edfe9c63fb659d7e75e>> * @generated SignedSource<<86b86e1209fbe58b62c2be028fb0482c>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -266,6 +266,13 @@ return {
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -298,25 +305,6 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
(v6/*: any*/)
],
"storageKey": null
},
{ {
"alias": "lastInvitation", "alias": "lastInvitation",
"args": (v8/*: any*/), "args": (v8/*: any*/),
@@ -342,20 +330,6 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v6/*: any*/), (v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"storageKey": null
},
(v9/*: any*/), (v9/*: any*/),
(v2/*: any*/) (v2/*: any*/)
], ],
@@ -489,12 +463,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "a5d551184b2abe39feea3dfb05bfe21d", "cacheID": "af366ea24d2c96763b104acc69d9b3ff",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PeoplePageQuery", "name": "PeoplePageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canCreateUser: permission(action: \"iam:membership-profile:create\")\n ...PeopleListFragment_8lnpd\n }\n id\n }\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n identity {\n email\n id\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n expiresAt\n acceptedAt\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n" "text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n canCreateUser: permission(action: \"iam:membership-profile:create\")\n ...PeopleListFragment_8lnpd\n }\n id\n }\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n profiles(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n emailAddress\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<a8e224db1200fa5aaa6edc17c09e7a35>> * @generated SignedSource<<1eea90ca68f7381b881c68f0489837cb>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -25,6 +25,7 @@ export type PersonFormFragment$data = {
readonly role: MembershipRole; readonly role: MembershipRole;
}; };
readonly position: string | null | undefined; readonly position: string | null | undefined;
readonly source: string;
readonly " $fragmentType": "PersonFormFragment"; readonly " $fragmentType": "PersonFormFragment";
}; };
export type PersonFormFragment$key = { export type PersonFormFragment$key = {
@@ -59,6 +60,13 @@ const node: ReaderFragment = {
"name": "emailAddress", "name": "emailAddress",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "source",
"storageKey": null
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -134,6 +142,6 @@ const node: ReaderFragment = {
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "4f99d9b9ed708290c5a7d7ec258a0b1d"; (node as any).hash = "37e8a56333b217e5b6d3b94b455d7d40";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<85bf3907699ac605e0603c8d1ae6ef8b>> * @generated SignedSource<<7889e38e185230a2c5b41c3b0fb73cca>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -195,6 +195,13 @@ return {
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -227,25 +234,6 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{ {
"alias": "lastInvitation", "alias": "lastInvitation",
"args": (v4/*: any*/), "args": (v4/*: any*/),
@@ -271,20 +259,6 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"storageKey": null
},
(v5/*: any*/), (v5/*: any*/),
{ {
"alias": null, "alias": null,
@@ -425,12 +399,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "afbd8ffc5b5ae7156a108a2d1b25440b", "cacheID": "738254d707f8ed725c30ee87baa585fb",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PersonForm_createMutation", "name": "PersonForm_createMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation PersonForm_createMutation(\n $input: CreateUserInput!\n) {\n createUser(input: $input) {\n profileEdge {\n node {\n ...PeopleListItemFragment\n id\n }\n }\n }\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n identity {\n email\n id\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n expiresAt\n acceptedAt\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n" "text": "mutation PersonForm_createMutation(\n $input: CreateUserInput!\n) {\n createUser(input: $input) {\n profileEdge {\n node {\n ...PeopleListItemFragment\n id\n }\n }\n }\n}\n\nfragment PeopleListItemFragment on Profile {\n id\n source\n state\n fullName\n emailAddress\n membership {\n id\n role\n canUpdate: permission(action: \"iam:membership:update\")\n }\n lastInvitation: pendingInvitations(first: 1, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n createdAt\n canUpdate: permission(action: \"iam:membership-profile:update\")\n canInvite: permission(action: \"iam:invitation:create\")\n canDelete: permission(action: \"iam:membership-profile:delete\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<679cd9698980b9fdf8b330df8df3aac5>> * @generated SignedSource<<aa1188431214a8844e8ee1879bbada7e>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,11 +17,10 @@ export type PersonPageQuery$data = {
readonly person: { readonly person: {
readonly __typename: "Profile"; readonly __typename: "Profile";
readonly canDelete: boolean; readonly canDelete: boolean;
readonly emailAddress: string;
readonly fullName: string; readonly fullName: string;
readonly id: string; readonly id: string;
readonly identity: { readonly source: string;
readonly email: string;
};
readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">; readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">;
} | { } | {
// This will never be '%other', but we need some // This will never be '%other', but we need some
@@ -74,10 +73,17 @@ v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "email", "name": "emailAddress",
"storageKey": null "storageKey": null
}, },
v6 = { v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "source",
"storageKey": null
},
v7 = {
"alias": "canDelete", "alias": "canDelete",
"args": [ "args": [
{ {
@@ -113,23 +119,9 @@ return {
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v4/*: any*/),
{ (v5/*: any*/),
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
(v5/*: any*/)
],
"storageKey": null
},
"action": "THROW"
},
(v6/*: any*/), (v6/*: any*/),
(v7/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -168,27 +160,9 @@ return {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
(v4/*: any*/), (v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "identity",
"plural": false,
"selections": [
(v5/*: any*/), (v5/*: any*/),
(v3/*: any*/)
],
"storageKey": null
},
(v6/*: any*/), (v6/*: any*/),
{ (v7/*: any*/),
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -266,16 +240,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "cb1cec2fd7c66b42f61d40e6a22b0d67", "cacheID": "f78728c7deb19de68b5b996c58ebd05e",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "PersonPageQuery", "name": "PersonPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query PersonPageQuery(\n $personId: ID!\n) {\n person: node(id: $personId) {\n __typename\n ... on Profile {\n id\n fullName\n identity {\n email\n id\n }\n canDelete: permission(action: \"iam:membership-profile:delete\")\n ...PersonFormFragment\n }\n id\n }\n}\n\nfragment PersonFormFragment on Profile {\n id\n fullName\n emailAddress\n membership {\n role\n id\n }\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n" "text": "query PersonPageQuery(\n $personId: ID!\n) {\n person: node(id: $personId) {\n __typename\n ... on Profile {\n id\n fullName\n emailAddress\n source\n canDelete: permission(action: \"iam:membership-profile:delete\")\n ...PersonFormFragment\n }\n id\n }\n}\n\nfragment PersonFormFragment on Profile {\n id\n fullName\n emailAddress\n source\n membership {\n role\n id\n }\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n"
} }
}; };
})(); })();
(node as any).hash = "c2954bc62ed4a3e3a9b1ca9596b68e1c"; (node as any).hash = "4aecfb7a9ff05a585ccc6b533fb75bec";
export default node; export default node;

View File

@@ -1,6 +1,6 @@
import { sprintf } from "@probo/helpers"; import { sprintf } from "@probo/helpers";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { ActionDropdown, Avatar, Breadcrumb, Card, DropdownItem, IconTrashCan, useConfirm } from "@probo/ui"; import { ActionDropdown, Avatar, Badge, Breadcrumb, Card, DropdownItem, IconTrashCan, useConfirm } from "@probo/ui";
import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
@@ -18,9 +18,8 @@ export const personPageQuery = graphql`
... on Profile { ... on Profile {
id id
fullName fullName
identity @required(action: THROW) { emailAddress
email source
}
canDelete: permission(action: "iam:membership-profile:delete") canDelete: permission(action: "iam:membership-profile:delete")
...PersonFormFragment ...PersonFormFragment
} }
@@ -100,9 +99,11 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
<Avatar name={person.fullName} size="xl" /> <Avatar name={person.fullName} size="xl" />
<div> <div>
<div className="flex items-center gap-2">
<div className="text-2xl">{person.fullName}</div> <span className="text-2xl">{person.fullName}</span>
<div className="text-lg text-txt-secondary">{person.identity.email}</div> <Badge variant="info">{person.source}</Badge>
</div>
<div className="text-lg text-txt-secondary">{person.emailAddress}</div>
</div> </div>
</div> </div>
{person.canDelete && ( {person.canDelete && (

View File

@@ -29,14 +29,12 @@ const fragment = graphql`
source source
state state
fullName fullName
emailAddress
membership @required(action: THROW) { membership @required(action: THROW) {
id id
role role
canUpdate: permission(action: "iam:membership:update") canUpdate: permission(action: "iam:membership:update")
} }
identity @required(action: THROW) {
email
}
lastInvitation: pendingInvitations(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) lastInvitation: pendingInvitations(first: 1, orderBy: { field: CREATED_AT, direction: DESC })
@required(action: THROW) @required(action: THROW)
@connection(key: "PeopleListItem_lastInvitation") { @connection(key: "PeopleListItem_lastInvitation") {
@@ -44,8 +42,6 @@ const fragment = graphql`
edges { edges {
node { node {
id id
expiresAt
acceptedAt
createdAt createdAt
} }
} }
@@ -193,7 +189,6 @@ export function PeopleListItem(props: {
}; };
return ( return (
<>
<Tr to={`/organizations/${organizationId}/people/${profile.id}`}> <Tr to={`/organizations/${organizationId}/people/${profile.id}`}>
<Td className={clsx( <Td className={clsx(
isRemoving && "opacity-60 pointer-events-none", isRemoving && "opacity-60 pointer-events-none",
@@ -211,7 +206,7 @@ export function PeopleListItem(props: {
)} )}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{profile.identity.email} {profile.emailAddress}
<Badge variant="info">{profile.source}</Badge> <Badge variant="info">{profile.source}</Badge>
</div> </div>
</Td> </Td>
@@ -254,7 +249,7 @@ export function PeopleListItem(props: {
</Td> </Td>
<Td noLink width={160} className="text-end"> <Td noLink width={160} className="text-end">
<ActionDropdown> <ActionDropdown>
{isInactive && ( {isInactive && profile.source !== "SCIM" && profile.canInvite && (
<DropdownItem <DropdownItem
onClick={handleInvite} onClick={handleInvite}
icon={IconMail} icon={IconMail}
@@ -262,7 +257,7 @@ export function PeopleListItem(props: {
{lastInvitation ? __("Resend activation mail") : __("Send activation mail")} {lastInvitation ? __("Resend activation mail") : __("Send activation mail")}
</DropdownItem> </DropdownItem>
)} )}
{profile.canDelete && ( {profile.canDelete && profile.source !== "SCIM" && (
<DropdownItem <DropdownItem
onClick={handleRemove} onClick={handleRemove}
variant="danger" variant="danger"
@@ -274,6 +269,5 @@ export function PeopleListItem(props: {
</ActionDropdown> </ActionDropdown>
</Td> </Td>
</Tr> </Tr>
</>
); );
} }

View File

@@ -23,6 +23,7 @@ const fragment = graphql`
id id
fullName fullName
emailAddress emailAddress
source
membership @required(action: THROW) { membership @required(action: THROW) {
role role
} }
@@ -268,7 +269,7 @@ export function PersonFormLoader(props: { fragmentRef: PersonFormFragment$key })
return ( return (
<PersonForm <PersonForm
id={person.id} id={person.id}
disabled={!person.canUpdate} disabled={!person.canUpdate || person.source === "SCIM"}
defaultValues={ defaultValues={
{ {
kind: person.kind, kind: person.kind,

View File

@@ -177,6 +177,10 @@ func (s *AuthService) ActivateAccount(
return fmt.Errorf("cannot load user: %w", err) return fmt.Errorf("cannot load user: %w", err)
} }
if profile.Source == coredata.ProfileSourceSCIM {
return NewUserManagedBySCIMError(profile.ID)
}
if profile.State == coredata.ProfileStateInactive { if profile.State == coredata.ProfileStateInactive {
profile.State = coredata.ProfileStateActive profile.State = coredata.ProfileStateActive
profile.UpdatedAt = now profile.UpdatedAt = now

View File

@@ -372,6 +372,10 @@ func (s *OrganizationService) InviteUser(
return fmt.Errorf("cannot load profile: %w", err) return fmt.Errorf("cannot load profile: %w", err)
} }
if profile.Source == coredata.ProfileSourceSCIM {
return NewUserManagedBySCIMError(profile.ID)
}
err = invitation.Insert(ctx, tx, scope) err = invitation.Insert(ctx, tx, scope)
if err != nil { if err != nil {
return fmt.Errorf("cannot insert invitation: %w", err) return fmt.Errorf("cannot insert invitation: %w", err)

View File

@@ -69,6 +69,7 @@ func NewProfile(profile *coredata.MembershipProfile) *Profile {
return &Profile{ return &Profile{
ID: profile.ID, ID: profile.ID,
FullName: profile.FullName, FullName: profile.FullName,
EmailAddress: profile.EmailAddress,
Source: profile.Source.String(), Source: profile.Source.String(),
State: profile.State, State: profile.State,
AdditionalEmailAddresses: profile.AdditionalEmailAddresses, AdditionalEmailAddresses: profile.AdditionalEmailAddresses,