@stigmer/react 3.30.0 → 3.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/iam-policy/OrgMembersPanel.d.ts +7 -0
- package/iam-policy/OrgMembersPanel.d.ts.map +1 -1
- package/iam-policy/OrgMembersPanel.js +28 -12
- package/iam-policy/OrgMembersPanel.js.map +1 -1
- package/iam-policy/RoleSelector.d.ts +7 -1
- package/iam-policy/RoleSelector.d.ts.map +1 -1
- package/iam-policy/RoleSelector.js +2 -2
- package/iam-policy/RoleSelector.js.map +1 -1
- package/iam-policy/useOwnerAssignment.d.ts +23 -0
- package/iam-policy/useOwnerAssignment.d.ts.map +1 -0
- package/iam-policy/useOwnerAssignment.js +23 -0
- package/iam-policy/useOwnerAssignment.js.map +1 -0
- package/iam-policy/useRoleSelector.d.ts +6 -1
- package/iam-policy/useRoleSelector.d.ts.map +1 -1
- package/iam-policy/useRoleSelector.js +11 -3
- package/iam-policy/useRoleSelector.js.map +1 -1
- package/invitation/InvitationManager.d.ts.map +1 -1
- package/invitation/InvitationManager.js +5 -1
- package/invitation/InvitationManager.js.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts +5 -1
- package/oauth-app/OAuthAppDetailPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppDetailPanel.js +10 -5
- package/oauth-app/OAuthAppDetailPanel.js.map +1 -1
- package/oauth-app/OAuthAppListPanel.d.ts +11 -3
- package/oauth-app/OAuthAppListPanel.d.ts.map +1 -1
- package/oauth-app/OAuthAppListPanel.js +4 -4
- package/oauth-app/OAuthAppListPanel.js.map +1 -1
- package/oauth-app/copy.d.ts +15 -0
- package/oauth-app/copy.d.ts.map +1 -0
- package/oauth-app/copy.js +15 -0
- package/oauth-app/copy.js.map +1 -0
- package/oauth-app/useDeleteOAuthApp.d.ts +4 -3
- package/oauth-app/useDeleteOAuthApp.d.ts.map +1 -1
- package/oauth-app/useDeleteOAuthApp.js +4 -3
- package/oauth-app/useDeleteOAuthApp.js.map +1 -1
- package/oauth-app/useOAuthAppList.d.ts +6 -4
- package/oauth-app/useOAuthAppList.d.ts.map +1 -1
- package/oauth-app/useOAuthAppList.js +6 -4
- package/oauth-app/useOAuthAppList.js.map +1 -1
- package/package.json +5 -5
- package/platform-client/PlatformClientDetailPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientDetailPanel.js +9 -4
- package/platform-client/PlatformClientDetailPanel.js.map +1 -1
- package/platform-client/PlatformClientListPanel.d.ts.map +1 -1
- package/platform-client/PlatformClientListPanel.js +2 -1
- package/platform-client/PlatformClientListPanel.js.map +1 -1
- package/platform-client/expiry.d.ts +4 -0
- package/platform-client/expiry.d.ts.map +1 -0
- package/platform-client/expiry.js +18 -0
- package/platform-client/expiry.js.map +1 -0
- package/settings/MembersSection.d.ts.map +1 -1
- package/settings/MembersSection.js +1 -1
- package/settings/MembersSection.js.map +1 -1
- package/settings/OAuthAppsSection.d.ts +9 -1
- package/settings/OAuthAppsSection.d.ts.map +1 -1
- package/settings/OAuthAppsSection.js +17 -3
- package/settings/OAuthAppsSection.js.map +1 -1
- package/src/iam-policy/OrgMembersPanel.tsx +38 -8
- package/src/iam-policy/RoleSelector.tsx +8 -0
- package/src/iam-policy/__tests__/OrgMembersPanel.test.tsx +139 -0
- package/src/iam-policy/__tests__/useOwnerAssignment.test.tsx +55 -0
- package/src/iam-policy/useOwnerAssignment.ts +40 -0
- package/src/iam-policy/useRoleSelector.ts +14 -3
- package/src/invitation/InvitationManager.tsx +5 -0
- package/src/oauth-app/OAuthAppDetailPanel.tsx +39 -24
- package/src/oauth-app/OAuthAppListPanel.tsx +12 -3
- package/src/oauth-app/__tests__/OAuthAppDetailPanel.test.tsx +57 -9
- package/src/oauth-app/copy.ts +16 -0
- package/src/oauth-app/useDeleteOAuthApp.ts +4 -3
- package/src/oauth-app/useOAuthAppList.ts +6 -4
- package/src/platform-client/PlatformClientDetailPanel.tsx +28 -5
- package/src/platform-client/PlatformClientListPanel.tsx +2 -1
- package/src/platform-client/__tests__/PlatformClientDetailPanel.test.tsx +45 -3
- package/src/platform-client/__tests__/expiry.test.ts +37 -0
- package/src/platform-client/expiry.ts +22 -0
- package/src/settings/MembersSection.tsx +2 -1
- package/src/settings/OAuthAppsSection.tsx +21 -3
- package/src/settings/__tests__/OAuthAppsSection.test.tsx +133 -0
|
@@ -8,6 +8,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../internal/tooltip.js"
|
|
|
8
8
|
import { usePlatformClientList } from "./usePlatformClientList.js";
|
|
9
9
|
import { useDeletePlatformClient } from "./useDeletePlatformClient.js";
|
|
10
10
|
import { SpinnerIcon } from "../internal/SpinnerIcon.js";
|
|
11
|
+
import { isPlatformClientExpired } from "./expiry.js";
|
|
11
12
|
/**
|
|
12
13
|
* Displays a list of {@link PlatformClient} resources for an
|
|
13
14
|
* organization with inline delete confirmation.
|
|
@@ -92,7 +93,7 @@ function ExpiryBadge({ spec }) {
|
|
|
92
93
|
}
|
|
93
94
|
if (spec?.expiresAt) {
|
|
94
95
|
const date = timestampDate(spec.expiresAt);
|
|
95
|
-
const isExpired =
|
|
96
|
+
const isExpired = isPlatformClientExpired(spec);
|
|
96
97
|
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("time", { dateTime: date.toISOString(), className: cn("stg:text-[0.65rem]", isExpired ? "stg:text-destructive stg:font-medium" : "stg:text-muted-foreground") }), children: isExpired ? "Expired" : `Exp ${formatShortDate(date)}` }), _jsx(TooltipContent, { side: "top", children: `Expires ${date.toISOString()}` })] }));
|
|
97
98
|
}
|
|
98
99
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlatformClientListPanel.js","sourceRoot":"","sources":["../../src/platform-client/PlatformClientListPanel.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC9C,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"PlatformClientListPanel.js","sourceRoot":"","sources":["../../src/platform-client/PlatformClientListPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PlatformClient } from "@stigmer/protos/ai/stigmer/iam/platformclient/v1/api_pb";
|
|
2
|
+
/** Whether the client's secret no longer mints (its expiry has passed). */
|
|
3
|
+
export declare function isPlatformClientExpired(spec: PlatformClient["spec"], now?: Date): boolean;
|
|
4
|
+
//# sourceMappingURL=expiry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.d.ts","sourceRoot":"","sources":["../../src/platform-client/expiry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yDAAyD,CAAC;AAE9F,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,EAC5B,GAAG,GAAE,IAAiB,GACrB,OAAO,CAKT"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one rule for "this PlatformClient has expired", shared by the list's
|
|
3
|
+
* expiry badge, the detail view and the rotate confirmation so the three
|
|
4
|
+
* never disagree. It is the server's rule, the one the mint refuses by: an
|
|
5
|
+
* expiry is set, `never_expires` is off, and the expiry has passed.
|
|
6
|
+
*
|
|
7
|
+
* Package-internal on purpose: every name the barrel exports is a public
|
|
8
|
+
* API contract, and no platform builder has asked for this one.
|
|
9
|
+
*/
|
|
10
|
+
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
11
|
+
/** Whether the client's secret no longer mints (its expiry has passed). */
|
|
12
|
+
export function isPlatformClientExpired(spec, now = new Date()) {
|
|
13
|
+
if (spec === undefined || spec.neverExpires || spec.expiresAt === undefined) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return timestampDate(spec.expiresAt).getTime() < now.getTime();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=expiry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expiry.js","sourceRoot":"","sources":["../../src/platform-client/expiry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,2EAA2E;AAC3E,MAAM,UAAU,uBAAuB,CACrC,IAA4B,EAC5B,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;AACjE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MembersSection.d.ts","sourceRoot":"","sources":["../../src/settings/MembersSection.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,
|
|
1
|
+
{"version":3,"file":"MembersSection.d.ts","sourceRoot":"","sources":["../../src/settings/MembersSection.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,4CA4D7B"}
|
|
@@ -25,6 +25,6 @@ export function MembersSection() {
|
|
|
25
25
|
const orgSlug = activeOrg?.metadata?.slug ?? "";
|
|
26
26
|
const { identityProviders } = useIdentityProviderList(idpAvailable && orgSlug ? orgSlug : null);
|
|
27
27
|
const hasJitProviders = identityProviders.some((idp) => idp.spec?.autoProvisionAccounts || idp.spec?.isSsoProvider);
|
|
28
|
-
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold", children: "Members" }), _jsxs("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: ["Manage who has access to this organization and what they can do. Members can be granted owner, admin, member, or viewer roles.", !invitationsAvailable && (_jsxs(_Fragment, { children: [" ", "There are no invitations on this edition: people join this organization the first time they sign in, and you adjust their roles here."] }))] }), !orgId ? (_jsx("p", { className: "stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs", children: "Select an organization to manage members." })) : (_jsxs(_Fragment, { children: [hasJitProviders && (_jsx("div", { className: "stg:mb-3 stg:rounded-md stg:border stg:border-border-muted stg:bg-muted-faint stg:px-3 stg:py-2", children: _jsx("p", { className: "stg:text-[0.65rem] stg:text-muted-foreground", children: "This organization has identity providers with auto-provisioning enabled. Members may appear here automatically when users authenticate via federated identity." }) })), _jsx(OrgMembersPanel, { orgId: orgId })] }))] }));
|
|
28
|
+
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:mb-1 stg:text-sm stg:font-semibold", children: "Members" }), _jsxs("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: ["Manage who has access to this organization and what they can do. Members can be granted owner, admin, member, or viewer roles; only an owner grants or removes owner.", !invitationsAvailable && (_jsxs(_Fragment, { children: [" ", "There are no invitations on this edition: people join this organization the first time they sign in, and you adjust their roles here."] }))] }), !orgId ? (_jsx("p", { className: "stg:text-muted-foreground stg:py-4 stg:text-center stg:text-xs", children: "Select an organization to manage members." })) : (_jsxs(_Fragment, { children: [hasJitProviders && (_jsx("div", { className: "stg:mb-3 stg:rounded-md stg:border stg:border-border-muted stg:bg-muted-faint stg:px-3 stg:py-2", children: _jsx("p", { className: "stg:text-[0.65rem] stg:text-muted-foreground", children: "This organization has identity providers with auto-provisioning enabled. Members may appear here automatically when users authenticate via federated identity." }) })), _jsx(OrgMembersPanel, { orgId: orgId })] }))] }));
|
|
29
29
|
}
|
|
30
30
|
//# sourceMappingURL=MembersSection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MembersSection.js","sourceRoot":"","sources":["../../src/settings/MembersSection.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iDAAiD,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC;IAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC;IAC/B,MAAM,oBAAoB,GAAG,oBAAoB,CAC/C,eAAe,CAAC,UAAU,CAC3B,CAAC;IACF,MAAM,YAAY,GAAG,oBAAoB,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;IAEhD,MAAM,EAAE,iBAAiB,EAAE,GAAG,uBAAuB,CACnD,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACzC,CAAC;IAEF,MAAM,eAAe,GAAG,iBAAiB,CAAC,IAAI,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAqB,IAAI,GAAG,CAAC,IAAI,EAAE,aAAa,CACpE,CAAC;IAEF,OAAO,CACL,sCAA0B,SAAS,aACjC,aACE,EAAE,EAAE,SAAS,EACb,SAAS,EAAC,4DAA4D,wBAGnE,EACL,aAAG,SAAS,EAAC,gDAAgD
|
|
1
|
+
{"version":3,"file":"MembersSection.js","sourceRoot":"","sources":["../../src/settings/MembersSection.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iDAAiD,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC;IAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC;IAC/B,MAAM,oBAAoB,GAAG,oBAAoB,CAC/C,eAAe,CAAC,UAAU,CAC3B,CAAC;IACF,MAAM,YAAY,GAAG,oBAAoB,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,SAAS,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC;IAEhD,MAAM,EAAE,iBAAiB,EAAE,GAAG,uBAAuB,CACnD,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CACzC,CAAC;IAEF,MAAM,eAAe,GAAG,iBAAiB,CAAC,IAAI,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAqB,IAAI,GAAG,CAAC,IAAI,EAAE,aAAa,CACpE,CAAC;IAEF,OAAO,CACL,sCAA0B,SAAS,aACjC,aACE,EAAE,EAAE,SAAS,EACb,SAAS,EAAC,4DAA4D,wBAGnE,EACL,aAAG,SAAS,EAAC,gDAAgD,sLAI1D,CAAC,oBAAoB,IAAI,CACxB,8BACG,GAAG,6IAIH,CACJ,IACC,EAEH,CAAC,KAAK,CAAC,CAAC,CAAC,CACR,YAAG,SAAS,EAAC,gEAAgE,0DAEzE,CACL,CAAC,CAAC,CAAC,CACF,8BACG,eAAe,IAAI,CAClB,cAAK,SAAS,EAAC,iGAAiG,YAC9G,YAAG,SAAS,EAAC,8CAA8C,+KAIvD,GACA,CACP,EACD,KAAC,eAAe,IAAC,KAAK,EAAE,KAAK,GAAI,IAChC,CACJ,IACO,CACX,CAAC;AACJ,CAAC"}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Settings section for organization OAuth app credentials.
|
|
3
|
+
*
|
|
4
|
+
* "New OAuth app" is offered once the server confirms the caller holds
|
|
5
|
+
* `can_create_oauth_app` on the organization. The list shows only the apps
|
|
6
|
+
* the caller may view (each app's creator and the organization's admins),
|
|
7
|
+
* so a caller who may not create OAuth apps is told that the
|
|
8
|
+
* organization's admins manage them rather than that none is configured.
|
|
9
|
+
*/
|
|
2
10
|
export declare function OAuthAppsSection(): import("react/jsx-runtime").JSX.Element;
|
|
3
11
|
//# sourceMappingURL=OAuthAppsSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthAppsSection.d.ts","sourceRoot":"","sources":["../../src/settings/OAuthAppsSection.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OAuthAppsSection.d.ts","sourceRoot":"","sources":["../../src/settings/OAuthAppsSection.tsx"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,4CA2F/B"}
|
|
@@ -4,14 +4,28 @@ import { useCallback, useId, useRef, useState } from "react";
|
|
|
4
4
|
import { OAuthAppListPanel } from "../oauth-app/OAuthAppListPanel.js";
|
|
5
5
|
import { CreateOAuthAppForm } from "../oauth-app/CreateOAuthAppForm.js";
|
|
6
6
|
import { OAuthAppDetailPanel } from "../oauth-app/OAuthAppDetailPanel.js";
|
|
7
|
+
import { OAUTH_APPS_MANAGED_BY_ADMINS } from "../oauth-app/copy.js";
|
|
7
8
|
import { useResourceAvailable, ApiResourceKind } from "../deployment-mode.js";
|
|
9
|
+
import { useCheckPermission } from "../iam-policy/useCheckPermission.js";
|
|
8
10
|
import { CloudFeatureNotice } from "../internal/CloudFeatureNotice.js";
|
|
9
|
-
import { useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
10
|
-
/**
|
|
11
|
+
import { useActiveOrgId, useActiveOrgSlug } from "../organization/OrgProvider.js";
|
|
12
|
+
/**
|
|
13
|
+
* Settings section for organization OAuth app credentials.
|
|
14
|
+
*
|
|
15
|
+
* "New OAuth app" is offered once the server confirms the caller holds
|
|
16
|
+
* `can_create_oauth_app` on the organization. The list shows only the apps
|
|
17
|
+
* the caller may view (each app's creator and the organization's admins),
|
|
18
|
+
* so a caller who may not create OAuth apps is told that the
|
|
19
|
+
* organization's admins manage them rather than that none is configured.
|
|
20
|
+
*/
|
|
11
21
|
export function OAuthAppsSection() {
|
|
12
22
|
const headingId = useId();
|
|
13
23
|
const org = useActiveOrgSlug();
|
|
24
|
+
const orgId = useActiveOrgId();
|
|
14
25
|
const oauthAppsAvailable = useResourceAvailable(ApiResourceKind.oauth_app);
|
|
26
|
+
const createCheck = useCheckPermission(oauthAppsAvailable && orgId ? { kind: "organization", id: orgId } : null, "can_create_oauth_app");
|
|
27
|
+
const canCreate = !createCheck.isLoading && createCheck.allowed;
|
|
28
|
+
const deniedCreate = !createCheck.isLoading && !createCheck.allowed;
|
|
15
29
|
const [flow, setFlow] = useState({ phase: "idle" });
|
|
16
30
|
const listRefetchRef = useRef(null);
|
|
17
31
|
const handleRefetchRef = useCallback((refetch) => {
|
|
@@ -29,6 +43,6 @@ export function OAuthAppsSection() {
|
|
|
29
43
|
listRefetchRef.current?.();
|
|
30
44
|
setFlow({ phase: "idle" });
|
|
31
45
|
}, []);
|
|
32
|
-
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsxs("div", { className: "stg:mb-3 stg:flex stg:items-center stg:justify-between", children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "OAuth Apps" }), oauthAppsAvailable && org && flow.phase === "idle" && (_jsx("button", { type: "button", onClick: () => setFlow({ phase: "creating" }), className: "stg:text-primary stg:hover:text-foreground stg:text-xs stg:font-medium stg:transition-colors", children: "+ New OAuth app" }))] }), _jsx("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: "OAuth app credentials configured for your organization. Create new apps here or bring your own from an MCP server's detail page." }), !oauthAppsAvailable ? (_jsx(CloudFeatureNotice, { children: "OAuth apps are not available in local mode. Bring-your-own-app OAuth requires the cloud platform." })) : flow.phase === "creating" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(CreateOAuthAppForm, { org: org, onCreated: handleCreated, onCancel: () => setFlow({ phase: "idle" }) }) })) : flow.phase === "editing" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(OAuthAppDetailPanel, { oauthApp: flow.oauthApp, onUpdated: handleUpdated, onDeleted: handleDeleted, onBack: () => setFlow({ phase: "idle" }) }) })) : (_jsx(OAuthAppListPanel, { org: org, onEdit: (app) => setFlow({ phase: "editing", oauthApp: app }), onRefetchRef: handleRefetchRef }))] }));
|
|
46
|
+
return (_jsxs("section", { "aria-labelledby": headingId, children: [_jsxs("div", { className: "stg:mb-3 stg:flex stg:items-center stg:justify-between", children: [_jsx("h2", { id: headingId, className: "stg:text-foreground stg:text-sm stg:font-semibold", children: "OAuth Apps" }), oauthAppsAvailable && org && canCreate && flow.phase === "idle" && (_jsx("button", { type: "button", onClick: () => setFlow({ phase: "creating" }), className: "stg:text-primary stg:hover:text-foreground stg:text-xs stg:font-medium stg:transition-colors", children: "+ New OAuth app" }))] }), _jsx("p", { className: "stg:text-muted-foreground stg:mb-4 stg:text-xs", children: "OAuth app credentials configured for your organization. Create new apps here or bring your own from an MCP server's detail page." }), !oauthAppsAvailable ? (_jsx(CloudFeatureNotice, { children: "OAuth apps are not available in local mode. Bring-your-own-app OAuth requires the cloud platform." })) : flow.phase === "creating" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(CreateOAuthAppForm, { org: org, onCreated: handleCreated, onCancel: () => setFlow({ phase: "idle" }) }) })) : flow.phase === "editing" ? (_jsx("div", { className: "stg:border-border stg:bg-card stg:rounded-lg stg:border stg:p-4", children: _jsx(OAuthAppDetailPanel, { oauthApp: flow.oauthApp, onUpdated: handleUpdated, onDeleted: handleDeleted, onBack: () => setFlow({ phase: "idle" }) }) })) : (_jsx(OAuthAppListPanel, { org: org, onEdit: (app) => setFlow({ phase: "editing", oauthApp: app }), onRefetchRef: handleRefetchRef, emptyState: deniedCreate ? OAUTH_APPS_MANAGED_BY_ADMINS : undefined }))] }));
|
|
33
47
|
}
|
|
34
48
|
//# sourceMappingURL=OAuthAppsSection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthAppsSection.js","sourceRoot":"","sources":["../../src/settings/OAuthAppsSection.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"OAuthAppsSection.js","sourceRoot":"","sources":["../../src/settings/OAuthAppsSection.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import { create } from "@bufbuild/protobuf";
|
|
5
5
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
6
|
-
import
|
|
6
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
7
7
|
import type { PrincipalAccess, RoleGrant } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
|
|
8
8
|
import {
|
|
9
9
|
IamPolicySpecSchema,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
iamRoleToString,
|
|
17
17
|
} from "@stigmer/sdk";
|
|
18
18
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../internal/tooltip.js";
|
|
19
|
+
import { useOwnerAssignment } from "./useOwnerAssignment.js";
|
|
19
20
|
import { useResourceAccess } from "./useResourceAccess.js";
|
|
20
21
|
import { usePrincipalsCount } from "./usePrincipalsCount.js";
|
|
21
22
|
import { useWhoAmI } from "./useWhoAmI.js";
|
|
@@ -52,6 +53,13 @@ export interface OrgMembersPanelProps {
|
|
|
52
53
|
* `identityAccount.whoAmI()` for self-protection (disabling
|
|
53
54
|
* destructive actions on yourself).
|
|
54
55
|
*
|
|
56
|
+
* Owner is assigned by owners: the server asks `can_assign_roles` on the
|
|
57
|
+
* organization before any change to the owner role. The panel asks the
|
|
58
|
+
* same question once, and for a caller without it offers no owner in the
|
|
59
|
+
* role picker and no change or removal on an owner's row. A role change
|
|
60
|
+
* grants the new role before it revokes the old one, so a refused step
|
|
61
|
+
* leaves the member with the role they had.
|
|
62
|
+
*
|
|
55
63
|
* All visual properties flow through `--stgm-*` design tokens.
|
|
56
64
|
*
|
|
57
65
|
* @example
|
|
@@ -69,6 +77,7 @@ export function OrgMembersPanel({
|
|
|
69
77
|
const { count, refetch: refetchCount } = usePrincipalsCount(orgId || null);
|
|
70
78
|
const { account: currentAccount } = useWhoAmI();
|
|
71
79
|
const currentAccountId = currentAccount?.metadata?.id ?? null;
|
|
80
|
+
const { canAssignOwner, unassignable } = useOwnerAssignment(orgId || null);
|
|
72
81
|
|
|
73
82
|
const [actionMemberId, setActionMemberId] = useState<string | null>(null);
|
|
74
83
|
|
|
@@ -133,6 +142,8 @@ export function OrgMembersPanel({
|
|
|
133
142
|
entry={entry}
|
|
134
143
|
orgId={orgId}
|
|
135
144
|
isSelf={memberId === currentAccountId}
|
|
145
|
+
canAssignOwner={canAssignOwner}
|
|
146
|
+
unassignable={unassignable}
|
|
136
147
|
isActioning={actionMemberId === memberId}
|
|
137
148
|
onStartAction={() => setActionMemberId(memberId)}
|
|
138
149
|
onEndAction={() => setActionMemberId(null)}
|
|
@@ -154,6 +165,8 @@ function MemberRow({
|
|
|
154
165
|
entry,
|
|
155
166
|
orgId,
|
|
156
167
|
isSelf,
|
|
168
|
+
canAssignOwner,
|
|
169
|
+
unassignable,
|
|
157
170
|
isActioning,
|
|
158
171
|
onStartAction,
|
|
159
172
|
onEndAction,
|
|
@@ -162,6 +175,10 @@ function MemberRow({
|
|
|
162
175
|
entry: PrincipalAccess;
|
|
163
176
|
orgId: string;
|
|
164
177
|
isSelf: boolean;
|
|
178
|
+
/** Whether the caller may grant, revoke or remove the owner role here. */
|
|
179
|
+
canAssignOwner: boolean;
|
|
180
|
+
/** The roles the caller may not assign, left out of the role picker. */
|
|
181
|
+
unassignable: readonly IamRole[];
|
|
165
182
|
isActioning: boolean;
|
|
166
183
|
onStartAction: () => void;
|
|
167
184
|
onEndAction: () => void;
|
|
@@ -177,6 +194,11 @@ function MemberRow({
|
|
|
177
194
|
const email = principal?.email ?? "";
|
|
178
195
|
const initial = name.charAt(0).toUpperCase();
|
|
179
196
|
const directRoles = entry.roles.filter((r) => !r.isInherited);
|
|
197
|
+
const holdsOwner = directRoles.some(
|
|
198
|
+
(grant) => grant.role?.code === iamRoleToString(IamRole.owner),
|
|
199
|
+
);
|
|
200
|
+
// Changing or removing an owner changes the owner role: an owner's act.
|
|
201
|
+
const mayAct = !isSelf && (canAssignOwner || !holdsOwner);
|
|
180
202
|
|
|
181
203
|
const startAction = (type: "remove" | "change-role") => {
|
|
182
204
|
setActionType(type);
|
|
@@ -208,6 +230,7 @@ function MemberRow({
|
|
|
208
230
|
<ChangeRoleRow
|
|
209
231
|
entry={entry}
|
|
210
232
|
orgId={orgId}
|
|
233
|
+
omitRoles={unassignable}
|
|
211
234
|
onDone={() => {
|
|
212
235
|
cancelAction();
|
|
213
236
|
onMutated();
|
|
@@ -255,7 +278,7 @@ function MemberRow({
|
|
|
255
278
|
</div>
|
|
256
279
|
|
|
257
280
|
{/* Actions */}
|
|
258
|
-
{
|
|
281
|
+
{mayAct && (
|
|
259
282
|
<div className="stg:flex stg:shrink-0 stg:items-center stg:gap-1">
|
|
260
283
|
<button
|
|
261
284
|
type="button"
|
|
@@ -398,11 +421,14 @@ function RemoveConfirmation({
|
|
|
398
421
|
function ChangeRoleRow({
|
|
399
422
|
entry,
|
|
400
423
|
orgId,
|
|
424
|
+
omitRoles,
|
|
401
425
|
onDone,
|
|
402
426
|
onCancel,
|
|
403
427
|
}: {
|
|
404
428
|
entry: PrincipalAccess;
|
|
405
429
|
orgId: string;
|
|
430
|
+
/** The roles the caller may not assign, left out of the picker. */
|
|
431
|
+
omitRoles: readonly IamRole[];
|
|
406
432
|
onDone: () => void;
|
|
407
433
|
onCancel: () => void;
|
|
408
434
|
}) {
|
|
@@ -429,7 +455,10 @@ function ChangeRoleRow({
|
|
|
429
455
|
setError(null);
|
|
430
456
|
|
|
431
457
|
try {
|
|
432
|
-
|
|
458
|
+
// Grant first, then revoke: a refusal of either step (the owner rule,
|
|
459
|
+
// an organization's last owner) leaves the member holding a role, never
|
|
460
|
+
// none, and the revoke is the one that can be retried.
|
|
461
|
+
const createSpec = create(IamPolicySpecSchema, {
|
|
433
462
|
principal: create(ApiResourceRefSchema, {
|
|
434
463
|
kind: "identity_account",
|
|
435
464
|
id: memberId,
|
|
@@ -438,11 +467,11 @@ function ChangeRoleRow({
|
|
|
438
467
|
kind: "organization",
|
|
439
468
|
id: orgId,
|
|
440
469
|
}),
|
|
441
|
-
relation:
|
|
470
|
+
relation: iamRoleToString(selectedRole),
|
|
442
471
|
});
|
|
443
|
-
await
|
|
472
|
+
await createPolicy(createSpec);
|
|
444
473
|
|
|
445
|
-
const
|
|
474
|
+
const deleteSpec = create(IamPolicySpecSchema, {
|
|
446
475
|
principal: create(ApiResourceRefSchema, {
|
|
447
476
|
kind: "identity_account",
|
|
448
477
|
id: memberId,
|
|
@@ -451,9 +480,9 @@ function ChangeRoleRow({
|
|
|
451
480
|
kind: "organization",
|
|
452
481
|
id: orgId,
|
|
453
482
|
}),
|
|
454
|
-
relation:
|
|
483
|
+
relation: currentRoleCode,
|
|
455
484
|
});
|
|
456
|
-
await
|
|
485
|
+
await deletePolicy(deleteSpec);
|
|
457
486
|
|
|
458
487
|
onDone();
|
|
459
488
|
} catch (err) {
|
|
@@ -482,6 +511,7 @@ function ChangeRoleRow({
|
|
|
482
511
|
kind={ApiResourceKind.organization}
|
|
483
512
|
selected={selectedRole ?? currentRole}
|
|
484
513
|
onSelect={setSelectedRole}
|
|
514
|
+
omitRoles={omitRoles}
|
|
485
515
|
disabled={isWorking}
|
|
486
516
|
/>
|
|
487
517
|
|
|
@@ -25,6 +25,12 @@ export interface RoleSelectorProps {
|
|
|
25
25
|
* Defaults to a person.
|
|
26
26
|
*/
|
|
27
27
|
readonly granteeKind?: GranteeKind;
|
|
28
|
+
/**
|
|
29
|
+
* Roles to leave out even though the kind grants them: the ones the
|
|
30
|
+
* caller may not assign (an organization's `owner`, for anyone who is not
|
|
31
|
+
* its owner). See {@link useRoleSelector}.
|
|
32
|
+
*/
|
|
33
|
+
readonly omitRoles?: readonly IamRole[];
|
|
28
34
|
/** Additional CSS class names for the root container. */
|
|
29
35
|
readonly className?: string;
|
|
30
36
|
}
|
|
@@ -54,6 +60,7 @@ export function RoleSelector({
|
|
|
54
60
|
defaultRole,
|
|
55
61
|
disabled = false,
|
|
56
62
|
granteeKind = "identity_account",
|
|
63
|
+
omitRoles,
|
|
57
64
|
className,
|
|
58
65
|
}: RoleSelectorProps) {
|
|
59
66
|
// Instance-scoped radio-group name (oss#593): a hardcoded name would make
|
|
@@ -66,6 +73,7 @@ export function RoleSelector({
|
|
|
66
73
|
kind,
|
|
67
74
|
defaultRole,
|
|
68
75
|
granteeKind,
|
|
76
|
+
omitRoles,
|
|
69
77
|
);
|
|
70
78
|
|
|
71
79
|
const currentSelected = controlledSelected !== undefined
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins OrgMembersPanel's reading of the owner rule (the server asks
|
|
3
|
+
* `can_assign_roles` on the organization before any change to its owner
|
|
4
|
+
* role):
|
|
5
|
+
*
|
|
6
|
+
* - a caller who may not assign owner is offered no change and no removal
|
|
7
|
+
* on an owner's row, and no owner in the role picker for anyone else;
|
|
8
|
+
* an owner is offered all of them;
|
|
9
|
+
* - a role change grants the new role before it revokes the old one, so
|
|
10
|
+
* a refused grant leaves the member with the role they had.
|
|
11
|
+
*
|
|
12
|
+
* The data hooks are stubbed: each is proven by its own tests, and the
|
|
13
|
+
* permission answer is the one question this panel adds.
|
|
14
|
+
*/
|
|
15
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
16
|
+
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
17
|
+
import { create } from "@bufbuild/protobuf";
|
|
18
|
+
import {
|
|
19
|
+
PrincipalAccessSchema,
|
|
20
|
+
type PrincipalAccess,
|
|
21
|
+
} from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/io_pb";
|
|
22
|
+
import type { IamPolicySpec } from "@stigmer/protos/ai/stigmer/iam/iampolicy/v1/spec_pb";
|
|
23
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
24
|
+
|
|
25
|
+
const state = vi.hoisted(() => ({
|
|
26
|
+
members: [] as PrincipalAccess[],
|
|
27
|
+
canAssignOwner: false,
|
|
28
|
+
calls: [] as string[],
|
|
29
|
+
createFails: false,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock("../useResourceAccess.js", () => ({
|
|
33
|
+
useResourceAccess: () => ({
|
|
34
|
+
members: state.members,
|
|
35
|
+
isLoading: false,
|
|
36
|
+
error: null,
|
|
37
|
+
refetch: () => {},
|
|
38
|
+
}),
|
|
39
|
+
}));
|
|
40
|
+
vi.mock("../usePrincipalsCount.js", () => ({
|
|
41
|
+
usePrincipalsCount: () => ({ count: state.members.length, refetch: () => {} }),
|
|
42
|
+
}));
|
|
43
|
+
vi.mock("../useWhoAmI.js", () => ({
|
|
44
|
+
useWhoAmI: () => ({ account: { metadata: { id: "ida_me" } } }),
|
|
45
|
+
}));
|
|
46
|
+
vi.mock("../useOwnerAssignment.js", () => ({
|
|
47
|
+
useOwnerAssignment: () => ({
|
|
48
|
+
canAssignOwner: state.canAssignOwner,
|
|
49
|
+
unassignable: state.canAssignOwner ? [] : [IamRole.owner],
|
|
50
|
+
}),
|
|
51
|
+
}));
|
|
52
|
+
vi.mock("../useRevokeOrgAccess.js", () => ({
|
|
53
|
+
useRevokeOrgAccess: () => ({ revoke: async () => {}, isRevoking: false, error: null }),
|
|
54
|
+
}));
|
|
55
|
+
vi.mock("../useCreateIamPolicy.js", () => ({
|
|
56
|
+
useCreateIamPolicy: () => ({
|
|
57
|
+
create: async (spec: IamPolicySpec) => {
|
|
58
|
+
state.calls.push(`create ${spec.relation}`);
|
|
59
|
+
if (state.createFails) {
|
|
60
|
+
throw new Error("only an owner of the organization can grant, revoke or remove the owner role");
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
isCreating: false,
|
|
64
|
+
}),
|
|
65
|
+
}));
|
|
66
|
+
vi.mock("../useDeleteIamPolicy.js", () => ({
|
|
67
|
+
useDeleteIamPolicy: () => ({
|
|
68
|
+
remove: async (spec: IamPolicySpec) => {
|
|
69
|
+
state.calls.push(`delete ${spec.relation}`);
|
|
70
|
+
},
|
|
71
|
+
isDeleting: false,
|
|
72
|
+
}),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
import { OrgMembersPanel } from "../OrgMembersPanel";
|
|
76
|
+
|
|
77
|
+
function member(id: string, name: string, role: string): PrincipalAccess {
|
|
78
|
+
return create(PrincipalAccessSchema, {
|
|
79
|
+
principal: { kind: "identity_account", id, name },
|
|
80
|
+
roles: [{ role: { code: role, name: role } }],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
cleanup();
|
|
86
|
+
state.members = [];
|
|
87
|
+
state.canAssignOwner = false;
|
|
88
|
+
state.calls = [];
|
|
89
|
+
state.createFails = false;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("OrgMembersPanel and the owner role", () => {
|
|
93
|
+
it("offers a caller who is no owner nothing on an owner's row, and no owner in the picker", () => {
|
|
94
|
+
state.members = [member("ida_root", "Root", "owner"), member("ida_dave", "Dave", "member")];
|
|
95
|
+
render(<OrgMembersPanel orgId="acme" />);
|
|
96
|
+
|
|
97
|
+
expect(screen.queryByRole("button", { name: "Change role for Root" })).toBeNull();
|
|
98
|
+
expect(screen.queryByRole("button", { name: "Remove Root" })).toBeNull();
|
|
99
|
+
expect(screen.getByRole("button", { name: "Remove Dave" })).toBeTruthy();
|
|
100
|
+
|
|
101
|
+
fireEvent.click(screen.getByRole("button", { name: "Change role for Dave" }));
|
|
102
|
+
expect(screen.queryByRole("radio", { name: /^Owner/ })).toBeNull();
|
|
103
|
+
expect(screen.getByRole("radio", { name: /^Admin/ })).toBeTruthy();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("offers an owner every control on every row, owner in the picker included", () => {
|
|
107
|
+
state.canAssignOwner = true;
|
|
108
|
+
state.members = [member("ida_root", "Root", "owner"), member("ida_dave", "Dave", "member")];
|
|
109
|
+
render(<OrgMembersPanel orgId="acme" />);
|
|
110
|
+
|
|
111
|
+
expect(screen.getByRole("button", { name: "Remove Root" })).toBeTruthy();
|
|
112
|
+
fireEvent.click(screen.getByRole("button", { name: "Change role for Dave" }));
|
|
113
|
+
expect(screen.getByRole("radio", { name: /^Owner/ })).toBeTruthy();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("grants the new role before it revokes the old one", async () => {
|
|
117
|
+
state.canAssignOwner = true;
|
|
118
|
+
state.members = [member("ida_dave", "Dave", "member")];
|
|
119
|
+
render(<OrgMembersPanel orgId="acme" />);
|
|
120
|
+
|
|
121
|
+
fireEvent.click(screen.getByRole("button", { name: "Change role for Dave" }));
|
|
122
|
+
fireEvent.click(screen.getByRole("radio", { name: /^Admin/ }));
|
|
123
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
124
|
+
await waitFor(() => expect(state.calls).toEqual(["create admin", "delete member"]));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("revokes nothing when the new role is refused, so the member keeps the one they had", async () => {
|
|
128
|
+
state.canAssignOwner = true;
|
|
129
|
+
state.createFails = true;
|
|
130
|
+
state.members = [member("ida_dave", "Dave", "member")];
|
|
131
|
+
render(<OrgMembersPanel orgId="acme" />);
|
|
132
|
+
|
|
133
|
+
fireEvent.click(screen.getByRole("button", { name: "Change role for Dave" }));
|
|
134
|
+
fireEvent.click(screen.getByRole("radio", { name: /^Owner/ }));
|
|
135
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
136
|
+
await waitFor(() => expect(screen.getByRole("alert")).toBeTruthy());
|
|
137
|
+
expect(state.calls).toEqual(["create owner"]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins useOwnerAssignment, the console's one reading of the owner rule: it
|
|
3
|
+
* asks `can_assign_roles` on the organization it is given (and nothing
|
|
4
|
+
* without one), and leaves owner out of a role picker exactly when the
|
|
5
|
+
* answer is no. The permission hook is stubbed: it is proven by its own
|
|
6
|
+
* tests, fail-open posture included.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { renderHook } from "@testing-library/react";
|
|
10
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
11
|
+
|
|
12
|
+
const check = vi.hoisted(() => ({
|
|
13
|
+
allowed: false,
|
|
14
|
+
asked: [] as Array<{ resource: { kind: string; id: string } | null; relation: string }>,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("../useCheckPermission.js", () => ({
|
|
18
|
+
useCheckPermission: (resource: { kind: string; id: string } | null, relation: string) => {
|
|
19
|
+
check.asked.push({ resource, relation });
|
|
20
|
+
return { allowed: check.allowed, isLoading: false, error: null };
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import { useOwnerAssignment } from "../useOwnerAssignment";
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
check.allowed = false;
|
|
28
|
+
check.asked = [];
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("useOwnerAssignment", () => {
|
|
32
|
+
it("asks can_assign_roles on the organization, and leaves owner out when the answer is no", () => {
|
|
33
|
+
const { result } = renderHook(() => useOwnerAssignment("acme"));
|
|
34
|
+
expect(check.asked.at(-1)).toEqual({
|
|
35
|
+
resource: { kind: "organization", id: "acme" },
|
|
36
|
+
relation: "can_assign_roles",
|
|
37
|
+
});
|
|
38
|
+
expect(result.current).toEqual({ canAssignOwner: false, unassignable: [IamRole.owner] });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("leaves nothing out for an owner, and hands back the same list on every render", () => {
|
|
42
|
+
check.allowed = true;
|
|
43
|
+
const { result, rerender } = renderHook(() => useOwnerAssignment("acme"));
|
|
44
|
+
const first = result.current.unassignable;
|
|
45
|
+
rerender();
|
|
46
|
+
expect(result.current.canAssignOwner).toBe(true);
|
|
47
|
+
expect(result.current.unassignable).toEqual([]);
|
|
48
|
+
expect(result.current.unassignable).toBe(first);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("names no organization to the check when it has none", () => {
|
|
52
|
+
renderHook(() => useOwnerAssignment(null));
|
|
53
|
+
expect(check.asked.at(-1)?.resource).toBeNull();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { IamRole } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
4
|
+
import { useCheckPermission } from "./useCheckPermission.js";
|
|
5
|
+
|
|
6
|
+
/** What a caller who is not an owner may not assign: owner is assigned by owners. */
|
|
7
|
+
const OWNER_ONLY: readonly IamRole[] = [IamRole.owner];
|
|
8
|
+
const NOTHING_OMITTED: readonly IamRole[] = [];
|
|
9
|
+
|
|
10
|
+
/** Return value of {@link useOwnerAssignment}. */
|
|
11
|
+
export interface UseOwnerAssignmentReturn {
|
|
12
|
+
/** Whether the caller may grant, revoke or remove the owner role on the organization. */
|
|
13
|
+
readonly canAssignOwner: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The organization roles to leave out of a role picker for this caller:
|
|
16
|
+
* owner unless they may assign it. A stable list, as `RoleSelector`'s
|
|
17
|
+
* `omitRoles` asks.
|
|
18
|
+
*/
|
|
19
|
+
readonly unassignable: readonly IamRole[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The console's one reading of the owner rule: the server asks
|
|
24
|
+
* `can_assign_roles` on an organization before any change to its owner
|
|
25
|
+
* role (only its owners hold it), and every surface that offers such a
|
|
26
|
+
* change asks the same question here. Fails open, like every capability
|
|
27
|
+
* gate: the server refuses whatever this lets through.
|
|
28
|
+
*
|
|
29
|
+
* @param orgId - The organization's id, or `null` to skip the check.
|
|
30
|
+
*/
|
|
31
|
+
export function useOwnerAssignment(orgId: string | null): UseOwnerAssignmentReturn {
|
|
32
|
+
const { allowed } = useCheckPermission(
|
|
33
|
+
orgId ? { kind: "organization", id: orgId } : null,
|
|
34
|
+
"can_assign_roles",
|
|
35
|
+
);
|
|
36
|
+
return {
|
|
37
|
+
canAssignOwner: allowed,
|
|
38
|
+
unassignable: allowed ? NOTHING_OMITTED : OWNER_ONLY,
|
|
39
|
+
};
|
|
40
|
+
}
|