useCheckPermissions.ts190 lines · main
1import { useIsLoggedIn, useParams } from 'common'
2import jsonLogic from 'json-logic-js'
3import { useMemo } from 'react'
4
5import { useSelectedOrganizationQuery } from './useSelectedOrganization'
6import { useSelectedProjectQuery } from './useSelectedProject'
7import { usePermissionsQuery } from '@/data/permissions/permissions-query'
8import { IS_PLATFORM } from '@/lib/constants'
9import type { Permission } from '@/types'
10
11const toRegexpString = (actionOrResource: string) =>
12 `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$`
13
14function doPermissionConditionCheck(permissions: Permission[], data?: object) {
15 const isRestricted = permissions
16 .filter((permission) => permission.restrictive)
17 .some(
18 ({ condition }: { condition: jsonLogic.RulesLogic }) =>
19 condition === null || jsonLogic.apply(condition, data)
20 )
21 if (isRestricted) return false
22
23 return permissions
24 .filter((permission) => !permission.restrictive)
25 .some(
26 ({ condition }: { condition: jsonLogic.RulesLogic }) =>
27 condition === null || jsonLogic.apply(condition, data)
28 )
29}
30
31export function doPermissionsCheck(
32 permissions: Permission[] | undefined,
33 action: string,
34 resource: string,
35 data?: object,
36 organizationSlug?: string,
37 projectRef?: string
38) {
39 if (!permissions || !Array.isArray(permissions)) {
40 return false
41 }
42
43 if (projectRef) {
44 const projectPermissions = permissions.filter(
45 (permission) =>
46 permission.organization_slug === organizationSlug &&
47 permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) &&
48 permission.resources.some((res) => resource.match(toRegexpString(res))) &&
49 permission.project_refs?.includes(projectRef)
50 )
51 if (projectPermissions.length > 0) {
52 return doPermissionConditionCheck(projectPermissions, { resource_name: resource, ...data })
53 }
54 }
55
56 const orgPermissions = permissions
57 // filter out org-level permission
58 .filter((permission) => !permission.project_refs || permission.project_refs.length === 0)
59 .filter(
60 (permission) =>
61 permission.organization_slug === organizationSlug &&
62 permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) &&
63 permission.resources.some((res) => resource.match(toRegexpString(res)))
64 )
65 return doPermissionConditionCheck(orgPermissions, { resource_name: resource, ...data })
66}
67
68export function useGetPermissions(
69 permissionsOverride?: Permission[],
70 organizationSlugOverride?: string,
71 enabled = true
72) {
73 return useGetProjectPermissions(permissionsOverride, organizationSlugOverride, undefined, enabled)
74}
75
76function useGetProjectPermissions(
77 permissionsOverride?: Permission[],
78 organizationSlugOverride?: string,
79 projectRefOverride?: string,
80 enabled = true
81) {
82 const {
83 data,
84 isPending: isLoadingPermissions,
85 isSuccess: isSuccessPermissions,
86 } = usePermissionsQuery({
87 enabled: permissionsOverride === undefined && enabled,
88 })
89 const permissions = permissionsOverride === undefined ? data : permissionsOverride
90
91 const getOrganizationDataFromParamsSlug = organizationSlugOverride === undefined && enabled
92 const {
93 data: organizationData,
94 isPending: isLoadingOrganization,
95 isSuccess: isSuccessOrganization,
96 } = useSelectedOrganizationQuery({
97 enabled: getOrganizationDataFromParamsSlug,
98 })
99 const organization =
100 organizationSlugOverride === undefined ? organizationData : { slug: organizationSlugOverride }
101 const organizationSlug = organization?.slug
102
103 const { ref: urlProjectRef } = useParams()
104 const getProjectDataFromParamsRef = !!urlProjectRef && projectRefOverride === undefined && enabled
105 const {
106 data: projectData,
107 isPending: isLoadingProject,
108 isSuccess: isSuccessProject,
109 } = useSelectedProjectQuery({
110 enabled: getProjectDataFromParamsRef,
111 })
112 const project =
113 projectRefOverride === undefined || projectData?.parent_project_ref
114 ? projectData
115 : { ref: projectRefOverride, parent_project_ref: undefined }
116
117 const projectRef = project?.parent_project_ref ? project.parent_project_ref : project?.ref
118
119 const isLoading =
120 isLoadingPermissions ||
121 (getOrganizationDataFromParamsSlug && isLoadingOrganization) ||
122 (getProjectDataFromParamsRef && isLoadingProject)
123 const isSuccess =
124 isSuccessPermissions &&
125 (!getOrganizationDataFromParamsSlug || isSuccessOrganization) &&
126 (!getProjectDataFromParamsRef || isSuccessProject)
127
128 return {
129 permissions,
130 organizationSlug,
131 projectRef,
132 isLoading,
133 isSuccess,
134 }
135}
136
137/** [Joshen] To be renamed to be useAsyncCheckPermissions, more generic as it covers both org and project perms */
138// Useful when you want to avoid layout changes while waiting for permissions to load
139export function useAsyncCheckPermissions(
140 action: string,
141 resource: string,
142 data?: object,
143 overrides?: {
144 organizationSlug?: string
145 projectRef?: string
146 permissions?: Permission[]
147 }
148) {
149 const isLoggedIn = useIsLoggedIn()
150 const { organizationSlug, projectRef, permissions } = overrides ?? {}
151
152 const {
153 permissions: allPermissions,
154 organizationSlug: _organizationSlug,
155 projectRef: _projectRef,
156 isLoading: isPermissionsLoading,
157 isSuccess: isPermissionsSuccess,
158 } = useGetProjectPermissions(permissions, organizationSlug, projectRef, isLoggedIn)
159
160 const can = useMemo(() => {
161 if (!IS_PLATFORM) return true
162 if (!isLoggedIn) return false
163 if (!isPermissionsSuccess || !allPermissions) return false
164
165 return doPermissionsCheck(
166 allPermissions,
167 action,
168 resource,
169 data,
170 _organizationSlug,
171 _projectRef
172 )
173 }, [
174 isLoggedIn,
175 isPermissionsSuccess,
176 allPermissions,
177 action,
178 resource,
179 data,
180 _organizationSlug,
181 _projectRef,
182 ])
183
184 // Derive loading/success consistently from the same branches
185 const isLoading = !IS_PLATFORM ? false : !isLoggedIn ? true : isPermissionsLoading
186
187 const isSuccess = !IS_PLATFORM ? true : !isLoggedIn ? false : isPermissionsSuccess
188
189 return { isLoading, isSuccess, can }
190}