diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index b0e25a985bd0f..36a1c3f808ce8 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -306,6 +306,7 @@ export const organizationsPermissions = ( export const workspacePermissionsByOrganization = ( organizationIds: string[] | undefined, + userId: string, ) => { if (!organizationIds) { return { enabled: false }; @@ -315,10 +316,9 @@ export const workspacePermissionsByOrganization = ( queryKey: ["workspaces", organizationIds.sort(), "permissions"], queryFn: async () => { const prefixedChecks = organizationIds.flatMap((orgId) => - Object.entries(workspacePermissionChecks(orgId)).map(([key, val]) => [ - `${orgId}.${key}`, - val, - ]), + Object.entries(workspacePermissionChecks(orgId, userId)).map( + ([key, val]) => [`${orgId}.${key}`, val], + ), ); const response = await API.checkAuthorization({ diff --git a/site/src/modules/permissions/workspaces.ts b/site/src/modules/permissions/workspaces.ts index 9ebb75d4790de..b0834877e8e6c 100644 --- a/site/src/modules/permissions/workspaces.ts +++ b/site/src/modules/permissions/workspaces.ts @@ -1,10 +1,13 @@ -export const workspacePermissionChecks = (organizationId: string) => +export const workspacePermissionChecks = ( + organizationId: string, + userId: string, +) => ({ - createWorkspaceForUser: { + createWorkspace: { object: { resource_type: "workspace", organization_id: organizationId, - owner_id: "*", + owner_id: userId, }, action: "create", }, diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx index 26f1808b83152..150a79bd69487 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx @@ -17,10 +17,6 @@ import { Loader } from "components/Loader/Loader"; import { useAuthenticated } from "contexts/auth/RequireAuth"; import { useEffectEvent } from "hooks/hookPolyfills"; import { useDashboard } from "modules/dashboard/useDashboard"; -import { - type WorkspacePermissions, - workspacePermissionChecks, -} from "modules/permissions/workspaces"; import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName"; import { type FC, useCallback, useEffect, useRef, useState } from "react"; import { Helmet } from "react-helmet-async"; @@ -30,6 +26,7 @@ import { pageTitle } from "utils/page"; import type { AutofillBuildParameter } from "utils/richParameters"; import { paramsUsedToCreateWorkspace } from "utils/workspace"; import { CreateWorkspacePageView } from "./CreateWorkspacePageView"; +import { type CreateWSPermissions, createWorkspaceChecks } from "./permissions"; export const createWorkspaceModes = ["form", "auto", "duplicate"] as const; export type CreateWorkspaceMode = (typeof createWorkspaceModes)[number]; @@ -67,7 +64,7 @@ const CreateWorkspacePage: FC = () => { const permissionsQuery = useQuery( templateQuery.data ? checkAuthorization({ - checks: workspacePermissionChecks(templateQuery.data.organization_id), + checks: createWorkspaceChecks(templateQuery.data.organization_id), }) : { enabled: false }, ); @@ -209,7 +206,7 @@ const CreateWorkspacePage: FC = () => { externalAuthPollingState={externalAuthPollingState} startPollingExternalAuth={startPollingExternalAuth} hasAllRequiredExternalAuth={hasAllRequiredExternalAuth} - permissions={permissionsQuery.data as WorkspacePermissions} + permissions={permissionsQuery.data as CreateWSPermissions} parameters={realizedParameters as TemplateVersionParameter[]} presets={templateVersionPresetsQuery.data ?? []} creatingWorkspace={createWorkspaceMutation.isLoading} diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 660580b5b80b8..656e18563eb60 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -28,7 +28,6 @@ import { Stack } from "components/Stack/Stack"; import { Switch } from "components/Switch/Switch"; import { UserAutocomplete } from "components/UserAutocomplete/UserAutocomplete"; import { type FormikContextType, useFormik } from "formik"; -import type { WorkspacePermissions } from "modules/permissions/workspaces"; import { generateWorkspaceName } from "modules/workspaces/generateWorkspaceName"; import { type FC, useCallback, useEffect, useMemo, useState } from "react"; import { @@ -47,7 +46,7 @@ import type { ExternalAuthPollingState, } from "./CreateWorkspacePage"; import { ExternalAuthButton } from "./ExternalAuthButton"; - +import type { CreateWSPermissions } from "./permissions"; export const Language = { duplicationWarning: "Duplicating a workspace only copies its parameters. No state from the old workspace is copied over.", @@ -69,7 +68,7 @@ export interface CreateWorkspacePageViewProps { parameters: TypesGen.TemplateVersionParameter[]; autofillParameters: AutofillBuildParameter[]; presets: TypesGen.Preset[]; - permissions: WorkspacePermissions; + permissions: CreateWSPermissions; creatingWorkspace: boolean; onCancel: () => void; onSubmit: ( diff --git a/site/src/pages/CreateWorkspacePage/permissions.ts b/site/src/pages/CreateWorkspacePage/permissions.ts new file mode 100644 index 0000000000000..07bad5031ddc2 --- /dev/null +++ b/site/src/pages/CreateWorkspacePage/permissions.ts @@ -0,0 +1,16 @@ +export const createWorkspaceChecks = (organizationId: string) => + ({ + createWorkspaceForUser: { + object: { + resource_type: "workspace", + organization_id: organizationId, + owner_id: "*", + }, + action: "create", + }, + }) as const; + +export type CreateWSPermissions = Record< + keyof ReturnType, + boolean +>; diff --git a/site/src/pages/TemplatePage/TemplateLayout.tsx b/site/src/pages/TemplatePage/TemplateLayout.tsx index 93d25d6f591db..78b8822b6fa42 100644 --- a/site/src/pages/TemplatePage/TemplateLayout.tsx +++ b/site/src/pages/TemplatePage/TemplateLayout.tsx @@ -5,6 +5,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert"; import { Loader } from "components/Loader/Loader"; import { Margins } from "components/Margins/Margins"; import { TabLink, Tabs, TabsList } from "components/Tabs/Tabs"; +import { useAuthenticated } from "contexts/auth/RequireAuth"; import { workspacePermissionChecks } from "modules/permissions/workspaces"; import { type FC, @@ -73,6 +74,7 @@ export const TemplateLayout: FC = ({ children = , }) => { const navigate = useNavigate(); + const { user: me } = useAuthenticated(); const { organization: organizationName = "default", template: templateName } = useParams() as { organization?: string; template: string }; const { data, error, isLoading } = useQuery({ @@ -81,7 +83,7 @@ export const TemplateLayout: FC = ({ }); const workspacePermissionsQuery = useQuery( checkAuthorization({ - checks: workspacePermissionChecks(organizationName), + checks: workspacePermissionChecks(organizationName, me.id), }), ); diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.stories.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.stories.tsx index 4acd28446631f..b70dd4204b1ba 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.stories.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.stories.tsx @@ -14,7 +14,7 @@ const meta: Meta = { canUpdateTemplate: true, }, workspacePermissions: { - createWorkspaceForUser: true, + createWorkspace: true, }, }, }; @@ -35,7 +35,7 @@ export const CanNotUpdate: Story = { export const CannotCreateWorkspace: Story = { args: { workspacePermissions: { - createWorkspaceForUser: false, + createWorkspace: false, }, }, }; diff --git a/site/src/pages/TemplatePage/TemplatePageHeader.tsx b/site/src/pages/TemplatePage/TemplatePageHeader.tsx index 1d70379e75f43..918db1bfe9368 100644 --- a/site/src/pages/TemplatePage/TemplatePageHeader.tsx +++ b/site/src/pages/TemplatePage/TemplatePageHeader.tsx @@ -179,17 +179,16 @@ export const TemplatePageHeader: FC = ({ - {!template.deprecated && - workspacePermissions.createWorkspaceForUser && ( - - )} + {!template.deprecated && workspacePermissions.createWorkspace && ( + + )} {permissions.canUpdateTemplate && ( { - const { permissions } = useAuthenticated(); + const { permissions, user: me } = useAuthenticated(); const { showOrganizations } = useDashboard(); const searchParamsResult = useSearchParams(); @@ -30,6 +30,7 @@ export const TemplatesPage: FC = () => { const workspacePermissionsQuery = useQuery( workspacePermissionsByOrganization( templatesQuery.data?.map((template) => template.organization_id), + me.id, ), ); diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx index bed6b72c5d719..d259113286f88 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx @@ -76,7 +76,7 @@ export const WithTemplates: Story = { examples: [], workspacePermissions: { [MockTemplate.organization_id]: { - createWorkspaceForUser: true, + createWorkspace: true, }, }, }, @@ -94,7 +94,7 @@ export const CannotCreateWorkspaces: Story = { ...WithTemplates.args, workspacePermissions: { [MockTemplate.organization_id]: { - createWorkspaceForUser: false, + createWorkspace: false, }, }, }, diff --git a/site/src/pages/TemplatesPage/TemplatesPageView.tsx b/site/src/pages/TemplatesPage/TemplatesPageView.tsx index fbf3743043c08..abd867bfcb60a 100644 --- a/site/src/pages/TemplatesPage/TemplatesPageView.tsx +++ b/site/src/pages/TemplatesPage/TemplatesPageView.tsx @@ -160,7 +160,7 @@ const TemplateRow: FC = ({ {template.deprecated ? ( ) : workspacePermissions?.[template.organization_id] - ?.createWorkspaceForUser ? ( + ?.createWorkspace ? ( { // each hook. const searchParamsResult = useSafeSearchParams(); const pagination = usePagination({ searchParamsResult }); - const { permissions } = useAuthenticated(); + const { permissions, user: me } = useAuthenticated(); const { entitlements } = useDashboard(); const templatesQuery = useQuery(templates()); @@ -48,6 +48,7 @@ const WorkspacesPage: FC = () => { const orgPermissionsQuery = useQuery( workspacePermissionsByOrganization( templatesQuery.data?.map((template) => template.organization_id), + me.id, ), ); @@ -59,7 +60,7 @@ const WorkspacesPage: FC = () => { return templatesQuery.data.filter((template) => { const orgPermission = orgPermissionsQuery.data[template.organization_id]; - return orgPermission?.createWorkspaceForUser; + return orgPermission?.createWorkspace; }); }, [templatesQuery.data, orgPermissionsQuery.data]);