fix(fe2): project delete btn not following auth policy (#4770)

This commit is contained in:
Kristaps Fabians Geikins
2025-05-20 13:32:07 +03:00
committed by GitHub
parent 7573e74d83
commit 7d64667ea0
4 changed files with 51 additions and 35 deletions
@@ -44,8 +44,10 @@ import { LayoutDialog, type LayoutDialogButton } from '@speckle/ui-components'
import { useDeleteProject } from '~~/lib/projects/composables/projectManagement'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { graphql } from '~~/lib/common/generated/gql'
import type { ProjectsDeleteDialog_ProjectFragment } from '~~/lib/common/generated/gql/graphql'
import { Roles } from '@speckle/shared'
import type {
FullPermissionCheckResultFragment,
ProjectsDeleteDialog_ProjectFragment
} from '~~/lib/common/generated/gql/graphql'
graphql(`
fragment ProjectsDeleteDialog_Project on Project {
@@ -62,6 +64,11 @@ graphql(`
versions(limit: 0) {
totalCount
}
permissions {
canDelete {
...FullPermissionCheckResult
}
}
}
`)
@@ -75,7 +82,6 @@ const isOpen = defineModel<boolean>('open', { required: true })
const deleteProject = useDeleteProject()
const mixpanel = useMixpanel()
const { isAdmin } = useActiveUser()
const projectNameInput = ref('')
@@ -91,6 +97,18 @@ const versionsText = computed(
props.project.versions.totalCount === 1 ? 'version' : 'versions'
}`
)
const canDelete = computed((): FullPermissionCheckResultFragment => {
if (projectNameInput.value !== props.project.name)
return {
authorized: false,
code: 'NAME_MISMATCH',
message: 'Entered project name does not match the actual project name'
}
return props.project.permissions.canDelete
})
const dialogButtons = computed<LayoutDialogButton[]>(() => [
{
text: 'Cancel',
@@ -105,27 +123,25 @@ const dialogButtons = computed<LayoutDialogButton[]>(() => [
props: {
color: 'danger',
disabled: projectNameInput.value !== props.project.name
disabled: !canDelete.value.authorized
},
disabledMessage: canDelete.value.message,
onClick: async () => {
if (
projectNameInput.value === props.project.name &&
(props.project.role === Roles.Stream.Owner || isAdmin.value)
) {
const options = {
goHome: props.redirectOnComplete,
workspaceSlug: props.project.workspace?.slug
}
if (!canDelete.value.authorized) return
await deleteProject(props.project.id, options)
isOpen.value = false
mixpanel.track('Stream Action', {
type: 'action',
name: 'delete',
// eslint-disable-next-line camelcase
workspace_id: props.project.workspace?.id
})
const options = {
goHome: props.redirectOnComplete,
workspaceSlug: props.project.workspace?.slug
}
await deleteProject(props.project.id, options)
isOpen.value = false
mixpanel.track('Stream Action', {
type: 'action',
name: 'delete',
// eslint-disable-next-line camelcase
workspace_id: props.project.workspace?.id
})
}
}
])
@@ -99,7 +99,7 @@ type Documents = {
"\n fragment ProjectsDashboard_User on User {\n permissions {\n canCreatePersonalProject {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.ProjectsDashboard_UserFragmentDoc,
"\n fragment ProjectsDashboardFilledProject on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": typeof types.ProjectsDashboardFilledProjectFragmentDoc,
"\n fragment ProjectsDashboardFilledUser on UserProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": typeof types.ProjectsDashboardFilledUserFragmentDoc,
"\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n }\n": typeof types.ProjectsDeleteDialog_ProjectFragmentDoc,
"\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n permissions {\n canDelete {\n ...FullPermissionCheckResult\n }\n }\n }\n": typeof types.ProjectsDeleteDialog_ProjectFragmentDoc,
"\n fragment ProjectsHiddenProjectWarning_User on User {\n id\n expiredSsoSessions {\n id\n slug\n name\n logo\n }\n }\n": typeof types.ProjectsHiddenProjectWarning_UserFragmentDoc,
"\n fragment ProjectsWorkspaceSelect_Workspace on Workspace {\n id\n role\n name\n logo\n readOnly\n slug\n }\n": typeof types.ProjectsWorkspaceSelect_WorkspaceFragmentDoc,
"\n fragment ProjectsInviteBanner on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": typeof types.ProjectsInviteBannerFragmentDoc,
@@ -512,7 +512,7 @@ const documents: Documents = {
"\n fragment ProjectsDashboard_User on User {\n permissions {\n canCreatePersonalProject {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.ProjectsDashboard_UserFragmentDoc,
"\n fragment ProjectsDashboardFilledProject on ProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": types.ProjectsDashboardFilledProjectFragmentDoc,
"\n fragment ProjectsDashboardFilledUser on UserProjectCollection {\n items {\n ...ProjectDashboardItem\n }\n }\n": types.ProjectsDashboardFilledUserFragmentDoc,
"\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n }\n": types.ProjectsDeleteDialog_ProjectFragmentDoc,
"\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n permissions {\n canDelete {\n ...FullPermissionCheckResult\n }\n }\n }\n": types.ProjectsDeleteDialog_ProjectFragmentDoc,
"\n fragment ProjectsHiddenProjectWarning_User on User {\n id\n expiredSsoSessions {\n id\n slug\n name\n logo\n }\n }\n": types.ProjectsHiddenProjectWarning_UserFragmentDoc,
"\n fragment ProjectsWorkspaceSelect_Workspace on Workspace {\n id\n role\n name\n logo\n readOnly\n slug\n }\n": types.ProjectsWorkspaceSelect_WorkspaceFragmentDoc,
"\n fragment ProjectsInviteBanner on PendingStreamCollaborator {\n id\n invitedBy {\n ...LimitedUserAvatar\n }\n projectId\n projectName\n token\n user {\n id\n }\n }\n": types.ProjectsInviteBannerFragmentDoc,
@@ -1197,7 +1197,7 @@ export function graphql(source: "\n fragment ProjectsDashboardFilledUser on Use
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n }\n"];
export function graphql(source: "\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n permissions {\n canDelete {\n ...FullPermissionCheckResult\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectsDeleteDialog_Project on Project {\n id\n name\n role\n models(limit: 0) {\n totalCount\n }\n workspace {\n slug\n id\n }\n versions(limit: 0) {\n totalCount\n }\n permissions {\n canDelete {\n ...FullPermissionCheckResult\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Result } from 'true-myth'
import type { Result } from 'true-myth'
import { AuthError } from '../domain/authErrors.js'
export type GraphqlPermissionCheckResult = {