Implementando o controle de acesso com a biblioteca react-abac em um projeto React grande e complexo

Apr 26 2023
Neste artigo vou mergulhar em como usei a biblioteca react-abac criada por Rik Hoffbauer para gerenciar funções de usuário e regras de acessibilidade em um projeto com inúmeras dependências entre usuários. Gerenciar o controle de acesso em projetos React grandes e complexos pode ser uma tarefa desafiadora, especialmente ao lidar com uma infinidade de funções de usuário, regras de acessibilidade e dependências entre usuários.

Neste artigo vou mergulhar em como usei a biblioteca react-abac criada por Rik Hoffbauer para gerenciar funções de usuário e regras de acessibilidade em um projeto com inúmeras dependências entre usuários.

Foto de Andrew Moca no Unsplash

Gerenciar o controle de acesso em projetos React grandes e complexos pode ser uma tarefa desafiadora, especialmente ao lidar com uma infinidade de funções de usuário, regras de acessibilidade e dependências entre usuários. Neste artigo, exploraremos como utilizei a biblioteca react-abac para simplificar o controle de acesso em tal projeto. Fornecerei exemplos reais da estrutura do projeto, juntamente com uma explicação detalhada de como a biblioteca react-abac foi integrada ao projeto para melhorar a limpeza e a manutenção do código.

estrutura ABAC

estrutura ABAC
  1. O gancho onde a mágica acontece — withAbac.tsx
  2. 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;
    

2. Lista de usuários — 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 };

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 };

const showSettingsUserRequests= ({ isAuthenticated, isPrepaid, hasBillPaid, ...anyOtherData}) =>
    isAuthenticated && authType === "AUTH_TYPE" && !isPrepaid && hasBillPaid;

export { showSettingsUserRequests }

export const SHOW_SETTINGS_USER_REQUESTS = 'SHOW_SETTINGS_USER_REQUESTS'

      
                
Photo by Tim De Pauw on Unsplash

Reagir Integração de Componentes

Após definir a estrutura do projeto, a biblioteca react-abac foi integrada aos componentes React. Veja um exemplo de como o controle de acesso foi implementado usando o 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 />;

Dentro do componente, o useDisplayElementgancho personalizado é chamado para gerenciar a exibição do arquivo userRequestsHiddenContainer. O componente então retorna um NewRequestscomponente encapsulado em um arquivo NewRequestsWrapper.

O RenderChangeSIMcomponente é criado passando uma função que renderiza o NewRequestsWrapper, a permissão necessária SHOW_SETTINGS_USER_REQUESTSe quaisquer dados adicionais necessários para a avaliação do controle de acesso.

Por fim, o RenderChangeSIMcomponente é renderizado, o que exibirá condicionalmente o componente original com base nas permissões do usuário e nas regras especificadas no arquivo useAbacHook.

Uau! Acertamos em cheio e o código está o mais limpo possível!
Agora, mostrarei como era a implementação anterior, para que você tenha uma visão geral melhor dos benefícios de usar o Controle de acesso baseado em atributos em seu aplicativo.

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>
</>
)

Foto de Ben White no Unsplash

Ter um código limpo, uma arquitetura fácil de entender e recursos de fácil manutenção são aspectos essenciais de qualquer projeto de desenvolvimento de software bem-sucedido. Esses princípios levam a inúmeros benefícios que impactam tanto os desenvolvedores quanto os usuários finais do aplicativo.

  1. Legibilidade aprimorada : código limpo e uma arquitetura bem estruturada facilitam a leitura e a compreensão da base de código pelos desenvolvedores. Isso reduz o tempo necessário para integrar novos membros da equipe e permite que os desenvolvedores trabalhem com mais eficiência.
  2. Depuração e solução de problemas mais fáceis : quando o código está limpo e bem organizado, fica mais simples identificar e corrigir bugs, resultando em tempos de resolução mais rápidos e um aplicativo mais estável.
  3. Melhor colaboração : código limpo e uma arquitetura clara facilitam a colaboração entre os membros da equipe, garantindo que todos possam entender rapidamente a base de código e contribuir de forma eficaz.
  4. Escalabilidade aprimorada : uma arquitetura de projeto bem projetada permite uma escalabilidade mais fácil, pois fornece uma base sólida para adicionar novos recursos e funcionalidades sem afetar negativamente a base de código existente.
  5. Dívida técnica reduzida : código limpo e recursos sustentáveis ​​contribuem para a redução da dívida técnica, pois os desenvolvedores podem evitar hacks ou soluções alternativas que podem causar problemas a longo prazo.
  6. Maior capacidade de manutenção : quando o código é limpo e a arquitetura é direta, a manutenção do aplicativo se torna um desafio menor. Isso torna mais fácil aplicar atualizações, otimizar o desempenho e adaptar o aplicativo aos requisitos em constante mudança.
  7. Maior qualidade de código : um foco em código limpo e uma arquitetura bem planejada resulta em maior qualidade geral do código, levando a um aplicativo mais robusto e confiável.

Conclusão

A biblioteca react-abac criada pela biblioteca Rik Hoffbauer provou ser uma ferramenta poderosa para gerenciar o controle de acesso em um grande e complexo projeto React. A biblioteca permitiu uma estrutura de código mais simplificada e sustentável, facilitando o gerenciamento de funções de usuário, regras de acessibilidade e dependências entre usuários. Ao compartilhar minha experiência com a biblioteca react-abac, espero inspirar outras pessoas a considerar a utilização dessa biblioteca em seus próprios projetos, levando a um código mais eficiente e sustentável.