From a07b9721f8bacfef6a108ac4c4e2f6398f9f0589 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 2 Oct 2025 09:10:20 +1000 Subject: [PATCH 1/7] feat: update state to return the roles and permissions metadata --- src/authz-module/data/api.ts | 13 ++++++ src/authz-module/data/hooks.test.tsx | 35 +++++++++++++++- src/authz-module/data/hooks.ts | 22 +++++++++- .../libraries-manager/constants.ts | 41 +++++++++++++++++++ .../libraries-manager/context.test.tsx | 20 +++++++++ .../libraries-manager/context.tsx | 19 ++++++--- src/types.ts | 23 +++++++++++ 7 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/authz-module/libraries-manager/constants.ts diff --git a/src/authz-module/data/api.ts b/src/authz-module/data/api.ts index 7529c60..53a786f 100644 --- a/src/authz-module/data/api.ts +++ b/src/authz-module/data/api.ts @@ -8,6 +8,12 @@ export interface GetTeamMembersResponse { totalCount: number; } +export type PermissionsByRole = { + key: string; + permissions: string[]; + userCount: number; +}; + // TODO: replece api path once is created export const getTeamMembers = async (object: string): Promise => { const { data } = await getAuthenticatedHttpClient().get(getApiUrl(`/api/authz/v1/roles/users?scope=${object}`)); @@ -24,3 +30,10 @@ export const getLibrary = async (libraryId: string): Promise => slug: data.slug, }; }; + +export const getPermissionsByRole = async (scope: string): Promise => { + const url = new URL(getApiUrl('/api/authz/v1/roles')); + url.searchParams.append('scope', scope); + const { data } = await getAuthenticatedHttpClient().get(url); + return camelCaseObject(data); +}; diff --git a/src/authz-module/data/hooks.test.tsx b/src/authz-module/data/hooks.test.tsx index 28aced0..dfeb3f5 100644 --- a/src/authz-module/data/hooks.test.tsx +++ b/src/authz-module/data/hooks.test.tsx @@ -2,7 +2,7 @@ import { ReactNode } from 'react'; import { act, renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { useLibrary, useTeamMembers } from './hooks'; +import { useLibrary, usePermissionsByRole, useTeamMembers } from './hooks'; jest.mock('@edx/frontend-platform/auth', () => ({ getAuthenticatedHttpClient: jest.fn(), @@ -123,3 +123,36 @@ describe('useLibrary', () => { expect(getAuthenticatedHttpClient).toHaveBeenCalled(); }); }); + +describe('usePermissionsByRole', () => { + it('fetches roles for a given scope', async () => { + const mockRoles = [ + { key: 'admin', permissions: ['perm1'], userCount: 1 }, + { key: 'user', permissions: ['perm2'], userCount: 2 }, + ]; + + getAuthenticatedHttpClient.mockReturnValue({ + get: jest.fn().mockResolvedValue({ data: mockRoles }), + }); + + const wrapper = createWrapper(); + const { result } = renderHook(() => usePermissionsByRole('lib'), { wrapper }); + await waitFor(() => result.current.data !== undefined); + expect(result.current.data).toEqual(mockRoles); + expect(getAuthenticatedHttpClient).toHaveBeenCalled(); + }); + + it('returns error if getRoles fails', async () => { + getAuthenticatedHttpClient.mockReturnValue({ + get: jest.fn().mockRejectedValue(new Error('Not found')), + }); + const wrapper = createWrapper(); + try { + act(() => { + renderHook(() => usePermissionsByRole('lib'), { wrapper }); + }); + } catch (e) { + expect(e).toEqual(new Error('Not found')); + } + }); +}); diff --git a/src/authz-module/data/hooks.ts b/src/authz-module/data/hooks.ts index e374c09..9af0374 100644 --- a/src/authz-module/data/hooks.ts +++ b/src/authz-module/data/hooks.ts @@ -1,11 +1,14 @@ import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; import { appId } from '@src/constants'; import { LibraryMetadata, TeamMember } from '@src/types'; -import { getLibrary, getTeamMembers } from './api'; +import { + getLibrary, getPermissionsByRole, getTeamMembers, PermissionsByRole, +} from './api'; const authzQueryKeys = { all: [appId, 'authz'] as const, teamMembers: (object: string) => [...authzQueryKeys.all, 'teamMembers', object] as const, + permissionsByRole: (scope: string) => [...authzQueryKeys.all, 'permissionsByRole', scope] as const, library: (libraryId: string) => [...authzQueryKeys.all, 'library', libraryId] as const, }; @@ -26,6 +29,23 @@ export const useTeamMembers = (object: string) => useQuery( staleTime: 1000 * 60 * 30, // refetch after 30 minutes }); +/** + * React Query hook to fetch all the roles for the specific object/scope. + * It retrieves the full list of roles with the corresponding permissions. + * + * @param scope - The unique identifier of the object/scope + * + * @example + * ```tsx + * const { data: roles, isLoading, isError } = useTeamMembers('lib:123'); + * ``` + */ +export const usePermissionsByRole = (scope: string) => useSuspenseQuery({ + queryKey: authzQueryKeys.permissionsByRole(scope), + queryFn: () => getPermissionsByRole(scope), + retry: false, +}); + /** * React Query hook to retrieve the information of the current library. * diff --git a/src/authz-module/libraries-manager/constants.ts b/src/authz-module/libraries-manager/constants.ts new file mode 100644 index 0000000..d6e3fbc --- /dev/null +++ b/src/authz-module/libraries-manager/constants.ts @@ -0,0 +1,41 @@ +import { PermissionMetadata, ResourceMetadata, RoleMetadata } from 'types'; + +// Note: this information will eventually come from the backend API +// but for the MVP we decided to manage it in the frontend +export const libraryRolesMetadata: RoleMetadata[] = [ + { key: 'library_admin', name: 'Library Admin', description: 'The Library Admin has full control over the library, including managing users, modifying content, and handling publishing workflows. They ensure content is properly maintained and accessible as needed.' }, + { key: 'library_author', name: 'Library Author', description: 'The Library Author is responsible for creating, editing, and publishing content within a library. They can manage tags and collections but cannot delete libraries or manage users.' }, + { key: 'library_collaborator', name: 'Library Collaborator', description: 'The Library Collaborator can create and edit content within a library but cannot publish it. They support the authoring process while leaving final publishing to Authors or Admins.' }, + { key: 'library_user', name: 'Library User', description: 'The Library User can view and reuse content but cannot edit or delete any resource.' }, +]; + +export const libraryResourceTypes: ResourceMetadata[] = [ + { key: 'library', label: 'Library', description: 'Permissions related to the library as a whole.' }, + { key: 'library_content', label: 'Content', description: 'Permissions to create, edit, delete, and publish individual content items within the library.' }, + { key: 'library_collection', label: 'Collection', description: 'Permissions to create, edit, and delete content collections within the library.' }, + { key: 'library_team', label: 'Team', description: 'Permissions to manage user access and roles within the library.' }, +]; + +export const libraryPermissions: PermissionMetadata[] = [ + { key: 'create_library', resource: 'library', description: 'Allows the user to create new libraries.' }, + { key: 'edit_library', resource: 'library', description: 'Allows the user to modify library settings and metadata.' }, + { key: 'delete_library', resource: 'library', description: 'Allows the user to delete the library and all its contents.' }, + { key: 'publish_library', resource: 'library', description: 'Publish the library (change from draft mode to published).' }, + { key: 'view_library', resource: 'library', description: 'View content, search, filter, and sort within the library.' }, + { + key: 'manage_library_tags', resource: 'library', description: 'Add or remove tags from content.', + }, + + { key: 'create_library_content', resource: 'library_content', description: 'Create new components or content units.' }, + { key: 'edit_library_content', resource: 'library_content', description: 'Edit content in draft mode' }, + { key: 'delete_library_content', resource: 'library_content', description: 'Delete individual content (not collections).' }, + { key: 'publish_library_content', resource: 'library_content', description: 'Publish content, making it available for reuse' }, + { key: 'reuse_library_content', resource: 'library_content', description: 'Reuse published content within a course.' }, + + { key: 'create_library_collection', resource: 'library_collection', description: 'Create new collections within a library.' }, + { key: 'edit_library_collection', resource: 'library_collection', description: 'Add or remove content from existing collections.' }, + { key: 'delete_library_collection', resource: 'library_collection', description: 'Delete entire collections from the library.' }, + + { key: 'manage_library_team', resource: 'library_team', description: 'View the list of users who have access to the library.' }, + { key: 'view_library_team', resource: 'library_team', description: 'Add, remove, and assign roles to users within the library.' }, +]; diff --git a/src/authz-module/libraries-manager/context.test.tsx b/src/authz-module/libraries-manager/context.test.tsx index f133f41..bacfbc5 100644 --- a/src/authz-module/libraries-manager/context.test.tsx +++ b/src/authz-module/libraries-manager/context.test.tsx @@ -12,6 +12,20 @@ jest.mock('react-router-dom', () => ({ jest.mock('@src/data/hooks', () => ({ useValidateUserPermissions: jest.fn(), })); +jest.mock('@src/authz-module/data/hooks', () => ({ + usePermissionsByRole: jest.fn().mockReturnValue({ + data: [ + { + key: 'library_author', + permissions: [ + 'view_library_team', + 'edit_library', + ], + user_count: 12, + }, + ], + }), +})); const TestComponent = () => { const context = useLibraryAuthZ(); @@ -20,6 +34,9 @@ const TestComponent = () => {
{context.username}
{context.libraryId}
{context.canManageTeam ? 'true' : 'false'}
+
{Array.isArray(context.roles) ? context.roles.length : 'undefined'}
+
{Array.isArray(context.permissions) ? context.permissions.length : 'undefined'}
+
{Array.isArray(context.resources) ? context.resources.length : 'undefined'}
); }; @@ -47,6 +64,9 @@ describe('LibraryAuthZProvider', () => { expect(screen.getByTestId('username')).toHaveTextContent('testuser'); expect(screen.getByTestId('libraryId')).toHaveTextContent('lib123'); expect(screen.getByTestId('canManageTeam')).toHaveTextContent('true'); + expect(Number(screen.getByTestId('roles').textContent)).not.toBeNaN(); + expect(Number(screen.getByTestId('permissions').textContent)).not.toBeNaN(); + expect(Number(screen.getByTestId('resources').textContent)).not.toBeNaN(); }); it('throws error when user lacks both view and manage permissions', () => { diff --git a/src/authz-module/libraries-manager/context.tsx b/src/authz-module/libraries-manager/context.tsx index b013e6b..67d9c05 100644 --- a/src/authz-module/libraries-manager/context.tsx +++ b/src/authz-module/libraries-manager/context.tsx @@ -4,8 +4,12 @@ import { import { useParams } from 'react-router-dom'; import { AppContext } from '@edx/frontend-platform/react'; import { useValidateUserPermissions } from '@src/data/hooks'; +import { usePermissionsByRole } from '@src/authz-module/data/hooks'; +import { PermissionMetadata, ResourceMetadata, Role } from 'types'; +import { libraryPermissions, libraryResourceTypes, libraryRolesMetadata } from './constants'; const LIBRARY_TEAM_PERMISSIONS = ['act:view_library_team', 'act:manage_library_team']; +const LIBRARY_AUTHZ_SCOPE = 'lib:*'; export type AppContextType = { authenticatedUser: { @@ -18,8 +22,9 @@ type LibraryAuthZContextType = { canManageTeam: boolean; username: string; libraryId: string; - roles: string[]; - permissions: string[]; + resources: ResourceMetadata[]; + roles: Role[]; + permissions: PermissionMetadata[]; }; const LibraryAuthZContext = createContext(undefined); @@ -45,13 +50,17 @@ export const LibraryAuthZProvider: React.FC = ({ children }: throw new Error('NoAccess'); } + const { data: libraryRoles } = usePermissionsByRole(LIBRARY_AUTHZ_SCOPE); + const roles = libraryRoles.map(role => ({ ...role, ...libraryRolesMetadata.find(r => r.key === role.key) } as Role)); + const value = useMemo((): LibraryAuthZContextType => ({ username: authenticatedUser.username, libraryId, - roles: [], - permissions: [], + roles, + permissions: libraryPermissions, + resources: libraryResourceTypes, canManageTeam, - }), [libraryId, authenticatedUser.username, canManageTeam]); + }), [libraryId, authenticatedUser.username, canManageTeam, roles]); return ( diff --git a/src/types.ts b/src/types.ts index 6ce5b7f..ef8f126 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,6 +22,29 @@ export interface LibraryMetadata { slug: string; } +export interface RoleMetadata { + key: string; + name: string; + description: string; +} +export interface Role extends RoleMetadata { + userCount: number; + permissions: string[]; +} + +export type ResourceMetadata = { + key: string; + label: string; + description: string; +}; + +export type PermissionMetadata = { + key: string; + resource: string; + label?: string; + description?: string; +}; + // Paragon table type export interface TableCellValue { row: { From a2e33d4301e754d401de4e9431928e5ee1b9cd11 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 2 Oct 2025 11:56:25 +1000 Subject: [PATCH 2/7] fix: use role insted of key for roles identifier --- src/authz-module/data/api.ts | 2 +- src/authz-module/data/hooks.test.tsx | 4 ++-- src/authz-module/libraries-manager/constants.ts | 8 ++++---- src/authz-module/libraries-manager/context.test.tsx | 2 +- src/authz-module/libraries-manager/context.tsx | 5 ++++- src/types.ts | 4 ++-- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/authz-module/data/api.ts b/src/authz-module/data/api.ts index 53a786f..fd570d6 100644 --- a/src/authz-module/data/api.ts +++ b/src/authz-module/data/api.ts @@ -9,7 +9,7 @@ export interface GetTeamMembersResponse { } export type PermissionsByRole = { - key: string; + role: string; permissions: string[]; userCount: number; }; diff --git a/src/authz-module/data/hooks.test.tsx b/src/authz-module/data/hooks.test.tsx index dfeb3f5..7d1cdcc 100644 --- a/src/authz-module/data/hooks.test.tsx +++ b/src/authz-module/data/hooks.test.tsx @@ -127,8 +127,8 @@ describe('useLibrary', () => { describe('usePermissionsByRole', () => { it('fetches roles for a given scope', async () => { const mockRoles = [ - { key: 'admin', permissions: ['perm1'], userCount: 1 }, - { key: 'user', permissions: ['perm2'], userCount: 2 }, + { role: 'admin', permissions: ['perm1'], userCount: 1 }, + { role: 'user', permissions: ['perm2'], userCount: 2 }, ]; getAuthenticatedHttpClient.mockReturnValue({ diff --git a/src/authz-module/libraries-manager/constants.ts b/src/authz-module/libraries-manager/constants.ts index d6e3fbc..a247829 100644 --- a/src/authz-module/libraries-manager/constants.ts +++ b/src/authz-module/libraries-manager/constants.ts @@ -3,10 +3,10 @@ import { PermissionMetadata, ResourceMetadata, RoleMetadata } from 'types'; // Note: this information will eventually come from the backend API // but for the MVP we decided to manage it in the frontend export const libraryRolesMetadata: RoleMetadata[] = [ - { key: 'library_admin', name: 'Library Admin', description: 'The Library Admin has full control over the library, including managing users, modifying content, and handling publishing workflows. They ensure content is properly maintained and accessible as needed.' }, - { key: 'library_author', name: 'Library Author', description: 'The Library Author is responsible for creating, editing, and publishing content within a library. They can manage tags and collections but cannot delete libraries or manage users.' }, - { key: 'library_collaborator', name: 'Library Collaborator', description: 'The Library Collaborator can create and edit content within a library but cannot publish it. They support the authoring process while leaving final publishing to Authors or Admins.' }, - { key: 'library_user', name: 'Library User', description: 'The Library User can view and reuse content but cannot edit or delete any resource.' }, + { role: 'library_admin', name: 'Library Admin', description: 'The Library Admin has full control over the library, including managing users, modifying content, and handling publishing workflows. They ensure content is properly maintained and accessible as needed.' }, + { role: 'library_author', name: 'Library Author', description: 'The Library Author is responsible for creating, editing, and publishing content within a library. They can manage tags and collections but cannot delete libraries or manage users.' }, + { role: 'library_collaborator', name: 'Library Collaborator', description: 'The Library Collaborator can create and edit content within a library but cannot publish it. They support the authoring process while leaving final publishing to Authors or Admins.' }, + { role: 'library_user', name: 'Library User', description: 'The Library User can view and reuse content but cannot edit or delete any resource.' }, ]; export const libraryResourceTypes: ResourceMetadata[] = [ diff --git a/src/authz-module/libraries-manager/context.test.tsx b/src/authz-module/libraries-manager/context.test.tsx index bacfbc5..9755c70 100644 --- a/src/authz-module/libraries-manager/context.test.tsx +++ b/src/authz-module/libraries-manager/context.test.tsx @@ -16,7 +16,7 @@ jest.mock('@src/authz-module/data/hooks', () => ({ usePermissionsByRole: jest.fn().mockReturnValue({ data: [ { - key: 'library_author', + role: 'library_author', permissions: [ 'view_library_team', 'edit_library', diff --git a/src/authz-module/libraries-manager/context.tsx b/src/authz-module/libraries-manager/context.tsx index 67d9c05..5c8e2c7 100644 --- a/src/authz-module/libraries-manager/context.tsx +++ b/src/authz-module/libraries-manager/context.tsx @@ -51,7 +51,10 @@ export const LibraryAuthZProvider: React.FC = ({ children }: } const { data: libraryRoles } = usePermissionsByRole(LIBRARY_AUTHZ_SCOPE); - const roles = libraryRoles.map(role => ({ ...role, ...libraryRolesMetadata.find(r => r.key === role.key) } as Role)); + const roles = libraryRoles.map(role => ({ + ...role, + ...libraryRolesMetadata.find(r => r.role === role.role), + } as Role)); const value = useMemo((): LibraryAuthZContextType => ({ username: authenticatedUser.username, diff --git a/src/types.ts b/src/types.ts index ef8f126..3a7ebdd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,5 @@ export interface PermissionValidationRequest { action: string; - object?: string; scope?: string; } @@ -11,6 +10,7 @@ export interface PermissionValidationResponse extends PermissionValidationReques // Libraries AuthZ types export interface TeamMember { username: string; + fullName: string; email: string; roles: string[]; } @@ -23,7 +23,7 @@ export interface LibraryMetadata { } export interface RoleMetadata { - key: string; + role: string; name: string; description: string; } From 9aee1e4e535ee08f503af8775e7edf6ee2a25f76 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 2 Oct 2025 11:58:05 +1000 Subject: [PATCH 3/7] fix: update validation request body and getTeamMembers response --- src/authz-module/data/hooks.test.tsx | 4 ++-- src/authz-module/libraries-manager/context.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/authz-module/data/hooks.test.tsx b/src/authz-module/data/hooks.test.tsx index 7d1cdcc..c8fb969 100644 --- a/src/authz-module/data/hooks.test.tsx +++ b/src/authz-module/data/hooks.test.tsx @@ -10,13 +10,13 @@ jest.mock('@edx/frontend-platform/auth', () => ({ const mockMembers = [ { - displayName: 'Alice', + fullName: 'Alice', username: 'user1', email: 'alice@example.com', roles: ['admin', 'author'], }, { - displayName: 'Bob', + fullName: 'Bob', username: 'user2', email: 'bob@example.com', roles: ['collaborator'], diff --git a/src/authz-module/libraries-manager/context.tsx b/src/authz-module/libraries-manager/context.tsx index 5c8e2c7..e9c8905 100644 --- a/src/authz-module/libraries-manager/context.tsx +++ b/src/authz-module/libraries-manager/context.tsx @@ -8,7 +8,7 @@ import { usePermissionsByRole } from '@src/authz-module/data/hooks'; import { PermissionMetadata, ResourceMetadata, Role } from 'types'; import { libraryPermissions, libraryResourceTypes, libraryRolesMetadata } from './constants'; -const LIBRARY_TEAM_PERMISSIONS = ['act:view_library_team', 'act:manage_library_team']; +const LIBRARY_TEAM_PERMISSIONS = ['view_library_team', 'manage_library_team']; const LIBRARY_AUTHZ_SCOPE = 'lib:*'; export type AppContextType = { @@ -41,7 +41,7 @@ export const LibraryAuthZProvider: React.FC = ({ children }: if (!libraryId) { throw new Error('MissingLibrary'); } - const permissions = LIBRARY_TEAM_PERMISSIONS.map(action => ({ action, object: libraryId })); + const permissions = LIBRARY_TEAM_PERMISSIONS.map(action => ({ action, scope: libraryId })); const { data: userPermissions } = useValidateUserPermissions(permissions); const [{ allowed: canViewTeam }, { allowed: canManageTeam }] = userPermissions; From f2f0627c9db0aeea07b0f096b3ce93824ff1c422 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Wed, 8 Oct 2025 23:03:07 +1100 Subject: [PATCH 4/7] fix: remove extra permissions from libraries metadata --- src/authz-module/libraries-manager/constants.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/authz-module/libraries-manager/constants.ts b/src/authz-module/libraries-manager/constants.ts index a247829..590b654 100644 --- a/src/authz-module/libraries-manager/constants.ts +++ b/src/authz-module/libraries-manager/constants.ts @@ -17,18 +17,10 @@ export const libraryResourceTypes: ResourceMetadata[] = [ ]; export const libraryPermissions: PermissionMetadata[] = [ - { key: 'create_library', resource: 'library', description: 'Allows the user to create new libraries.' }, - { key: 'edit_library', resource: 'library', description: 'Allows the user to modify library settings and metadata.' }, - { key: 'delete_library', resource: 'library', description: 'Allows the user to delete the library and all its contents.' }, - { key: 'publish_library', resource: 'library', description: 'Publish the library (change from draft mode to published).' }, { key: 'view_library', resource: 'library', description: 'View content, search, filter, and sort within the library.' }, - { - key: 'manage_library_tags', resource: 'library', description: 'Add or remove tags from content.', - }, + { key: 'manage_library_tags', resource: 'library', description: 'Add or remove tags from content.' }, - { key: 'create_library_content', resource: 'library_content', description: 'Create new components or content units.' }, { key: 'edit_library_content', resource: 'library_content', description: 'Edit content in draft mode' }, - { key: 'delete_library_content', resource: 'library_content', description: 'Delete individual content (not collections).' }, { key: 'publish_library_content', resource: 'library_content', description: 'Publish content, making it available for reuse' }, { key: 'reuse_library_content', resource: 'library_content', description: 'Reuse published content within a course.' }, From 84554f3aa49281f330f2e4b0ed6f35a57f7cb07b Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Thu, 9 Oct 2025 23:45:58 +1100 Subject: [PATCH 5/7] fix: update the library scope according to the backend requirements --- src/authz-module/data/api.ts | 6 +++--- src/authz-module/data/hooks.test.tsx | 2 +- src/authz-module/libraries-manager/context.tsx | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/authz-module/data/api.ts b/src/authz-module/data/api.ts index fd570d6..ea1e223 100644 --- a/src/authz-module/data/api.ts +++ b/src/authz-module/data/api.ts @@ -16,7 +16,7 @@ export type PermissionsByRole = { // TODO: replece api path once is created export const getTeamMembers = async (object: string): Promise => { - const { data } = await getAuthenticatedHttpClient().get(getApiUrl(`/api/authz/v1/roles/users?scope=${object}`)); + const { data } = await getAuthenticatedHttpClient().get(getApiUrl(`/api/authz/v1/roles/users/?scope=${object}`)); return camelCaseObject(data.results); }; @@ -32,8 +32,8 @@ export const getLibrary = async (libraryId: string): Promise => }; export const getPermissionsByRole = async (scope: string): Promise => { - const url = new URL(getApiUrl('/api/authz/v1/roles')); + const url = new URL(getApiUrl('/api/authz/v1/roles/')); url.searchParams.append('scope', scope); const { data } = await getAuthenticatedHttpClient().get(url); - return camelCaseObject(data); + return camelCaseObject(data.results); }; diff --git a/src/authz-module/data/hooks.test.tsx b/src/authz-module/data/hooks.test.tsx index c8fb969..79fafc4 100644 --- a/src/authz-module/data/hooks.test.tsx +++ b/src/authz-module/data/hooks.test.tsx @@ -132,7 +132,7 @@ describe('usePermissionsByRole', () => { ]; getAuthenticatedHttpClient.mockReturnValue({ - get: jest.fn().mockResolvedValue({ data: mockRoles }), + get: jest.fn().mockResolvedValue({ data: { results: mockRoles } }), }); const wrapper = createWrapper(); diff --git a/src/authz-module/libraries-manager/context.tsx b/src/authz-module/libraries-manager/context.tsx index e9c8905..0def9e0 100644 --- a/src/authz-module/libraries-manager/context.tsx +++ b/src/authz-module/libraries-manager/context.tsx @@ -9,7 +9,8 @@ import { PermissionMetadata, ResourceMetadata, Role } from 'types'; import { libraryPermissions, libraryResourceTypes, libraryRolesMetadata } from './constants'; const LIBRARY_TEAM_PERMISSIONS = ['view_library_team', 'manage_library_team']; -const LIBRARY_AUTHZ_SCOPE = 'lib:*'; +// Note: This value can change in the future +const LIBRARY_AUTHZ_SCOPE = '*'; export type AppContextType = { authenticatedUser: { From 3c7acfeecca3718b11a1863c62044ca3132d4e04 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Fri, 10 Oct 2025 01:05:24 +1100 Subject: [PATCH 6/7] refactor: fix tsdoc for usePermissionsByRole --- src/authz-module/data/hooks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/authz-module/data/hooks.ts b/src/authz-module/data/hooks.ts index 9af0374..658f548 100644 --- a/src/authz-module/data/hooks.ts +++ b/src/authz-module/data/hooks.ts @@ -37,7 +37,7 @@ export const useTeamMembers = (object: string) => useQuery( * * @example * ```tsx - * const { data: roles, isLoading, isError } = useTeamMembers('lib:123'); + * const { data: roles } = usePermissionsByRole('lib:123'); * ``` */ export const usePermissionsByRole = (scope: string) => useSuspenseQuery({ From 7a834bffc18bce5ff9730e0300f2db6f787764a3 Mon Sep 17 00:00:00 2001 From: Diana Olarte Date: Mon, 13 Oct 2025 19:56:53 +1100 Subject: [PATCH 7/7] fix: add delete permissions and user the libraryId to retrieve the mapping of roles and permissions --- src/authz-module/libraries-manager/constants.ts | 1 + src/authz-module/libraries-manager/context.tsx | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/authz-module/libraries-manager/constants.ts b/src/authz-module/libraries-manager/constants.ts index 590b654..7b3686e 100644 --- a/src/authz-module/libraries-manager/constants.ts +++ b/src/authz-module/libraries-manager/constants.ts @@ -18,6 +18,7 @@ export const libraryResourceTypes: ResourceMetadata[] = [ export const libraryPermissions: PermissionMetadata[] = [ { key: 'view_library', resource: 'library', description: 'View content, search, filter, and sort within the library.' }, + { key: 'delete_library', resource: 'library', description: 'Allows the user to delete the library and all its contents.' }, { key: 'manage_library_tags', resource: 'library', description: 'Add or remove tags from content.' }, { key: 'edit_library_content', resource: 'library_content', description: 'Edit content in draft mode' }, diff --git a/src/authz-module/libraries-manager/context.tsx b/src/authz-module/libraries-manager/context.tsx index 0def9e0..419744d 100644 --- a/src/authz-module/libraries-manager/context.tsx +++ b/src/authz-module/libraries-manager/context.tsx @@ -9,8 +9,6 @@ import { PermissionMetadata, ResourceMetadata, Role } from 'types'; import { libraryPermissions, libraryResourceTypes, libraryRolesMetadata } from './constants'; const LIBRARY_TEAM_PERMISSIONS = ['view_library_team', 'manage_library_team']; -// Note: This value can change in the future -const LIBRARY_AUTHZ_SCOPE = '*'; export type AppContextType = { authenticatedUser: { @@ -51,7 +49,7 @@ export const LibraryAuthZProvider: React.FC = ({ children }: throw new Error('NoAccess'); } - const { data: libraryRoles } = usePermissionsByRole(LIBRARY_AUTHZ_SCOPE); + const { data: libraryRoles } = usePermissionsByRole(libraryId); const roles = libraryRoles.map(role => ({ ...role, ...libraryRolesMetadata.find(r => r.role === role.role),