Skip to content

Commit

Permalink
refactor(core): grants permission logic (#5299)
Browse files Browse the repository at this point in the history
* refactor(core): replace `currentUser` with `userId` when evaluating user grants

* refactor(comments): replace copy-pasted grants evaluation logic

fix

* refactor(core): support both user ID and currentUser
  • Loading branch information
hermanwikner authored Jan 17, 2024
1 parent fb4eb68 commit dfebba6
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 102 deletions.
2 changes: 1 addition & 1 deletion packages/sanity/src/core/store/_legacy/datastores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function useGrantsStore(): GrantsStore {
resourceCache.get<GrantsStore>({
namespace: 'grantsStore',
dependencies: [client, currentUser],
}) || createGrantsStore({client, currentUser})
}) || createGrantsStore({client, userId: currentUser?.id || null})

resourceCache.set({
namespace: 'grantsStore',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('checkDocumentPermission', () => {
},
})

const {checkDocumentPermission} = createGrantsStore({client, currentUser: null})
const {checkDocumentPermission} = createGrantsStore({client, userId: null})

await expect(
firstValueFrom(checkDocumentPermission('create', {_id: 'example-id', _type: 'book'})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('getTemplatePermissions', () => {
const client = createMockSanityClient({requests: {'/acl': requiresApproval}})
const grantsStore = createGrantsStore({
client: client as unknown as SanityClient,
currentUser: null,
userId: null,
})

const permissions = firstValueFrom(
Expand Down
46 changes: 27 additions & 19 deletions packages/sanity/src/core/store/_legacy/grants/grantsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,18 @@ async function getDatasetGrants(
return grants
}

function getParams(currentUser: CurrentUser | null): EvaluationParams {
function getParams(userId: string | null): EvaluationParams {
const params: EvaluationParams = {}

if (currentUser !== null) {
params.identity = currentUser.id
if (userId !== null) {
params.identity = userId
}

return params
}

const PARSED_FILTERS_MEMO = new Map()
async function matchesFilter(
currentUser: CurrentUser | null,
filter: string,
document: SanityDocument,
) {
async function matchesFilter(userId: string | null, filter: string, document: SanityDocument) {
if (!PARSED_FILTERS_MEMO.has(filter)) {
// note: it might be tempting to also memoize the result of the evaluation here,
// Currently these filters are typically evaluated whenever a document change, which means they will be evaluated
Expand All @@ -55,22 +51,33 @@ async function matchesFilter(
}
const parsed = PARSED_FILTERS_MEMO.get(filter)

const evalParams = getParams(currentUser)
const evalParams = getParams(userId)
const {identity} = evalParams
const params: Record<string, unknown> = {...evalParams}
const data = await (await evaluate(parsed, {dataset: [document], identity, params})).get()
return data?.length === 1
}

/** @internal */
export interface GrantsStoreOptions {
interface GrantsStoreOptionsCurrentUser {
client: SanityClient
/**
* @deprecated The `currentUser` option is deprecated. Use `userId` instead.
*/
currentUser: CurrentUser | null
}

interface GrantsStoreOptionsUserId {
client: SanityClient
userId: string | null
}

/** @internal */
export type GrantsStoreOptions = GrantsStoreOptionsCurrentUser | GrantsStoreOptionsUserId

/** @internal */
export function createGrantsStore({client, currentUser}: GrantsStoreOptions): GrantsStore {
export function createGrantsStore(opts: GrantsStoreOptions): GrantsStore {
const {client} = opts
const versionedClient = client.withConfig({apiVersion: '2021-06-07'})
const userId = 'userId' in opts ? opts.userId : opts?.currentUser?.id || null

const datasetGrants$ = defer(() => of(versionedClient.config())).pipe(
switchMap(({projectId, dataset}) => {
Expand All @@ -90,19 +97,20 @@ export function createGrantsStore({client, currentUser}: GrantsStoreOptions): Gr
return {
checkDocumentPermission(permission: DocumentValuePermission, document: SanityDocument) {
return currentUserDatasetGrants.pipe(
switchMap((grants) => grantsPermissionOn(currentUser, grants, permission, document)),
switchMap((grants) => grantsPermissionOn(userId, grants, permission, document)),
distinctUntilChanged(shallowEquals),
)
},
}
}

/**
* takes a grants object, a permission and a document
* checks whether the the permission is granted for the given document
* @internal
* Takes a grants object, a permission and a document
* checks whether the permission is granted for the given document
*/
async function grantsPermissionOn(
currentUser: CurrentUser | null,
export async function grantsPermissionOn(
userId: string | null,
grants: Grant[],
permission: DocumentValuePermission,
document: SanityDocument | null,
Expand All @@ -119,7 +127,7 @@ async function grantsPermissionOn(
const matchingGrants: Grant[] = []

for (const grant of grants) {
if (await matchesFilter(currentUser, grant.filter, document)) {
if (await matchesFilter(userId, grant.filter, document)) {
matchingGrants.push(grant)
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/sanity/src/structure/comments/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './use-mention-options'
export * from './useMentionOptions'
export * from './useCommentOperations'
export * from './useComments'
export * from './useCommentsSetup'
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import {useState, useEffect, useMemo} from 'react'
import {Observable, concat, forkJoin, map, mergeMap, of, switchMap} from 'rxjs'
import {SanityDocument} from '@sanity/client'
import {sortBy} from 'lodash'
import {Loadable, MentionOptionUser, MentionOptionsHookValue} from '../../types'
import {grantsPermissionOn} from './helpers'
import {Loadable, MentionOptionUser, MentionOptionsHookValue} from '../types'
import {
useProjectStore,
useUserStore,
useClient,
DEFAULT_STUDIO_CLIENT_OPTIONS,
grantsPermissionOn,
ProjectData,
useClient,
useProjectStore,
useUserStore,
} from 'sanity'

const INITIAL_STATE: MentionOptionsHookValue = {
Expand Down Expand Up @@ -77,7 +77,12 @@ export function useMentionOptions(opts: MentionHookOptions): MentionOptionsHookV
})

const flattenedGrants = [...grants].flat()
const {granted} = await grantsPermissionOn(user, flattenedGrants, 'read', documentValue)
const {granted} = await grantsPermissionOn(
user.id,
flattenedGrants,
'read',
documentValue,
)

return {
...user,
Expand Down

2 comments on commit dfebba6

@vercel
Copy link

@vercel vercel bot commented on dfebba6 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

performance-studio – ./

performance-studio-git-next.sanity.build
performance-studio.sanity.build

@vercel
Copy link

@vercel vercel bot commented on dfebba6 Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

test-studio – ./

test-studio.sanity.build
test-studio-git-next.sanity.build

Please sign in to comment.