diff --git a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx index 2bbf73502a6c6..26292d61b2010 100644 --- a/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx +++ b/site/src/components/OrganizationAutocomplete/OrganizationAutocomplete.tsx @@ -9,8 +9,9 @@ import { useState, } from "react"; import { useQuery } from "react-query"; +import { checkAuthorization } from "api/queries/authCheck"; import { organizations } from "api/queries/organizations"; -import type { Organization } from "api/typesGenerated"; +import type { AuthorizationCheck, Organization } from "api/typesGenerated"; import { Avatar } from "components/Avatar/Avatar"; import { AvatarData } from "components/AvatarData/AvatarData"; import { useDebouncedFunction } from "hooks/debounce"; @@ -22,6 +23,7 @@ export type OrganizationAutocompleteProps = { className?: string; size?: ComponentProps["size"]; required?: boolean; + check?: AuthorizationCheck; }; export const OrganizationAutocomplete: FC = ({ @@ -31,6 +33,7 @@ export const OrganizationAutocomplete: FC = ({ className, size = "small", required, + check, }) => { const [autoComplete, setAutoComplete] = useState<{ value: string; @@ -41,6 +44,22 @@ export const OrganizationAutocomplete: FC = ({ }); const organizationsQuery = useQuery(organizations()); + const permissionsQuery = useQuery( + check && organizationsQuery.data + ? checkAuthorization({ + checks: Object.fromEntries( + organizationsQuery.data.map((org) => [ + org.id, + { + ...check, + object: { ...check.object, organization_id: org.id }, + }, + ]), + ), + }) + : { enabled: false }, + ); + const { debounced: debouncedInputOnChange } = useDebouncedFunction( (event: ChangeEvent) => { setAutoComplete((state) => ({ @@ -51,11 +70,20 @@ export const OrganizationAutocomplete: FC = ({ 750, ); + // If an authorization check was provided, filter the organizations based on + // the results of that check. + let options = organizationsQuery.data ?? []; + if (check) { + options = permissionsQuery.data + ? options.filter((org) => permissionsQuery.data[org.id]) + : []; + } + return ( { + return { + [organizationId]: { + object: { + resource_type: "template", + organization_id: organizationId, + }, + action: "create", + }, + }; +}; + export const StarterTemplateWithProvisionerWarning: Story = { parameters: { queries: [ @@ -68,6 +80,21 @@ export const StarterTemplateWithProvisionerWarning: Story = { key: organizationsKey, data: [MockDefaultOrganization, MockOrganization2], }, + { + key: [ + "authorization", + { + checks: { + ...canCreateTemplate(MockDefaultOrganization.id), + ...canCreateTemplate(MockOrganization2.id), + }, + }, + ], + data: { + [MockDefaultOrganization.id]: true, + [MockOrganization2.id]: true, + }, + }, { key: getProvisionerDaemonsKey(MockOrganization2.id), data: [], @@ -86,6 +113,44 @@ export const StarterTemplateWithProvisionerWarning: Story = { }, }; +export const StarterTemplatePermissionsCheck: Story = { + parameters: { + queries: [ + { + key: organizationsKey, + data: [MockDefaultOrganization, MockOrganization2], + }, + { + key: [ + "authorization", + { + checks: { + ...canCreateTemplate(MockDefaultOrganization.id), + ...canCreateTemplate(MockOrganization2.id), + }, + }, + ], + data: { + [MockDefaultOrganization.id]: true, + [MockOrganization2.id]: false, + }, + }, + { + key: getProvisionerDaemonsKey(MockOrganization2.id), + data: [], + }, + ], + }, + args: { + ...StarterTemplate.args, + showOrganizationPicker: true, + }, + play: async () => { + const organizationPicker = screen.getByPlaceholderText("Organization name"); + await userEvent.click(organizationPicker); + }, +}; + export const DuplicateTemplateWithVariables: Story = { args: { copiedTemplate: MockTemplate, diff --git a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx index 3364d99bf4745..7644f4f1479b1 100644 --- a/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx +++ b/site/src/pages/CreateTemplatePage/CreateTemplateForm.tsx @@ -267,6 +267,10 @@ export const CreateTemplateForm: FC = (props) => { void form.setFieldValue("organization", newValue?.name || ""); }} size="medium" + check={{ + object: { resource_type: "template" }, + action: "create", + }} /> )}