Fix iam pages permissions handling

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-30 18:33:18 +01:00
committed by Bryan Frimin
parent ca533ce7a7
commit e46a11001a
26 changed files with 1848 additions and 1686 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<f1bb84b37a7d8df3e9e563fe6204a307>> * @generated SignedSource<<0da865b38342a3574ae7dc7185a6071d>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -271,6 +271,19 @@ return {
"name": "acceptedAt", "name": "acceptedAt",
"storageKey": null "storageKey": null
}, },
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:invitation:delete\")"
},
(v11/*: any*/) (v11/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -360,12 +373,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "4cc36c8fb50b104ebe3fae66bf847ecc", "cacheID": "2f419e2fcb92f5a188dcd35ff5661fd7",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "InvitationListFragment_RefetchQuery", "name": "InvitationListFragment_RefetchQuery",
"operationKind": "query", "operationKind": "query",
"text": "query InvitationListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: InvitationOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...InvitationListFragment_16fISc\n id\n }\n}\n\nfragment InvitationListFragment_16fISc on Organization {\n invitations(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n}\n" "text": "query InvitationListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: InvitationOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...InvitationListFragment_16fISc\n id\n }\n}\n\nfragment InvitationListFragment_16fISc on Organization {\n invitations(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<748bbadd485b1e131e583c119f980e91>> * @generated SignedSource<<1ff88dc8e6cbfc1d0db4f6d041cd7e51>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -14,6 +14,7 @@ export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWE
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type InvitationListItemFragment$data = { export type InvitationListItemFragment$data = {
readonly acceptedAt: any | null | undefined; readonly acceptedAt: any | null | undefined;
readonly canDelete: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly email: any; readonly email: any;
readonly expiresAt: any; readonly expiresAt: any;
@@ -89,12 +90,25 @@ const node: ReaderFragment = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "acceptedAt", "name": "acceptedAt",
"storageKey": null "storageKey": null
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:invitation:delete\")"
} }
], ],
"type": "Invitation", "type": "Invitation",
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "772414270b8ce2fed6c7b5fe97082c17"; (node as any).hash = "c0e21d5dd227cd3fff739dc9f0922657";
export default node; export default node;

View File

@@ -1,59 +0,0 @@
/**
* @generated SignedSource<<0fa501d76c400538c62a44425ebe4b11>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type InvitationListItem_permissionsFragment$data = {
readonly canDeleteInvitation: boolean;
readonly " $fragmentType": "InvitationListItem_permissionsFragment";
};
export type InvitationListItem_permissionsFragment$key = {
readonly " $data"?: InvitationListItem_permissionsFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"InvitationListItem_permissionsFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
"kind": "Fragment",
"metadata": null,
"name": "InvitationListItem_permissionsFragment",
"selections": [
{
"alias": "canDeleteInvitation",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:delete"
},
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
}
],
"type": "Identity",
"abstractKey": null
};
(node as any).hash = "e9e75ae0f5a3755a777acf16e5024ee0";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<75b9df3cc430ed26d41b88a67a410aca>> * @generated SignedSource<<a828bfcd067f92e4dc3a1adcd4fe786d>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -288,6 +288,32 @@ return {
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:delete\")"
},
(v11/*: any*/) (v11/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -377,12 +403,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "df1f40dcee56d6e5181849cd82acb824", "cacheID": "a879076e1e75bea4d524852533db17b6",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MemberListFragment_RefetchQuery", "name": "MemberListFragment_RefetchQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n" "text": "query MemberListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MemberListFragment_16fISc\n id\n }\n}\n\nfragment MemberListFragment_16fISc on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<50f7d8bffdec238d8a262e892db7be39>> * @generated SignedSource<<2a646771572f86908a4000fae533c4d8>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -12,6 +12,8 @@ import { ReaderFragment } from 'relay-runtime';
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER"; export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type MemberListItemFragment$data = { export type MemberListItemFragment$data = {
readonly canDelete: boolean;
readonly canUpdate: boolean;
readonly createdAt: any; readonly createdAt: any;
readonly id: string; readonly id: string;
readonly identity: { readonly identity: {
@@ -98,12 +100,38 @@ const node: ReaderFragment = {
"kind": "ScalarField", "kind": "ScalarField",
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
},
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:delete\")"
} }
], ],
"type": "Membership", "type": "Membership",
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "31ca1c20d23f9709b0c1575ae56aeb64"; (node as any).hash = "a7adb3da099816d296abd56c0b8dde16";
export default node; export default node;

View File

@@ -1,77 +0,0 @@
/**
* @generated SignedSource<<66b5754d2d4ecfc54f6c698df247e241>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type MemberListItem_permissionsFragment$data = {
readonly canDeleteMembership: boolean;
readonly canUpdateMembership: boolean;
readonly " $fragmentType": "MemberListItem_permissionsFragment";
};
export type MemberListItem_permissionsFragment$key = {
readonly " $data"?: MemberListItem_permissionsFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"MemberListItem_permissionsFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
"kind": "Fragment",
"metadata": null,
"name": "MemberListItem_permissionsFragment",
"selections": [
{
"alias": "canUpdateMembership",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:update"
},
(v0/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canDeleteMembership",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:delete"
},
(v0/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
}
],
"type": "Identity",
"abstractKey": null
};
})();
(node as any).hash = "d467db6a9f7f80521cf35c8c1a0c3416";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<33faed6358cc7fba56a1f493c11f31d4>> * @generated SignedSource<<5854e660d90c68e5ec01199e55c42a41>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -16,6 +16,7 @@ export type MembersPageQuery$variables = {
export type MembersPageQuery$data = { export type MembersPageQuery$data = {
readonly organization: { readonly organization: {
readonly __typename: "Organization"; readonly __typename: "Organization";
readonly canInviteUser: boolean;
readonly invitations: { readonly invitations: {
readonly totalCount: number | null | undefined; readonly totalCount: number | null | undefined;
}; };
@@ -28,10 +29,6 @@ export type MembersPageQuery$data = {
// value in case none of the concrete values match. // value in case none of the concrete values match.
readonly __typename: "%other"; readonly __typename: "%other";
}; };
readonly viewer: {
readonly canInviteUser: boolean;
readonly " $fragmentSpreads": FragmentRefs<"InvitationListItem_permissionsFragment" | "MemberListItem_permissionsFragment">;
};
}; };
export type MembersPageQuery = { export type MembersPageQuery = {
response: MembersPageQuery$data; response: MembersPageQuery$data;
@@ -46,120 +43,111 @@ var v0 = [
"name": "organizationId" "name": "organizationId"
} }
], ],
v1 = { v1 = [
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
},
v2 = {
"alias": "canInviteUser",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:create"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
v3 = [
{ {
"kind": "Variable", "kind": "Variable",
"name": "organizationId", "name": "id",
"variableName": "organizationId" "variableName": "organizationId"
} }
], ],
v4 = [ v2 = {
(v1/*: any*/)
],
v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v6 = { v3 = {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 20 "value": 20
}, },
v7 = { v4 = {
"direction": "ASC", "direction": "ASC",
"field": "CREATED_AT" "field": "CREATED_AT"
}, },
v8 = [ v5 = [
(v6/*: any*/), (v3/*: any*/),
{ {
"kind": "Literal", "kind": "Literal",
"name": "order", "name": "order",
"value": (v7/*: any*/) "value": (v4/*: any*/)
} }
], ],
v9 = [ v6 = [
(v6/*: any*/), (v3/*: any*/),
{ {
"kind": "Literal", "kind": "Literal",
"name": "orderBy", "name": "orderBy",
"value": (v7/*: any*/) "value": (v4/*: any*/)
} }
], ],
v10 = { v7 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "totalCount", "name": "totalCount",
"storageKey": null "storageKey": null
}, },
v11 = [ v8 = [
(v10/*: any*/) (v7/*: any*/)
], ],
v12 = { v9 = {
"alias": "canInviteUser",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:create"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:invitation:create\")"
},
v10 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "id",
"storageKey": null "storageKey": null
}, },
v13 = { v11 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "role", "name": "role",
"storageKey": null "storageKey": null
}, },
v14 = { v12 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
v15 = { v13 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "email", "name": "email",
"storageKey": null "storageKey": null
}, },
v16 = { v14 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
v17 = { v15 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "cursor", "name": "cursor",
"storageKey": null "storageKey": null
}, },
v18 = { v16 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "PageInfo", "concreteType": "PageInfo",
@@ -198,7 +186,7 @@ v18 = {
], ],
"storageKey": null "storageKey": null
}, },
v19 = { v17 = {
"kind": "ClientExtension", "kind": "ClientExtension",
"selections": [ "selections": [
{ {
@@ -210,7 +198,7 @@ v19 = {
} }
] ]
}, },
v20 = [ v18 = [
"orderBy" "orderBy"
]; ];
return { return {
@@ -220,43 +208,17 @@ return {
"metadata": null, "metadata": null,
"name": "MembersPageQuery", "name": "MembersPageQuery",
"selections": [ "selections": [
{
"kind": "RequiredField",
"field": {
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/),
{
"args": (v3/*: any*/),
"kind": "FragmentSpread",
"name": "InvitationListItem_permissionsFragment"
},
{
"args": (v3/*: any*/),
"kind": "FragmentSpread",
"name": "MemberListItem_permissionsFragment"
}
],
"storageKey": null
},
"action": "THROW"
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
"alias": "organization", "alias": "organization",
"args": (v4/*: any*/), "args": (v1/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v5/*: any*/), (v2/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
@@ -266,7 +228,7 @@ return {
"name": "InviteUserDialog_currentRoleFragment" "name": "InviteUserDialog_currentRoleFragment"
}, },
{ {
"args": (v8/*: any*/), "args": (v5/*: any*/),
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "MemberListFragment" "name": "MemberListFragment"
}, },
@@ -274,18 +236,18 @@ return {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"concreteType": "MembershipConnection", "concreteType": "MembershipConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "members", "name": "members",
"plural": false, "plural": false,
"selections": (v11/*: any*/), "selections": (v8/*: any*/),
"storageKey": "members(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" "storageKey": "members(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
}, },
"action": "THROW" "action": "THROW"
}, },
{ {
"args": (v8/*: any*/), "args": (v5/*: any*/),
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "InvitationListFragment" "name": "InvitationListFragment"
}, },
@@ -293,16 +255,17 @@ return {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"concreteType": "InvitationConnection", "concreteType": "InvitationConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "invitations", "name": "invitations",
"plural": false, "plural": false,
"selections": (v11/*: any*/), "selections": (v8/*: any*/),
"storageKey": "invitations(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" "storageKey": "invitations(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
}, },
"action": "THROW" "action": "THROW"
} },
(v9/*: any*/)
], ],
"type": "Organization", "type": "Organization",
"abstractKey": null "abstractKey": null
@@ -322,71 +285,16 @@ return {
"kind": "Operation", "kind": "Operation",
"name": "MembersPageQuery", "name": "MembersPageQuery",
"selections": [ "selections": [
{
"alias": null,
"args": null,
"concreteType": "Identity",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": "canDeleteInvitation",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:delete"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canUpdateMembership",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:update"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canDeleteMembership",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:delete"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
},
{ {
"alias": "organization", "alias": "organization",
"args": (v4/*: any*/), "args": (v1/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v5/*: any*/), (v2/*: any*/),
(v12/*: any*/), (v10/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
@@ -398,20 +306,20 @@ return {
"name": "viewerMembership", "name": "viewerMembership",
"plural": false, "plural": false,
"selections": [ "selections": [
(v13/*: any*/), (v11/*: any*/),
(v12/*: any*/) (v10/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
{ {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"concreteType": "MembershipConnection", "concreteType": "MembershipConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "members", "name": "members",
"plural": false, "plural": false,
"selections": [ "selections": [
(v10/*: any*/), (v7/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -428,8 +336,8 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v12/*: any*/), (v10/*: any*/),
(v13/*: any*/), (v11/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -438,8 +346,8 @@ return {
"name": "profile", "name": "profile",
"plural": false, "plural": false,
"selections": [ "selections": [
(v14/*: any*/), (v12/*: any*/),
(v12/*: any*/) (v10/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -451,29 +359,55 @@ return {
"name": "identity", "name": "identity",
"plural": false, "plural": false,
"selections": [ "selections": [
(v15/*: any*/), (v13/*: any*/),
(v12/*: any*/) (v10/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v16/*: any*/), (v14/*: any*/),
(v5/*: any*/) {
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:membership:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:membership:delete\")"
},
(v2/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v17/*: any*/) (v15/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v18/*: any*/), (v16/*: any*/),
(v19/*: any*/) (v17/*: any*/)
], ],
"storageKey": "members(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" "storageKey": "members(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
}, },
{ {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"filters": (v20/*: any*/), "filters": (v18/*: any*/),
"handle": "connection", "handle": "connection",
"key": "MemberListFragment_members", "key": "MemberListFragment_members",
"kind": "LinkedHandle", "kind": "LinkedHandle",
@@ -481,13 +415,13 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"concreteType": "InvitationConnection", "concreteType": "InvitationConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "invitations", "name": "invitations",
"plural": false, "plural": false,
"selections": [ "selections": [
(v10/*: any*/), (v7/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -504,10 +438,10 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v10/*: any*/),
(v12/*: any*/), (v12/*: any*/),
(v14/*: any*/),
(v15/*: any*/),
(v13/*: any*/), (v13/*: any*/),
(v11/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -515,7 +449,7 @@ return {
"name": "status", "name": "status",
"storageKey": null "storageKey": null
}, },
(v16/*: any*/), (v14/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -530,28 +464,42 @@ return {
"name": "acceptedAt", "name": "acceptedAt",
"storageKey": null "storageKey": null
}, },
(v5/*: any*/) {
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:invitation:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:invitation:delete\")"
},
(v2/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v17/*: any*/) (v15/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v18/*: any*/), (v16/*: any*/),
(v19/*: any*/) (v17/*: any*/)
], ],
"storageKey": "invitations(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" "storageKey": "invitations(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})"
}, },
{ {
"alias": null, "alias": null,
"args": (v9/*: any*/), "args": (v6/*: any*/),
"filters": (v20/*: any*/), "filters": (v18/*: any*/),
"handle": "connection", "handle": "connection",
"key": "InvitationListFragment_invitations", "key": "InvitationListFragment_invitations",
"kind": "LinkedHandle", "kind": "LinkedHandle",
"name": "invitations" "name": "invitations"
} },
(v9/*: any*/)
], ],
"type": "Organization", "type": "Organization",
"abstractKey": null "abstractKey": null
@@ -562,16 +510,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "1c006f49a510a658f66a78c66e9289db", "cacheID": "acfe76e30a3a7db24fec4acf2e3fd987",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MembersPageQuery", "name": "MembersPageQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n viewer {\n canInviteUser: permission(action: \"iam:membership:create\", id: $organizationId)\n ...InvitationListItem_permissionsFragment_4xMPKw\n ...MemberListItem_permissionsFragment_4xMPKw\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...InviteUserDialog_currentRoleFragment\n ...MemberListFragment_1jRT0c\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n ...InvitationListFragment_1jRT0c\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n }\n id\n }\n}\n\nfragment InvitationListFragment_1jRT0c on Organization {\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n}\n\nfragment InvitationListItem_permissionsFragment_4xMPKw on Identity {\n canDeleteInvitation: permission(action: \"iam:invitation:delete\", id: $organizationId)\n}\n\nfragment InviteUserDialog_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n\nfragment MemberListFragment_1jRT0c on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n\nfragment MemberListItem_permissionsFragment_4xMPKw on Identity {\n canUpdateMembership: permission(action: \"iam:membership:update\", id: $organizationId)\n canDeleteMembership: permission(action: \"iam:membership:delete\", id: $organizationId)\n}\n" "text": "query MembersPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...InviteUserDialog_currentRoleFragment\n ...MemberListFragment_1jRT0c\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n ...InvitationListFragment_1jRT0c\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n }\n canInviteUser: permission(action: \"iam:invitation:create\")\n }\n id\n }\n}\n\nfragment InvitationListFragment_1jRT0c on Organization {\n invitations(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...InvitationListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment InvitationListItemFragment on Invitation {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n canDelete: permission(action: \"iam:invitation:delete\")\n}\n\nfragment InviteUserDialog_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n\nfragment MemberListFragment_1jRT0c on Organization {\n ...MemberListItem_currentRoleFragment\n members(first: 20, orderBy: {direction: ASC, field: CREATED_AT}) {\n totalCount\n edges {\n node {\n id\n ...MemberListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment MemberListItemFragment on Membership {\n id\n role\n profile {\n fullName\n id\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n\nfragment MemberListItem_currentRoleFragment on Organization {\n viewerMembership {\n role\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "6e919357c168bbfbdccfa7375ce71660"; (node as any).hash = "591a88a5e1b0bd427daff74dc27ac512";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<90e5e1f754a2b18caa9b6f4f208ec5a8>> * @generated SignedSource<<bbaeba32a81d742b0aac7fb505843d5f>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,13 +15,13 @@ export type MembershipLayoutQuery$variables = {
}; };
export type MembershipLayoutQuery$data = { export type MembershipLayoutQuery$data = {
readonly organization: { readonly organization: {
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment">; readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment" | "SidebarFragment">;
}; };
readonly viewer: { readonly viewer: {
readonly pendingInvitations: { readonly pendingInvitations: {
readonly totalCount: number; readonly totalCount: number;
}; };
readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_viewerFragment" | "SidebarFragment">; readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_viewerFragment">;
}; };
}; };
export type MembershipLayoutQuery = { export type MembershipLayoutQuery = {
@@ -37,22 +37,21 @@ var v0 = [
"name": "organizationId" "name": "organizationId"
} }
], ],
v1 = { v1 = [
"kind": "Variable", {
"name": "id", "kind": "Variable",
"variableName": "organizationId" "name": "id",
}, "variableName": "organizationId"
v2 = [ }
(v1/*: any*/)
], ],
v3 = { v2 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "totalCount", "name": "totalCount",
"storageKey": null "storageKey": null
}, },
v4 = { v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -70,7 +69,7 @@ return {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
"alias": "organization", "alias": "organization",
"args": (v2/*: any*/), "args": (v1/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
@@ -88,6 +87,11 @@ return {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
"name": "SessionDropdownFragment" "name": "SessionDropdownFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "SidebarFragment"
} }
], ],
"type": "Organization", "type": "Organization",
@@ -108,17 +112,6 @@ return {
"name": "viewer", "name": "viewer",
"plural": false, "plural": false,
"selections": [ "selections": [
{
"args": [
{
"kind": "Variable",
"name": "organizationId",
"variableName": "organizationId"
}
],
"kind": "FragmentSpread",
"name": "SidebarFragment"
},
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -136,7 +129,7 @@ return {
"selections": [ "selections": [
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": (v3/*: any*/), "field": (v2/*: any*/),
"action": "THROW" "action": "THROW"
} }
], ],
@@ -161,7 +154,7 @@ return {
"selections": [ "selections": [
{ {
"alias": "organization", "alias": "organization",
"args": (v2/*: any*/), "args": (v1/*: any*/),
"concreteType": null, "concreteType": null,
"kind": "LinkedField", "kind": "LinkedField",
"name": "node", "name": "node",
@@ -184,6 +177,19 @@ return {
"name": "name", "name": "name",
"storageKey": null "storageKey": null
}, },
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:organization:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:organization:delete\")"
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -207,7 +213,7 @@ return {
"name": "email", "name": "email",
"storageKey": null "storageKey": null
}, },
(v4/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -226,19 +232,266 @@ return {
"name": "fullName", "name": "fullName",
"storageKey": null "storageKey": null
}, },
(v4/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v4/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
},
{
"alias": "canListMeetings",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:meeting:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:meeting:list\")"
},
{
"alias": "canListTasks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:task:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:task:list\")"
},
{
"alias": "canListMeasures",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measures:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measures:list\")"
},
{
"alias": "canListRisks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:risk:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:risk:list\")"
},
{
"alias": "canListFrameworks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:frameworks:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:frameworks:list\")"
},
{
"alias": "canListPeople",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:people:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:people:list\")"
},
{
"alias": "canListVendors",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:vendor:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:vendor:list\")"
},
{
"alias": "canListDocuments",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:document:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:document:list\")"
},
{
"alias": "canListAssets",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:asset:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:asset:list\")"
},
{
"alias": "canListData",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:datum:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:datum:list\")"
},
{
"alias": "canListAudits",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:audit:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:audit:list\")"
},
{
"alias": "canListNonconformities",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:nonconformity:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:nonconformity:list\")"
},
{
"alias": "canListObligations",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:obligation:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:obligation:list\")"
},
{
"alias": "canListContinualImprovements",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:continual-improvement:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:continual-improvement:list\")"
},
{
"alias": "canListProcessingActivities",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:processing-activity:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:processing-activity:list\")"
},
{
"alias": "canListStatesOfApplicability",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:state-of-applicability:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:state-of-applicability:list\")"
},
{
"alias": "canListSnapshots",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:snapshot:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:snapshot:list\")"
},
{
"alias": "canGetTrustCenter",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:get"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:trust-center:get\")"
},
{
"alias": "canUpdateOrganization",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:organization:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:organization:update\")"
} }
], ],
"type": "Organization", "type": "Organization",
"abstractKey": null "abstractKey": null
}, },
(v4/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -250,272 +503,6 @@ return {
"name": "viewer", "name": "viewer",
"plural": false, "plural": false,
"selections": [ "selections": [
{
"alias": "canListMeetings",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:meeting:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListTasks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:task:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListMeasures",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measures:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListRisks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:risk:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListFrameworks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:frameworks:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListPeople",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:people:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListVendors",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:vendor:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListDocuments",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:document:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListAssets",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:asset:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListData",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:datum:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListAudits",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:audit:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListNonconformities",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:nonconformity:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListObligations",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:obligation:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListContinualImprovements",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:continual-improvement:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListProcessingActivities",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:processing-activity:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListStatesOfApplicability",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:state-of-applicability:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canListSnapshots",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:snapshot:list"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canGetTrustCenter",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:trust-center:get"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{
"alias": "canUpdateOrganization",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:organization:update"
},
(v1/*: any*/)
],
"kind": "ScalarField",
"name": "permission",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -524,27 +511,27 @@ return {
"name": "pendingInvitations", "name": "pendingInvitations",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/) (v2/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v4/*: any*/) (v3/*: any*/)
], ],
"storageKey": null "storageKey": null
} }
] ]
}, },
"params": { "params": {
"cacheID": "1fb1002661548737fdb22694a23bca35", "cacheID": "34a53202cb35bb4f38588dabe5063f6c",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MembershipLayoutQuery", "name": "MembershipLayoutQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MembershipLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n }\n id\n }\n viewer {\n ...SidebarFragment_4xMPKw\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n\nfragment SidebarFragment_4xMPKw on Identity {\n canListMeetings: permission(action: \"core:meeting:list\", id: $organizationId)\n canListTasks: permission(action: \"core:task:list\", id: $organizationId)\n canListMeasures: permission(action: \"core:measures:list\", id: $organizationId)\n canListRisks: permission(action: \"core:risk:list\", id: $organizationId)\n canListFrameworks: permission(action: \"core:frameworks:list\", id: $organizationId)\n canListPeople: permission(action: \"core:people:list\", id: $organizationId)\n canListVendors: permission(action: \"core:vendor:list\", id: $organizationId)\n canListDocuments: permission(action: \"core:document:list\", id: $organizationId)\n canListAssets: permission(action: \"core:asset:list\", id: $organizationId)\n canListData: permission(action: \"core:datum:list\", id: $organizationId)\n canListAudits: permission(action: \"core:audit:list\", id: $organizationId)\n canListNonconformities: permission(action: \"core:nonconformity:list\", id: $organizationId)\n canListObligations: permission(action: \"core:obligation:list\", id: $organizationId)\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\", id: $organizationId)\n canListProcessingActivities: permission(action: \"core:processing-activity:list\", id: $organizationId)\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\", id: $organizationId)\n canListSnapshots: permission(action: \"core:snapshot:list\", id: $organizationId)\n canGetTrustCenter: permission(action: \"core:trust-center:get\", id: $organizationId)\n canUpdateOrganization: permission(action: \"iam:organization:update\", id: $organizationId)\n}\n" "text": "query MembershipLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n ...SidebarFragment\n }\n id\n }\n viewer {\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n canDelete: permission(action: \"iam:organization:delete\")\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n}\n"
} }
}; };
})(); })();
(node as any).hash = "bea3f973a014e38e291ecd177da5c5f5"; (node as any).hash = "9a87ccbe2aa86cca7845e0145b133921";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<0d0906bc941900bb200a6a9824aaacc4>> * @generated SignedSource<<2c57cb9a331fb7fedf72b0113bf7cb66>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -11,6 +11,7 @@
import { ReaderFragment } from 'relay-runtime'; import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type SessionDropdownFragment$data = { export type SessionDropdownFragment$data = {
readonly canDelete: boolean;
readonly viewerMembership: { readonly viewerMembership: {
readonly identity: { readonly identity: {
readonly email: any; readonly email: any;
@@ -32,6 +33,19 @@ const node: ReaderFragment = {
"metadata": null, "metadata": null,
"name": "SessionDropdownFragment", "name": "SessionDropdownFragment",
"selections": [ "selections": [
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "iam:organization:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"iam:organization:delete\")"
},
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -96,6 +110,6 @@ const node: ReaderFragment = {
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "c283fcefcab15b977b44ece82f7cf450"; (node as any).hash = "51810eafbf165e7b5eeec42cb268ae54";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<d7cb6d45a4e2a8960d1984e0e1f74b0a>> * @generated SignedSource<<e98eac6c530fd62defd06d5978d9724b>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -37,20 +37,8 @@ export type SidebarFragment$key = {
readonly " $fragmentSpreads": FragmentRefs<"SidebarFragment">; readonly " $fragmentSpreads": FragmentRefs<"SidebarFragment">;
}; };
const node: ReaderFragment = (function(){ const node: ReaderFragment = {
var v0 = { "argumentDefinitions": [],
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
"kind": "Fragment", "kind": "Fragment",
"metadata": null, "metadata": null,
"name": "SidebarFragment", "name": "SidebarFragment",
@@ -62,12 +50,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:meeting:list" "value": "core:meeting:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:meeting:list\")"
}, },
{ {
"alias": "canListTasks", "alias": "canListTasks",
@@ -76,12 +63,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:task:list" "value": "core:task:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:task:list\")"
}, },
{ {
"alias": "canListMeasures", "alias": "canListMeasures",
@@ -90,12 +76,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:measures:list" "value": "core:measures:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:measures:list\")"
}, },
{ {
"alias": "canListRisks", "alias": "canListRisks",
@@ -104,12 +89,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:risk:list" "value": "core:risk:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:risk:list\")"
}, },
{ {
"alias": "canListFrameworks", "alias": "canListFrameworks",
@@ -118,12 +102,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:frameworks:list" "value": "core:frameworks:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:frameworks:list\")"
}, },
{ {
"alias": "canListPeople", "alias": "canListPeople",
@@ -132,12 +115,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:people:list" "value": "core:people:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:people:list\")"
}, },
{ {
"alias": "canListVendors", "alias": "canListVendors",
@@ -146,12 +128,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:vendor:list" "value": "core:vendor:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:vendor:list\")"
}, },
{ {
"alias": "canListDocuments", "alias": "canListDocuments",
@@ -160,12 +141,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:document:list" "value": "core:document:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:document:list\")"
}, },
{ {
"alias": "canListAssets", "alias": "canListAssets",
@@ -174,12 +154,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:asset:list" "value": "core:asset:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:asset:list\")"
}, },
{ {
"alias": "canListData", "alias": "canListData",
@@ -188,12 +167,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:datum:list" "value": "core:datum:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:datum:list\")"
}, },
{ {
"alias": "canListAudits", "alias": "canListAudits",
@@ -202,12 +180,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:audit:list" "value": "core:audit:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:audit:list\")"
}, },
{ {
"alias": "canListNonconformities", "alias": "canListNonconformities",
@@ -216,12 +193,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:nonconformity:list" "value": "core:nonconformity:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:nonconformity:list\")"
}, },
{ {
"alias": "canListObligations", "alias": "canListObligations",
@@ -230,12 +206,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:obligation:list" "value": "core:obligation:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:obligation:list\")"
}, },
{ {
"alias": "canListContinualImprovements", "alias": "canListContinualImprovements",
@@ -244,12 +219,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:continual-improvement:list" "value": "core:continual-improvement:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:continual-improvement:list\")"
}, },
{ {
"alias": "canListProcessingActivities", "alias": "canListProcessingActivities",
@@ -258,12 +232,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:processing-activity:list" "value": "core:processing-activity:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:processing-activity:list\")"
}, },
{ {
"alias": "canListStatesOfApplicability", "alias": "canListStatesOfApplicability",
@@ -272,12 +245,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:state-of-applicability:list" "value": "core:state-of-applicability:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:state-of-applicability:list\")"
}, },
{ {
"alias": "canListSnapshots", "alias": "canListSnapshots",
@@ -286,12 +258,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:snapshot:list" "value": "core:snapshot:list"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:snapshot:list\")"
}, },
{ {
"alias": "canGetTrustCenter", "alias": "canGetTrustCenter",
@@ -300,12 +271,11 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "core:trust-center:get" "value": "core:trust-center:get"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"core:trust-center:get\")"
}, },
{ {
"alias": "canUpdateOrganization", "alias": "canUpdateOrganization",
@@ -314,19 +284,17 @@ return {
"kind": "Literal", "kind": "Literal",
"name": "action", "name": "action",
"value": "iam:organization:update" "value": "iam:organization:update"
}, }
(v0/*: any*/)
], ],
"kind": "ScalarField", "kind": "ScalarField",
"name": "permission", "name": "permission",
"storageKey": null "storageKey": "permission(action:\"iam:organization:update\")"
} }
], ],
"type": "Identity", "type": "Organization",
"abstractKey": null "abstractKey": null
}; };
})();
(node as any).hash = "a116eb6c3f1c2808f90316b34ef43979"; (node as any).hash = "2baad6a78a98c3e9458cdb1ada294d76";
export default node; export default node;

View File

@@ -4,7 +4,6 @@ import { Badge, Button, IconPeopleAdd, Layout, Skeleton } from "@probo/ui";
import { Sidebar } from "./_components/Sidebar"; import { Sidebar } from "./_components/Sidebar";
import { MembershipsDropdown } from "./MembershipsDropdown"; import { MembershipsDropdown } from "./MembershipsDropdown";
import type { MembershipLayoutQuery } from "/__generated__/iam/MembershipLayoutQuery.graphql"; import type { MembershipLayoutQuery } from "/__generated__/iam/MembershipLayoutQuery.graphql";
import { PermissionsProvider } from "/providers/PermissionsProvider";
import { SessionDropdown } from "./_components/SessionDropdown"; import { SessionDropdown } from "./_components/SessionDropdown";
import { Suspense } from "react"; import { Suspense } from "react";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
@@ -16,10 +15,10 @@ export const membershipLayoutQuery = graphql`
... on Organization { ... on Organization {
...MembershipsDropdown_organizationFragment ...MembershipsDropdown_organizationFragment
...SessionDropdownFragment ...SessionDropdownFragment
...SidebarFragment
} }
} }
viewer @required(action: THROW) { viewer @required(action: THROW) {
...SidebarFragment @arguments(organizationId: $organizationId)
...MembershipsDropdown_viewerFragment ...MembershipsDropdown_viewerFragment
pendingInvitations @required(action: THROW) { pendingInvitations @required(action: THROW) {
totalCount @required(action: THROW) totalCount @required(action: THROW)
@@ -41,39 +40,37 @@ export function MembershipLayout(props: {
); );
return ( return (
<PermissionsProvider> <Layout
<Layout header={
header={ <>
<> <div className="mr-auto">
<div className="mr-auto"> <MembershipsDropdown
<MembershipsDropdown organizationFKey={organization}
organizationFKey={organization} viewerFKey={viewer}
viewerFKey={viewer} />
/> {viewer.pendingInvitations.totalCount > 0 && (
{viewer.pendingInvitations.totalCount > 0 && ( <Link to="/" className="relative" title={__("Invitations")}>
<Link to="/" className="relative" title={__("Invitations")}> <Button variant="tertiary" icon={IconPeopleAdd} />
<Button variant="tertiary" icon={IconPeopleAdd} /> <Badge
<Badge variant="info"
variant="info" size="sm"
size="sm" className="absolute -top-1 -right-1 min-w-[20px] h-5 flex items-center justify-center"
className="absolute -top-1 -right-1 min-w-[20px] h-5 flex items-center justify-center" >
> {viewer.pendingInvitations.totalCount}
{viewer.pendingInvitations.totalCount} </Badge>
</Badge> </Link>
</Link> )}
)} </div>
</div> <Suspense fallback={<Skeleton className="w-32 h-8" />}>
<Suspense fallback={<Skeleton className="w-32 h-8" />}> <SessionDropdown fKey={organization} />
<SessionDropdown fKey={organization} /> </Suspense>
</Suspense> </>
</> }
} sidebar={<Sidebar fKey={organization} />}
sidebar={<Sidebar fKey={viewer} />} >
> <CoreRelayProvider>
<CoreRelayProvider> <Outlet />
<Outlet /> </CoreRelayProvider>
</CoreRelayProvider> </Layout>
</Layout>
</PermissionsProvider>
); );
} }

View File

@@ -8,8 +8,6 @@ import {
UserDropdownItem, UserDropdownItem,
useToast, useToast,
} from "@probo/ui"; } from "@probo/ui";
import { use } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import { useFragment, useMutation } from "react-relay"; import { useFragment, useMutation } from "react-relay";
@@ -18,6 +16,7 @@ import type { SessionDropdownFragment$key } from "/__generated__/iam/SessionDrop
export const fragment = graphql` export const fragment = graphql`
fragment SessionDropdownFragment on Organization { fragment SessionDropdownFragment on Organization {
canDelete: permission(action: "iam:organization:delete")
viewerMembership @required(action: THROW) { viewerMembership @required(action: THROW) {
identity @required(action: THROW) { identity @required(action: THROW) {
email email
@@ -42,10 +41,10 @@ export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { isAuthorized } = use(PermissionsContext);
const { toast } = useToast(); const { toast } = useToast();
const { const {
canDelete,
viewerMembership: { viewerMembership: {
identity: { email }, identity: { email },
profile: { fullName }, profile: { fullName },
@@ -73,20 +72,18 @@ export function SessionDropdown(props: { fKey: SessionDropdownFragment$key }) {
return ( return (
<UserDropdown fullName={fullName} email={email}> <UserDropdown fullName={fullName} email={email}>
{isAuthorized("Organization", "deleteOrganization") && ( {canDelete && (
<UserDropdownItem <UserDropdownItem
to="/me/api-keys" to="/me/api-keys"
icon={IconKey} icon={IconKey}
label={__("API Keys")} label={__("API Keys")}
/> />
)} )}
{isAuthorized("Organization", "listSignableDocuments") && ( <UserDropdownItem
<UserDropdownItem to={`/organizations/${organizationId}/employee`}
to={`/organizations/${organizationId}/employee`} icon={IconPageTextLine}
icon={IconPageTextLine} label={__("My Signatures")}
label={__("My Signatures")} />
/>
)}
<UserDropdownItem <UserDropdownItem
to="mailto:support@getprobo.com" to="mailto:support@getprobo.com"
icon={IconCircleQuestionmark} icon={IconCircleQuestionmark}

View File

@@ -1,24 +1,24 @@
import { import {
IconBank, IconBank,
IconBook, IconBook,
IconBox, IconBox,
IconCalendar1, IconCalendar1,
IconCircleProgress, IconCircleProgress,
IconClock, IconClock,
IconCrossLargeX, IconCrossLargeX,
IconFire3, IconFire3,
IconGroup1, IconGroup1,
IconInboxEmpty, IconInboxEmpty,
IconListStack, IconListStack,
IconMedal, IconMedal,
IconPageCheck, IconPageCheck,
IconPageTextLine, IconPageTextLine,
IconRotateCw, IconRotateCw,
IconSettingsGear2, IconSettingsGear2,
IconShield, IconShield,
IconStore, IconStore,
IconTodo, IconTodo,
SidebarItem, SidebarItem,
} from "@probo/ui"; } from "@probo/ui";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
@@ -27,211 +27,180 @@ import { useFragment } from "react-relay";
import type { SidebarFragment$key } from "/__generated__/iam/SidebarFragment.graphql"; import type { SidebarFragment$key } from "/__generated__/iam/SidebarFragment.graphql";
const fragment = graphql` const fragment = graphql`
fragment SidebarFragment on Identity fragment SidebarFragment on Organization {
@argumentDefinitions(organizationId: { type: "ID!" }) { canListMeetings: permission(action: "core:meeting:list")
canListMeetings: permission( canListTasks: permission(action: "core:task:list")
action: "core:meeting:list" canListMeasures: permission(action: "core:measures:list")
id: $organizationId canListRisks: permission(action: "core:risk:list")
) canListFrameworks: permission(action: "core:frameworks:list")
canListTasks: permission(action: "core:task:list", id: $organizationId) canListPeople: permission(action: "core:people:list")
canListMeasures: permission( canListVendors: permission(action: "core:vendor:list")
action: "core:measures:list" canListDocuments: permission(action: "core:document:list")
id: $organizationId canListAssets: permission(action: "core:asset:list")
) canListData: permission(action: "core:datum:list")
canListRisks: permission(action: "core:risk:list", id: $organizationId) canListAudits: permission(action: "core:audit:list")
canListFrameworks: permission( canListNonconformities: permission(action: "core:nonconformity:list")
action: "core:frameworks:list" canListObligations: permission(action: "core:obligation:list")
id: $organizationId canListContinualImprovements: permission(
) action: "core:continual-improvement:list"
canListPeople: permission(action: "core:people:list", id: $organizationId) )
canListVendors: permission(action: "core:vendor:list", id: $organizationId) canListProcessingActivities: permission(
canListDocuments: permission( action: "core:processing-activity:list"
action: "core:document:list" )
id: $organizationId canListStatesOfApplicability: permission(
) action: "core:state-of-applicability:list"
canListAssets: permission(action: "core:asset:list", id: $organizationId) )
canListData: permission(action: "core:datum:list", id: $organizationId) canListSnapshots: permission(action: "core:snapshot:list")
canListAudits: permission(action: "core:audit:list", id: $organizationId) canGetTrustCenter: permission(action: "core:trust-center:get")
canListNonconformities: permission( canUpdateOrganization: permission(action: "iam:organization:update")
action: "core:nonconformity:list" }
id: $organizationId
)
canListObligations: permission(
action: "core:obligation:list"
id: $organizationId
)
canListContinualImprovements: permission(
action: "core:continual-improvement:list"
id: $organizationId
)
canListProcessingActivities: permission(
action: "core:processing-activity:list"
id: $organizationId
)
canListStatesOfApplicability: permission(
action: "core:state-of-applicability:list"
id: $organizationId
)
canListSnapshots: permission(
action: "core:snapshot:list"
id: $organizationId
)
canGetTrustCenter: permission(
action: "core:trust-center:get"
id: $organizationId
)
canUpdateOrganization: permission(
action: "iam:organization:update"
id: $organizationId
)
}
`; `;
export function Sidebar(props: { fKey: SidebarFragment$key }) { export function Sidebar(props: { fKey: SidebarFragment$key }) {
const { fKey } = props; const { fKey } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const permissions = useFragment<SidebarFragment$key>(fragment, fKey); const organization = useFragment<SidebarFragment$key>(fragment, fKey);
const prefix = `/organizations/${organizationId}`; const prefix = `/organizations/${organizationId}`;
return ( return (
<ul className="space-y-[2px]"> <ul className="space-y-[2px]">
{permissions.canListMeetings && ( {organization.canListMeetings && (
<SidebarItem <SidebarItem
label={__("Meetings")} label={__("Meetings")}
icon={IconCalendar1} icon={IconCalendar1}
to={`${prefix}/meetings`} to={`${prefix}/meetings`}
/> />
)} )}
{permissions.canListTasks && ( {organization.canListTasks && (
<SidebarItem <SidebarItem
label={__("Tasks")} label={__("Tasks")}
icon={IconInboxEmpty} icon={IconInboxEmpty}
to={`${prefix}/tasks`} to={`${prefix}/tasks`}
/> />
)} )}
{permissions.canListMeasures && ( {organization.canListMeasures && (
<SidebarItem <SidebarItem
label={__("Measures")} label={__("Measures")}
icon={IconTodo} icon={IconTodo}
to={`${prefix}/measures`} to={`${prefix}/measures`}
/> />
)} )}
{permissions.canListRisks && ( {organization.canListRisks && (
<SidebarItem <SidebarItem
label={__("Risks")} label={__("Risks")}
icon={IconFire3} icon={IconFire3}
to={`${prefix}/risks`} to={`${prefix}/risks`}
/> />
)} )}
{permissions.canListFrameworks && ( {organization.canListFrameworks && (
<SidebarItem <SidebarItem
label={__("Frameworks")} label={__("Frameworks")}
icon={IconBank} icon={IconBank}
to={`${prefix}/frameworks`} to={`${prefix}/frameworks`}
/> />
)} )}
{permissions.canListPeople && ( {organization.canListPeople && (
<SidebarItem <SidebarItem
label={__("People")} label={__("People")}
icon={IconGroup1} icon={IconGroup1}
to={`${prefix}/people`} to={`${prefix}/people`}
/> />
)} )}
{permissions.canListVendors && ( {organization.canListVendors && (
<SidebarItem <SidebarItem
label={__("Vendors")} label={__("Vendors")}
icon={IconStore} icon={IconStore}
to={`${prefix}/vendors`} to={`${prefix}/vendors`}
/> />
)} )}
{permissions.canListDocuments && ( {organization.canListDocuments && (
<SidebarItem <SidebarItem
label={__("Documents")} label={__("Documents")}
icon={IconPageTextLine} icon={IconPageTextLine}
to={`${prefix}/documents`} to={`${prefix}/documents`}
/> />
)} )}
{permissions.canListAssets && ( {organization.canListAssets && (
<SidebarItem <SidebarItem
label={__("Assets")} label={__("Assets")}
icon={IconBox} icon={IconBox}
to={`${prefix}/assets`} to={`${prefix}/assets`}
/> />
)} )}
{permissions.canListData && ( {organization.canListData && (
<SidebarItem <SidebarItem
label={__("Data")} label={__("Data")}
icon={IconListStack} icon={IconListStack}
to={`${prefix}/data`} to={`${prefix}/data`}
/> />
)} )}
{permissions.canListAudits && ( {organization.canListAudits && (
<SidebarItem <SidebarItem
label={__("Audits")} label={__("Audits")}
icon={IconMedal} icon={IconMedal}
to={`${prefix}/audits`} to={`${prefix}/audits`}
/> />
)} )}
{permissions.canListNonconformities && ( {organization.canListNonconformities && (
<SidebarItem <SidebarItem
label={__("Nonconformities")} label={__("Nonconformities")}
icon={IconCrossLargeX} icon={IconCrossLargeX}
to={`${prefix}/nonconformities`} to={`${prefix}/nonconformities`}
/> />
)} )}
{permissions.canListObligations && ( {organization.canListObligations && (
<SidebarItem <SidebarItem
label={__("Obligations")} label={__("Obligations")}
icon={IconBook} icon={IconBook}
to={`${prefix}/obligations`} to={`${prefix}/obligations`}
/> />
)} )}
{permissions.canListContinualImprovements && ( {organization.canListContinualImprovements && (
<SidebarItem <SidebarItem
label={__("Continual Improvements")} label={__("Continual Improvements")}
icon={IconRotateCw} icon={IconRotateCw}
to={`${prefix}/continual-improvements`} to={`${prefix}/continual-improvements`}
/> />
)} )}
{permissions.canListProcessingActivities && ( {organization.canListProcessingActivities && (
<SidebarItem <SidebarItem
label={__("Processing Activities")} label={__("Processing Activities")}
icon={IconCircleProgress} icon={IconCircleProgress}
to={`${prefix}/processing-activities`} to={`${prefix}/processing-activities`}
/> />
)} )}
{permissions.canListStatesOfApplicability && ( {organization.canListStatesOfApplicability && (
<SidebarItem <SidebarItem
label={__("States of Applicability")} label={__("States of Applicability")}
icon={IconPageCheck} icon={IconPageCheck}
to={`${prefix}/states-of-applicability`} to={`${prefix}/states-of-applicability`}
/> />
)} )}
{permissions.canListSnapshots && ( {organization.canListSnapshots && (
<SidebarItem <SidebarItem
label={__("Snapshots")} label={__("Snapshots")}
icon={IconClock} icon={IconClock}
to={`${prefix}/snapshots`} to={`${prefix}/snapshots`}
/> />
)} )}
{permissions.canGetTrustCenter && ( {organization.canGetTrustCenter && (
<SidebarItem <SidebarItem
label={__("Trust Center")} label={__("Trust Center")}
icon={IconShield} icon={IconShield}
to={`${prefix}/trust-center`} to={`${prefix}/trust-center`}
/> />
)} )}
{permissions.canUpdateOrganization && ( {organization.canUpdateOrganization && (
<SidebarItem <SidebarItem
label={__("Settings")} label={__("Settings")}
icon={IconSettingsGear2} icon={IconSettingsGear2}
to={`${prefix}/settings`} to={`${prefix}/settings`}
/> />
)} )}
</ul> </ul>
); );
} }

View File

@@ -10,16 +10,6 @@ import { InviteUserDialog } from "./_components/InviteUserDialog";
export const membersPageQuery = graphql` export const membersPageQuery = graphql`
query MembersPageQuery($organizationId: ID!) { query MembersPageQuery($organizationId: ID!) {
viewer @required(action: THROW) {
canInviteUser: permission(
action: "iam:membership:create"
id: $organizationId
)
...InvitationListItem_permissionsFragment
@arguments(organizationId: $organizationId)
...MemberListItem_permissionsFragment
@arguments(organizationId: $organizationId)
}
organization: node(id: $organizationId) @required(action: THROW) { organization: node(id: $organizationId) @required(action: THROW) {
__typename __typename
... on Organization { ... on Organization {
@@ -36,6 +26,7 @@ export const membersPageQuery = graphql`
@required(action: THROW) { @required(action: THROW) {
totalCount totalCount
} }
canInviteUser: permission(action: "iam:invitation:create")
} }
} }
} }
@@ -52,7 +43,7 @@ export function MembersPage(props: {
"memberships", "memberships",
); );
const { organization, viewer } = usePreloadedQuery<MembersPageQuery>( const { organization } = usePreloadedQuery<MembersPageQuery>(
membersPageQuery, membersPageQuery,
queryRef, queryRef,
); );
@@ -64,7 +55,7 @@ export function MembersPage(props: {
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Workspace members")}</h2> <h2 className="text-base font-medium">{__("Workspace members")}</h2>
{viewer.canInviteUser && ( {organization.canInviteUser && (
<InviteUserDialog viewerMembershipFKey={organization}> <InviteUserDialog viewerMembershipFKey={organization}>
<Button variant="secondary">{__("Invite member")}</Button> <Button variant="secondary">{__("Invite member")}</Button>
</InviteUserDialog> </InviteUserDialog>
@@ -94,12 +85,10 @@ export function MembersPage(props: {
<Card> <Card>
<div className="px-6 pb-6 pt-6"> <div className="px-6 pb-6 pt-6">
{activeTab === "memberships" && ( {activeTab === "memberships" && <MemberList fKey={organization} />}
<MemberList fKey={organization} permissionsFKey={viewer} />
)}
{activeTab === "invitations" && ( {activeTab === "invitations" && (
<InvitationList fKey={organization} permissionsFKey={viewer} /> <InvitationList fKey={organization} />
)} )}
</div> </div>
</Card> </Card>

View File

@@ -4,7 +4,6 @@ import { useTranslate } from "@probo/i18n";
import { graphql, usePaginationFragment } from "react-relay"; import { graphql, usePaginationFragment } from "react-relay";
import { InvitationListItem } from "./InvitationListItem"; import { InvitationListItem } from "./InvitationListItem";
import type { InvitationListFragment$key } from "/__generated__/iam/InvitationListFragment.graphql"; import type { InvitationListFragment$key } from "/__generated__/iam/InvitationListFragment.graphql";
import type { InvitationListItem_permissionsFragment$key } from "/__generated__/iam/InvitationListItem_permissionsFragment.graphql";
import type { InvitationListFragment_RefetchQuery } from "/__generated__/iam/InvitationListFragment_RefetchQuery.graphql"; import type { InvitationListFragment_RefetchQuery } from "/__generated__/iam/InvitationListFragment_RefetchQuery.graphql";
const fragment = graphql` const fragment = graphql`
@@ -41,11 +40,8 @@ const fragment = graphql`
} }
`; `;
export function InvitationList(props: { export function InvitationList(props: { fKey: InvitationListFragment$key }) {
fKey: InvitationListFragment$key; const { fKey } = props;
permissionsFKey: InvitationListItem_permissionsFragment$key;
}) {
const { fKey, permissionsFKey } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -106,7 +102,6 @@ export function InvitationList(props: {
key={invitation.id} key={invitation.id}
fKey={invitation} fKey={invitation}
onRefetch={refetchInvitations} onRefetch={refetchInvitations}
permissionsFKey={permissionsFKey}
/> />
), ),
) )

View File

@@ -15,7 +15,6 @@ import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { sprintf } from "@probo/helpers"; import { sprintf } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import type { InvitationListItemFragment$key } from "/__generated__/iam/InvitationListItemFragment.graphql"; import type { InvitationListItemFragment$key } from "/__generated__/iam/InvitationListItemFragment.graphql";
import type { InvitationListItem_permissionsFragment$key } from "/__generated__/iam/InvitationListItem_permissionsFragment.graphql";
const fragment = graphql` const fragment = graphql`
fragment InvitationListItemFragment on Invitation { fragment InvitationListItemFragment on Invitation {
@@ -27,16 +26,7 @@ const fragment = graphql`
createdAt createdAt
expiresAt expiresAt
acceptedAt acceptedAt
} canDelete: permission(action: "iam:invitation:delete")
`;
const permissionsFragment = graphql`
fragment InvitationListItem_permissionsFragment on Identity
@argumentDefinitions(organizationId: { type: "ID!" }) {
canDeleteInvitation: permission(
action: "iam:invitation:delete"
id: $organizationId
)
} }
`; `;
@@ -54,10 +44,9 @@ const deleteInvitationMutation = graphql`
export function InvitationListItem(props: { export function InvitationListItem(props: {
connectionId: string; connectionId: string;
fKey: InvitationListItemFragment$key; fKey: InvitationListItemFragment$key;
permissionsFKey: InvitationListItem_permissionsFragment$key;
onRefetch: () => void; onRefetch: () => void;
}) { }) {
const { connectionId, fKey, permissionsFKey } = props; const { connectionId, fKey } = props;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -67,10 +56,6 @@ export function InvitationListItem(props: {
fragment, fragment,
fKey, fKey,
); );
const permissions = useFragment<InvitationListItem_permissionsFragment$key>(
permissionsFragment,
permissionsFKey,
);
const [deleteInvitation, isDeleting] = useMutationWithToasts( const [deleteInvitation, isDeleting] = useMutationWithToasts(
deleteInvitationMutation, deleteInvitationMutation,
@@ -134,7 +119,7 @@ export function InvitationListItem(props: {
{isDeleting ? ( {isDeleting ? (
<Spinner size={16} /> <Spinner size={16} />
) : ( ) : (
permissions.canDeleteInvitation && ( invitation.canDelete && (
<Button <Button
variant="danger" variant="danger"
onClick={handleDelete} onClick={handleDelete}

View File

@@ -5,7 +5,6 @@ import { SortableTable, SortableTh } from "/components/SortableTable";
import type { MemberListFragment_RefetchQuery } from "/__generated__/iam/MemberListFragment_RefetchQuery.graphql"; import type { MemberListFragment_RefetchQuery } from "/__generated__/iam/MemberListFragment_RefetchQuery.graphql";
import { MemberListItem } from "./MemberListItem"; import { MemberListItem } from "./MemberListItem";
import type { MemberListFragment$key } from "/__generated__/iam/MemberListFragment.graphql"; import type { MemberListFragment$key } from "/__generated__/iam/MemberListFragment.graphql";
import type { MemberListItem_permissionsFragment$key } from "/__generated__/iam/MemberListItem_permissionsFragment.graphql";
const fragment = graphql` const fragment = graphql`
fragment MemberListFragment on Organization fragment MemberListFragment on Organization
@@ -40,11 +39,8 @@ const fragment = graphql`
} }
`; `;
export function MemberList(props: { export function MemberList(props: { fKey: MemberListFragment$key }) {
fKey: MemberListFragment$key; const { fKey } = props;
permissionsFKey: MemberListItem_permissionsFragment$key;
}) {
const { fKey, permissionsFKey } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -98,7 +94,6 @@ export function MemberList(props: {
key={membership.id} key={membership.id}
fKey={membership} fKey={membership}
onRefetch={refetchMemberships} onRefetch={refetchMemberships}
permissionsFKey={permissionsFKey}
viewerFKey={membersPagination.data} viewerFKey={membersPagination.data}
/> />
)) ))

View File

@@ -14,7 +14,6 @@ import { useState } from "react";
import { useFragment } from "react-relay"; import { useFragment } from "react-relay";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { MemberListItemFragment$key } from "/__generated__/iam/MemberListItemFragment.graphql"; import type { MemberListItemFragment$key } from "/__generated__/iam/MemberListItemFragment.graphql";
import type { MemberListItem_permissionsFragment$key } from "/__generated__/iam/MemberListItem_permissionsFragment.graphql";
import type { MemberListItem_currentRoleFragment$key } from "/__generated__/iam/MemberListItem_currentRoleFragment.graphql"; import type { MemberListItem_currentRoleFragment$key } from "/__generated__/iam/MemberListItem_currentRoleFragment.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
@@ -32,6 +31,8 @@ const fragment = graphql`
email email
} }
createdAt createdAt
canUpdate: permission(action: "iam:membership:update")
canDelete: permission(action: "iam:membership:delete")
} }
`; `;
@@ -43,20 +44,6 @@ const currentRoleFragment = graphql`
} }
`; `;
const permissionsFragment = graphql`
fragment MemberListItem_permissionsFragment on Identity
@argumentDefinitions(organizationId: { type: "ID!" }) {
canUpdateMembership: permission(
action: "iam:membership:update"
id: $organizationId
)
canDeleteMembership: permission(
action: "iam:membership:delete"
id: $organizationId
)
}
`;
const removeMemberMutation = graphql` const removeMemberMutation = graphql`
mutation MemberListItem_removeMutation( mutation MemberListItem_removeMutation(
$input: RemoveMemberInput! $input: RemoveMemberInput!
@@ -71,11 +58,10 @@ const removeMemberMutation = graphql`
export function MemberListItem(props: { export function MemberListItem(props: {
connectionId: string; connectionId: string;
fKey: MemberListItemFragment$key; fKey: MemberListItemFragment$key;
permissionsFKey: MemberListItem_permissionsFragment$key;
viewerFKey: MemberListItem_currentRoleFragment$key; viewerFKey: MemberListItem_currentRoleFragment$key;
onRefetch: () => void; onRefetch: () => void;
}) { }) {
const { fKey, connectionId, permissionsFKey, viewerFKey } = props; const { fKey, connectionId, viewerFKey } = props;
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { __ } = useTranslate(); const { __ } = useTranslate();
@@ -88,10 +74,6 @@ export function MemberListItem(props: {
currentRoleFragment, currentRoleFragment,
viewerFKey, viewerFKey,
); );
const permissions = useFragment<MemberListItem_permissionsFragment$key>(
permissionsFragment,
permissionsFKey,
);
// Only OWNER can edit OWNER members // Only OWNER can edit OWNER members
const canEditThisRole = const canEditThisRole =
@@ -151,7 +133,7 @@ export function MemberListItem(props: {
className="flex gap-2 justify-end" className="flex gap-2 justify-end"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{permissions.canUpdateMembership && canEditThisRole && ( {membership.canUpdate && canEditThisRole && (
<Button <Button
variant="secondary" variant="secondary"
onClick={() => setDialogOpen(true)} onClick={() => setDialogOpen(true)}
@@ -163,7 +145,7 @@ export function MemberListItem(props: {
{isRemoving ? ( {isRemoving ? (
<Spinner size={16} /> <Spinner size={16} />
) : ( ) : (
permissions.canDeleteMembership && membership.canDelete &&
canEditThisRole && ( canEditThisRole && (
<Button <Button
variant="danger" variant="danger"

View File

@@ -43,6 +43,7 @@ const (
ActionIAMSessionRevokeAll = "iam:session:revoke-all" ActionIAMSessionRevokeAll = "iam:session:revoke-all"
// Invitation actions // Invitation actions
ActionIAMInvitationCreate = "iam:invitation:create"
ActionIAMInvitationGet = "iam:invitation:get" ActionIAMInvitationGet = "iam:invitation:get"
ActionIAMInvitationAccept = "iam:invitation:accept" ActionIAMInvitationAccept = "iam:invitation:accept"
ActionIAMInvitationDelete = "iam:invitation:delete" ActionIAMInvitationDelete = "iam:invitation:delete"
@@ -56,6 +57,7 @@ const (
// Personal API Key actions // Personal API Key actions
ActionIAMPersonalAPIKeyCreate = "iam:personal-api-key:create" ActionIAMPersonalAPIKeyCreate = "iam:personal-api-key:create"
ActionIAMPersonalAPIKeyGet = "iam:personal-api-key:get" ActionIAMPersonalAPIKeyGet = "iam:personal-api-key:get"
ActionIAMPersonalAPIKeyList = "iam:personal-api-key:list"
ActionIAMPersonalAPIKeyUpdate = "iam:personal-api-key:update" ActionIAMPersonalAPIKeyUpdate = "iam:personal-api-key:update"
ActionIAMPersonalAPIKeyDelete = "iam:personal-api-key:delete" ActionIAMPersonalAPIKeyDelete = "iam:personal-api-key:delete"

View File

@@ -39,7 +39,7 @@ var IAMSelfManageIdentityPolicy = policy.NewPolicy(
ActionIAMIdentityListMemberships, ActionIAMIdentityListMemberships,
ActionIAMIdentityListInvitations, ActionIAMIdentityListInvitations,
ActionIAMIdentityListSessions, ActionIAMIdentityListSessions,
ActionIAMIdentityListPersonalAPIKeys, ActionIAMPersonalAPIKeyList,
).WithSID("list-own-associations"). ).WithSID("list-own-associations").
When(policy.Equals("principal.id", "resource.id")), When(policy.Equals("principal.id", "resource.id")),
).WithDescription("Allows users to manage their own identity, sessions, API keys, and view their memberships") ).WithDescription("Allows users to manage their own identity, sessions, API keys, and view their memberships")
@@ -104,6 +104,7 @@ var IAMOwnerPolicy = policy.NewPolicy(
policy.Allow("iam:membership:*").WithSID("full-membership-access"), policy.Allow("iam:membership:*").WithSID("full-membership-access"),
// Can manage invitations // Can manage invitations
policy.Allow( policy.Allow(
ActionIAMInvitationCreate,
ActionIAMInvitationGet, ActionIAMInvitationGet,
ActionIAMInvitationDelete, ActionIAMInvitationDelete,
).WithSID("manage-invitations"), ).WithSID("manage-invitations"),

View File

@@ -17,6 +17,8 @@
package connect_v1 package connect_v1
import ( import (
"context"
"errors"
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -24,6 +26,8 @@ import (
"go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/securecookie"
"go.probo.inc/probo/pkg/server/api/connect/v1/types"
"go.probo.inc/probo/pkg/server/gqlutils"
) )
type ( type (
@@ -67,3 +71,28 @@ func NewMux(logger *log.Logger, svc *iam.Service, cookieConfig securecookie.Conf
return r return r
} }
func (r *Resolver) Permission(ctx context.Context, obj types.Node, action string) (bool, error) {
identity := IdentityFromContext(ctx)
err := r.iam.Authorizer.Authorize(
ctx,
iam.AuthorizeParams{
Principal: identity.ID,
Resource: obj.GetID(),
Action: action,
},
)
if err != nil {
var errInsufficientPermissions *iam.ErrInsufficientPermissions
if errors.As(err, &errInsufficientPermissions) {
return false, nil
}
r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err))
return false, gqlutils.InternalServerError(ctx)
}
return true, nil
}

View File

@@ -167,10 +167,9 @@ type Identity implements Node {
before: CursorKey before: CursorKey
): PersonalAPIKeyConnection @goField(forceResolver: true) @isViewer ): PersonalAPIKeyConnection @goField(forceResolver: true) @isViewer
permission(action: String!, id: ID!): Boolean! permission(action: String!): Boolean!
@goField(forceResolver: true) @goField(forceResolver: true)
@session(required: PRESENT) @session(required: PRESENT)
@isViewer
} }
type MembershipProfile implements Node { type MembershipProfile implements Node {
@@ -178,6 +177,10 @@ type MembershipProfile implements Node {
fullName: String! fullName: String!
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
type Organization implements Node { type Organization implements Node {
@@ -217,6 +220,10 @@ type Organization implements Node {
): SAMLConfigurationConnection @goField(forceResolver: true) ): SAMLConfigurationConnection @goField(forceResolver: true)
viewerMembership: Membership @goField(forceResolver: true) viewerMembership: Membership @goField(forceResolver: true)
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
enum MembershipRole enum MembershipRole
@@ -237,9 +244,12 @@ type Membership implements Node {
profile: MembershipProfile @goField(forceResolver: true) profile: MembershipProfile @goField(forceResolver: true)
organization: Organization @goField(forceResolver: true) organization: Organization @goField(forceResolver: true)
role: MembershipRole! role: MembershipRole!
permissions: [Permission!] @goField(forceResolver: true)
lastSession: Session @goField(forceResolver: true) @isViewer lastSession: Session @goField(forceResolver: true) @isViewer
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
type Invitation implements Node { type Invitation implements Node {
@@ -252,6 +262,10 @@ type Invitation implements Node {
createdAt: Datetime! createdAt: Datetime!
status: InvitationStatus! status: InvitationStatus!
organization: Organization @goField(forceResolver: true) organization: Organization @goField(forceResolver: true)
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
type Session implements Node { type Session implements Node {
@@ -262,6 +276,10 @@ type Session implements Node {
updatedAt: Datetime! updatedAt: Datetime!
createdAt: Datetime! createdAt: Datetime!
expiresAt: Datetime! expiresAt: Datetime!
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
type PersonalAPIKey implements Node { type PersonalAPIKey implements Node {
@@ -272,16 +290,10 @@ type PersonalAPIKey implements Node {
createdAt: Datetime! createdAt: Datetime!
scopes: [TokenScope!]! scopes: [TokenScope!]!
organizations: [Organization!]! organizations: [Organization!]!
}
type Permission implements Node { permission(action: String!): Boolean!
id: ID! @goField(forceResolver: true)
createdAt: Datetime! @session(required: PRESENT)
application: Application!
accessLevel: AccessLevel!
organization: Organization!
principalType: PrincipalType!
principalId: ID!
} }
type Application { type Application {
@@ -305,6 +317,10 @@ type SAMLConfiguration implements Node {
updatedAt: Datetime! updatedAt: Datetime!
testLoginUrl: String! @goField(forceResolver: true) testLoginUrl: String! @goField(forceResolver: true)
attributeMappings: SAMLAttributeMappings! attributeMappings: SAMLAttributeMappings!
permission(action: String!): Boolean!
@goField(forceResolver: true)
@session(required: PRESENT)
} }
type SAMLAttributeMappings { type SAMLAttributeMappings {

File diff suppressed because it is too large Load Diff

View File

@@ -175,6 +175,7 @@ type Invitation struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Status coredata.InvitationStatus `json:"status"` Status coredata.InvitationStatus `json:"status"`
Organization *Organization `json:"organization,omitempty"` Organization *Organization `json:"organization,omitempty"`
Permission bool `json:"permission"`
} }
func (Invitation) IsNode() {} func (Invitation) IsNode() {}
@@ -204,8 +205,8 @@ type Membership struct {
Profile *MembershipProfile `json:"profile,omitempty"` Profile *MembershipProfile `json:"profile,omitempty"`
Organization *Organization `json:"organization,omitempty"` Organization *Organization `json:"organization,omitempty"`
Role coredata.MembershipRole `json:"role"` Role coredata.MembershipRole `json:"role"`
Permissions []*Permission `json:"permissions,omitempty"`
LastSession *Session `json:"lastSession,omitempty"` LastSession *Session `json:"lastSession,omitempty"`
Permission bool `json:"permission"`
} }
func (Membership) IsNode() {} func (Membership) IsNode() {}
@@ -217,10 +218,11 @@ type MembershipEdge struct {
} }
type MembershipProfile struct { type MembershipProfile struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
FullName string `json:"fullName"` FullName string `json:"fullName"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
Permission bool `json:"permission"`
} }
func (MembershipProfile) IsNode() {} func (MembershipProfile) IsNode() {}
@@ -244,6 +246,7 @@ type Organization struct {
Invitations *InvitationConnection `json:"invitations,omitempty"` Invitations *InvitationConnection `json:"invitations,omitempty"`
SamlConfigurations *SAMLConfigurationConnection `json:"samlConfigurations,omitempty"` SamlConfigurations *SAMLConfigurationConnection `json:"samlConfigurations,omitempty"`
ViewerMembership *Membership `json:"viewerMembership,omitempty"` ViewerMembership *Membership `json:"viewerMembership,omitempty"`
Permission bool `json:"permission"`
} }
func (Organization) IsNode() {} func (Organization) IsNode() {}
@@ -269,19 +272,6 @@ type PasswordRequired struct {
func (PasswordRequired) IsAssumeOrganizationSessionResult() {} func (PasswordRequired) IsAssumeOrganizationSessionResult() {}
type Permission struct {
ID gid.GID `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Application *Application `json:"application"`
AccessLevel AccessLevel `json:"accessLevel"`
Organization *Organization `json:"organization"`
PrincipalType PrincipalType `json:"principalType"`
PrincipalID gid.GID `json:"principalId"`
}
func (Permission) IsNode() {}
func (this Permission) GetID() gid.GID { return this.ID }
type PersonalAPIKey struct { type PersonalAPIKey struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@@ -290,6 +280,7 @@ type PersonalAPIKey struct {
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
Scopes []TokenScope `json:"scopes"` Scopes []TokenScope `json:"scopes"`
Organizations []*Organization `json:"organizations"` Organizations []*Organization `json:"organizations"`
Permission bool `json:"permission"`
} }
func (PersonalAPIKey) IsNode() {} func (PersonalAPIKey) IsNode() {}
@@ -384,6 +375,7 @@ type SAMLConfiguration struct {
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
TestLoginURL string `json:"testLoginUrl"` TestLoginURL string `json:"testLoginUrl"`
AttributeMappings *SAMLAttributeMappings `json:"attributeMappings"` AttributeMappings *SAMLAttributeMappings `json:"attributeMappings"`
Permission bool `json:"permission"`
} }
func (SAMLConfiguration) IsNode() {} func (SAMLConfiguration) IsNode() {}
@@ -401,13 +393,14 @@ type SSOAvailability struct {
} }
type Session struct { type Session struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
Identity *Identity `json:"identity,omitempty"` Identity *Identity `json:"identity,omitempty"`
IPAddress string `json:"ipAddress"` IPAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"` UserAgent string `json:"userAgent"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`
Permission bool `json:"permission"`
} }
func (Session) IsNode() {} func (Session) IsNode() {}

View File

@@ -11,7 +11,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/99designs/gqlgen/graphql"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
"go.gearno.de/kit/log" "go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
@@ -138,38 +137,8 @@ func (r *identityResolver) PersonalAPIKeys(ctx context.Context, obj *types.Ident
} }
// Permission is the resolver for the permission field. // Permission is the resolver for the permission field.
func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string, id gid.GID) (bool, error) { func (r *identityResolver) Permission(ctx context.Context, obj *types.Identity, action string) (bool, error) {
err := r.iam.Authorizer.Authorize( return r.Resolver.Permission(ctx, obj, action)
ctx,
iam.AuthorizeParams{
Principal: obj.ID,
Resource: id,
Action: action,
},
)
if err != nil {
var errInsufficientPermissions *iam.ErrInsufficientPermissions
if errors.As(err, &errInsufficientPermissions) {
graphql.AddError(
ctx,
&gqlerror.Error{
Path: graphql.GetPath(ctx),
Message: err.Error(),
Extensions: map[string]interface{}{
"code": "UNAUTHORIZED",
},
},
)
return false, nil
}
r.logger.ErrorCtx(ctx, "cannot authorize", log.Error(err))
return false, gqlutils.InternalServerError(ctx)
}
return true, nil
} }
// Organization is the resolver for the organization field. // Organization is the resolver for the organization field.
@@ -189,6 +158,11 @@ func (r *invitationResolver) Organization(ctx context.Context, obj *types.Invita
return types.NewOrganization(organization), nil return types.NewOrganization(organization), nil
} }
// Permission is the resolver for the permission field.
func (r *invitationResolver) Permission(ctx context.Context, obj *types.Invitation, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (*int, error) { func (r *invitationConnectionResolver) TotalCount(ctx context.Context, obj *types.InvitationConnection) (*int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -269,11 +243,6 @@ func (r *membershipResolver) Organization(ctx context.Context, obj *types.Member
return types.NewOrganization(organization), nil return types.NewOrganization(organization), nil
} }
// Permissions is the resolver for the permissions field.
func (r *membershipResolver) Permissions(ctx context.Context, obj *types.Membership) ([]*types.Permission, error) {
panic("not implemented")
}
// LastSession is the resolver for the lastSession field. // LastSession is the resolver for the lastSession field.
func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) { func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Membership) (*types.Session, error) {
session := SessionFromContext(ctx) session := SessionFromContext(ctx)
@@ -295,6 +264,11 @@ func (r *membershipResolver) LastSession(ctx context.Context, obj *types.Members
return types.NewSession(childSession), nil return types.NewSession(childSession), nil
} }
// Permission is the resolver for the permission field.
func (r *membershipResolver) Permission(ctx context.Context, obj *types.Membership, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *types.MembershipConnection) (*int, error) { func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *types.MembershipConnection) (*int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -320,6 +294,11 @@ func (r *membershipConnectionResolver) TotalCount(ctx context.Context, obj *type
return nil, gqlutils.InternalServerError(ctx) return nil, gqlutils.InternalServerError(ctx)
} }
// Permission is the resolver for the permission field.
func (r *membershipProfileResolver) Permission(ctx context.Context, obj *types.MembershipProfile, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// SignIn is the resolver for the signIn field. // SignIn is the resolver for the signIn field.
func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) { func (r *mutationResolver) SignIn(ctx context.Context, input types.SignInInput) (*types.SignInPayload, error) {
// TODO: handle existing session to only open child session and chnage root session auth method to PASSWORD // TODO: handle existing session to only open child session and chnage root session auth method to PASSWORD
@@ -1128,6 +1107,16 @@ func (r *organizationResolver) ViewerMembership(ctx context.Context, obj *types.
return types.NewMembership(membership), nil return types.NewMembership(membership), nil
} }
// Permission is the resolver for the permission field.
func (r *organizationResolver) Permission(ctx context.Context, obj *types.Organization, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// Permission is the resolver for the permission field.
func (r *personalAPIKeyResolver) Permission(ctx context.Context, obj *types.PersonalAPIKey, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) { func (r *personalAPIKeyConnectionResolver) TotalCount(ctx context.Context, obj *types.PersonalAPIKeyConnection) (*int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -1286,6 +1275,11 @@ func (r *sAMLConfigurationResolver) TestLoginURL(ctx context.Context, obj *types
return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil return r.baseURL.WithPath("/api/connect/v1/saml/2.0/" + obj.ID.String()).MustString(), nil
} }
// Permission is the resolver for the permission field.
func (r *sAMLConfigurationResolver) Permission(ctx context.Context, obj *types.SAMLConfiguration, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) { func (r *sAMLConfigurationConnectionResolver) TotalCount(ctx context.Context, obj *types.SAMLConfigurationConnection) (*int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -1319,6 +1313,11 @@ func (r *sessionResolver) Identity(ctx context.Context, obj *types.Session) (*ty
return types.NewIdentity(identity), nil return types.NewIdentity(identity), nil
} }
// Permission is the resolver for the permission field.
func (r *sessionResolver) Permission(ctx context.Context, obj *types.Session, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action)
}
// TotalCount is the resolver for the totalCount field. // TotalCount is the resolver for the totalCount field.
func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) { func (r *sessionConnectionResolver) TotalCount(ctx context.Context, obj *types.SessionConnection) (*int, error) {
switch obj.Resolver.(type) { switch obj.Resolver.(type) {
@@ -1355,12 +1354,20 @@ func (r *Resolver) MembershipConnection() schema.MembershipConnectionResolver {
return &membershipConnectionResolver{r} return &membershipConnectionResolver{r}
} }
// MembershipProfile returns schema.MembershipProfileResolver implementation.
func (r *Resolver) MembershipProfile() schema.MembershipProfileResolver {
return &membershipProfileResolver{r}
}
// Mutation returns schema.MutationResolver implementation. // Mutation returns schema.MutationResolver implementation.
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
// Organization returns schema.OrganizationResolver implementation. // Organization returns schema.OrganizationResolver implementation.
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} } func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
// PersonalAPIKey returns schema.PersonalAPIKeyResolver implementation.
func (r *Resolver) PersonalAPIKey() schema.PersonalAPIKeyResolver { return &personalAPIKeyResolver{r} }
// PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation. // PersonalAPIKeyConnection returns schema.PersonalAPIKeyConnectionResolver implementation.
func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver { func (r *Resolver) PersonalAPIKeyConnection() schema.PersonalAPIKeyConnectionResolver {
return &personalAPIKeyConnectionResolver{r} return &personalAPIKeyConnectionResolver{r}
@@ -1392,11 +1399,25 @@ type invitationResolver struct{ *Resolver }
type invitationConnectionResolver struct{ *Resolver } type invitationConnectionResolver struct{ *Resolver }
type membershipResolver struct{ *Resolver } type membershipResolver struct{ *Resolver }
type membershipConnectionResolver struct{ *Resolver } type membershipConnectionResolver struct{ *Resolver }
type membershipProfileResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver } type mutationResolver struct{ *Resolver }
type organizationResolver struct{ *Resolver } type organizationResolver struct{ *Resolver }
type personalAPIKeyResolver struct{ *Resolver }
type personalAPIKeyConnectionResolver struct{ *Resolver } type personalAPIKeyConnectionResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
type sAMLConfigurationResolver struct{ *Resolver } type sAMLConfigurationResolver struct{ *Resolver }
type sAMLConfigurationConnectionResolver struct{ *Resolver } type sAMLConfigurationConnectionResolver struct{ *Resolver }
type sessionResolver struct{ *Resolver } type sessionResolver struct{ *Resolver }
type sessionConnectionResolver struct{ *Resolver } type sessionConnectionResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
/*
func (r *membershipResolver) Permissions(ctx context.Context, obj *types.Membership) ([]*types.Permission, error) {
panic("not implemented")
}
*/

View File

@@ -348,17 +348,6 @@ func (r *Resolver) Permission(ctx context.Context, obj types.Node, action string
if err != nil { if err != nil {
var errInsufficientPermissions *iam.ErrInsufficientPermissions var errInsufficientPermissions *iam.ErrInsufficientPermissions
if errors.As(err, &errInsufficientPermissions) { if errors.As(err, &errInsufficientPermissions) {
graphql.AddError(
ctx,
&gqlerror.Error{
Path: graphql.GetPath(ctx),
Message: err.Error(),
Extensions: map[string]interface{}{
"code": "UNAUTHORIZED",
},
},
)
return false, nil return false, nil
} }