В этой статье я расскажу, как использовал библиотеку react-abac, созданную Риком Хоффбауэром, для управления ролями пользователей и правилами доступности в проекте с многочисленными зависимостями между пользователями.
Управление контролем доступа в больших и сложных проектах React может быть сложной задачей, особенно при работе с множеством пользовательских ролей, правил доступа и зависимостей между пользователями. В этой статье мы рассмотрим, как я использовал библиотеку react-abac для упрощения управления доступом в таком проекте. Я приведу реальные примеры структуры проекта, а также подробно объясню, как библиотека react-abac была интегрирована в проект для повышения чистоты кода и удобства сопровождения.
Структура ABAC
- Хук, в котором происходит волшебство — withAbac.tsx
import React from 'react'; import { AbacProvider, AllowedTo } from 'react-abac'; import { useSelector } from 'react-redux'; import { ApplicationState } from '@redux/interfaces'; import { permissions, rules } from './abac.config'; import { users } from './users'; // Define the useAbacHook, which takes a Component, permission, and optional data as parameters const useAbacHook = (Component, permission, data = {}) => (props) => { // Define a function to get the user based on the userRole from state const getUser = (user: string) => user ? users.find(({ name }) => name === user) : 'NoRole' // Get the userRole from the Redux store using useSelector const { userRole} = useSelector((state: ApplicationState) => state.userInfo); // Return the AbacProvider with the user, roles, permissions, and rules // Use the AllowedTo component to control access to the Component based on the permission return ( <AbacProvider user={getUser(userRole)} roles={getUser(userRole)?.roles} permissions={getUser(userRole)?.permissions} rules={rules as any} > <AllowedTo perform={permissions[permission]} data={props?.data || data} no={() => <React.Fragment></React.Fragment>} > <Component {...props} /> </AllowedTo> </AbacProvider> ); }; export default useAbacHook;
Этот код определяет функцию useAbacHook
, которая является компонентом более высокого порядка, который оборачивает данный компонент React в компоненты AbacProvider
и AllowedTo
из библиотеки react-abac. Ловушка принимает три параметра: компонент, который необходимо обернуть, требуемое разрешение на доступ к компоненту и необязательный объект данных для предоставления дополнительной информации для оценки управления доступом.
Функция getUser
определена в хуке для извлечения объекта пользователя на основе userRole
, полученного из хранилища Redux. Хук возвращает новый компонент, который обертывает исходный компонент компонентами AbacProvider
и AllowedTo
. AbacProvider
настраивается с пользователем, его ролями, разрешениями и правилами управления доступом. Компонент AllowedTo
определяет, следует ли отображать исходный компонент, на основе предоставленного разрешения и данных. Если пользователю не разрешен доступ к компоненту, вместо этого отображается пустой React.Fragment.
2. Список пользователей — users.tsx
const users = [ { id: 1, name: 'PowerUser', roles: ['PowerUser'], permissions: [] }, { id: 2, name: 'User', roles: ['User'], permissions: [] }, { id: 3, name: 'SubUser', roles: ['SubUser'], permissions: [] }, { id: 4, name: 'AuthorizedPerson', roles: ['AuthorizedPerson'], permissions: [] }, { id: 5, name: 'NoRole', roles: ['NoRole'], permissions: [] }, ]; export { users };
3. Конфигурационный файл, в котором можно включить/отключить доступ к различным компонентам — abac.config.tsx
import { SHOW_SETTINGS_USER_REQUESTS } from './constants' import { showSettingsUserRequests } from '/rulesList' import { PowerUser, User, SubUser, AuthorizedPerson, NoRole} from './userRolesConstants' // Make roles and permissions readonly const roles = { PowerUser, User, SubUser, AuthorizedPerson, NoRole } as const const permissions = { SHOW_SETTINGS_USER_REQUESTS } as const const rules = { [roles.User]: // this access will be handled by showSettingsUserRequests // the result of the function should be a boolean // data is the minimum necessary information for establishing the access for User role [permissions.SHOW_SETTINGS_USER_REQUESTS]: (data) => showSettingsUserRequests(data), // more rules for User role }, [roles.SubUser]: { // SubUser won't have access [permissions.SHOW_SETTINGS_USER_REQUESTS]:false // more rules for SubUser role }, [roles.PowerUser]: { // PowerUser will have access [permissions.SHOW_SETTINGS_USER_REQUESTS]: true // more rules for PowerUser role } [roles.NoRole]: { // NoRole won't have access [permissions.SHOW_SETTINGS_USER_REQUESTS]: false // more rules for NoRole } } export { roles, permissions, rules };
4. Список правил отображения: rulesList.tsx
const showSettingsUserRequests= ({ isAuthenticated, isPrepaid, hasBillPaid, ...anyOtherData}) => isAuthenticated && authType === "AUTH_TYPE" && !isPrepaid && hasBillPaid; export { showSettingsUserRequests }
5. Файл констант — constants.tsx
export const SHOW_SETTINGS_USER_REQUESTS = 'SHOW_SETTINGS_USER_REQUESTS'
Знаю, знаю… Давайте перейдем к самому интересному! :)
Интеграция компонентов React
После определения структуры проекта библиотека react-abac была интегрирована в компоненты React. Вот пример реализации контроля доступа с помощью useAbacHook:
// Destructure the SHOW_SETTINGS_USER_REQUESTS constant from the permissions object const { SHOW_SETTINGS_USER_REQUESTS } = permissions; // Create the RenderChangeSIM component using the useAbacHook higher-order component const RenderChangeSIM = useAbacHook( () => { // Call the custom useDisplayElement hook to manage the display of 'userRequestsHiddenContainer' useDisplayElement('userRequestsHiddenContainer'); // Return the component wrapped in the NewRequestsWrapper component return ( <NewRequestsWrapper> <NewRequests buttonText={seeMoreButtonText} text={`${totalPendingRequests} ${userRequestsDescription}`} title={userRequestsHeadline} handleChange={handleChange} /> </NewRequestsWrapper> ); }, // Pass the required permission (SHOW_SETTINGS_USER_REQUESTS) to the useAbacHook SHOW_SETTINGS_USER_REQUESTS, { hasBillPaid, isPrepaid, subscriptionType, authType, ...anyOtherDataToPass} ); // Render the RenderChangeSIM component that is controlled by the useAbacHook return <RenderChangeSIM />;
Этот код создает компонент RenderChangeSIM
, используя компонент более высокого порядка useAbacHook
. Разрешение SHOW_SETTINGS_USER_REQUESTS
деструктурируется из объекта permissions
и передается в качестве аргумента в useAbacHook
.
Внутри компонента вызывается пользовательский хук useDisplayElement
для управления отображением файла userRequestsHiddenContainer
. Затем компонент возвращает компонент NewRequests
, завернутый в файл NewRequestsWrapper
.
Компонент RenderChangeSIM
создается путем передачи функции, которая отображает NewRequestsWrapper
, необходимое разрешение SHOW_SETTINGS_USER_REQUESTS
и любые дополнительные данные, необходимые для оценки управления доступом.
Наконец, визуализируется компонент RenderChangeSIM
, который условно отображает исходный компонент на основе разрешений пользователя и правил, указанных в файле useAbacHook
.
Ух ты! Мы сделали это, и код стал максимально чистым!
Теперь я покажу вам, как выглядела предыдущая реализация, чтобы вы могли лучше понять преимущества. использования контроля доступа на основе атрибутов в вашем приложении.
useEffect(() => { if (isAuthenticated && accessToken) { // ... (code removed for brevity) } else { navigate('/'); } }, [sessionRole, isAuthenticated]) useEffect(() => { if (sessionType === PREPAID) { return navigate('/recharge'); } if ( (userRole=== POWER_USER || userRole=== USER || userRole=== SUB_USER) && !hybrid ) { return navigate('/pay-bill-unauthenticated'); } }, [userRole, sessionType]) return (<> { hasBillPaid || !isPrepaid ? <NewRequestsWrapper> <NewRequests buttonText={seeMoreButtonText} text={`${totalPendingRequests} ${userRequestsDescription}`} title={userRequestsHeadline} handleChange={handleChange} /> </NewRequestsWrapper> : <p> You do not have access here! </p> </> )
Что ты скажешь сейчас?
Я бы сказал, что это большое улучшение. Код стал намного чище, проще для понимания и обслуживания.
Чистый код, простая для понимания архитектура и легко поддерживаемые функции являются важными аспектами любого успешного проекта по разработке программного обеспечения. Эти принципы приводят к многочисленным преимуществам, которые влияют как на разработчиков, так и на конечных пользователей приложения.
- Улучшенная читабельность. Чистый код и хорошо структурированная архитектура облегчают разработчикам чтение и понимание кодовой базы. Это сокращает время, необходимое для адаптации новых членов команды, и позволяет разработчикам работать более эффективно.
- Более простая отладка и устранение неполадок: когда код чистый и хорошо организованный, становится проще выявлять и исправлять ошибки, что приводит к более быстрому разрешению проблем и более стабильной работе приложения.
- Улучшение совместной работы. Чистый код и понятная архитектура облегчают совместную работу членов команды, гарантируя, что каждый может быстро понять кодовую базу и эффективно внести свой вклад.
- Улучшенная масштабируемость. Хорошо продуманная архитектура проекта упрощает масштабирование, поскольку обеспечивает прочную основу для добавления новых функций и возможностей без негативного влияния на существующую кодовую базу.
- Сокращение технического долга. Чистый код и поддерживаемые функции способствуют сокращению технического долга, поскольку разработчики могут избежать взломов или обходных путей, которые могут вызвать проблемы в долгосрочной перспективе.
- Повышенная ремонтопригодность. Когда код чистый, а архитектура проста, обслуживание приложения становится менее сложной задачей. Это упрощает применение обновлений, оптимизацию производительности и адаптацию приложения к изменяющимся требованиям.
- Более высокое качество кода. Акцент на чистом коде и хорошо спланированной архитектуре в конечном итоге приводит к более высокому общему качеству кода, что приводит к более надежному и надежному приложению.
Отдавая приоритет чистому коду, понятной архитектуре и поддерживаемым функциям, разработчики могут создавать программы, которые не только более эффективны и надежны, но и более приятны в работе. Это, в свою очередь, приводит к более счастливым разработчикам, удовлетворенным конечным пользователям и более успешному проекту в целом.
Заключение
Библиотека react-abac, созданная Риком Хоффбауэром, оказалась мощным инструментом для управления контролем доступа в большом и сложном проекте React. Библиотека позволила создать более упорядоченную и удобную в сопровождении структуру кода, упростив управление ролями пользователей, правилами доступа и зависимостями между пользователями. Делясь своим опытом работы с библиотекой react-abac, я надеюсь вдохновить других на использование этой библиотеки в своих проектах, что в конечном итоге приведет к созданию более эффективного и удобного кода.