Before the words

The business system with user system often needs the ability of permission control. In the development mode, the permission control is mainly realized by the backend combined with the template engine, while in the development mode, the permission configuration is negotiated by the front and back ends for data permission control and component permission control respectively.

The body of the

The permission configuration format is not limited. For the following demonstration, the permission configuration is set as follows:

export type IAuthConfig = {
  /** Permission identifier list */
  keys: string[];
};
Copy the code

And provide a Hook to directly obtain the current user information:

export type IUser = {
  /** Permission configuration */
  authConfig: IAuthConfig;
};

/** user info Hook */
export function useUser() {
  / /... slightly
  return user;
}
Copy the code

First, we define a permission Hook, the content is as follows:

/** * converts to permission identifier array *@param Auth Permission identifier *@returns* /
function getAuthKeys(auth: string | string[]) {
  return Array.isArray(auth) ? auth : [auth];
}

/** Permission authentication type */
export enum EAuthType {
  /** or permission (at least one match) */
  "some" = "some"./** With permissions (all matches) */
  "every" = "every",}/** permission Hook */
export function useAuth() {
  const { authConfig } = useUser();

  // List of permissions
  const authKeys = useMemo(() = >authConfig? .keys ?? [], [authConfig]);// Verify whether you have permission
  const hasAuth = useCallback(
    (auth: string | string[].type? : EAuthType) = >
      getAuthKeys(auth)[type ?? EAuthType.every]((key) = >
        authKeys.includes(key)
      ),
    [authKeys]
  );

  const ret: [typeof authKeys, typeof hasAuth] = [authKeys, hasAuth];
  return ret;
}
Copy the code

1. Control mode

For front-end development generally only need to do component control, there are many ways to write control.

1.1 Direct Calculation

const Cmpt: React.FC = () = > {
  const [, hasAuth] = useAuth();
  // Calculate whether permissions are granted
  const authorized = useMemo(() = > hasAuth("test"), [hasAuth]);

  / /... slightly
};
Copy the code

1.2 General permission Hoc

export function withAuth<P extends Record<string.unknown> = {}>(
  auth: string | string[].type? : EAuthType ) {return function (Component: any) {
    const WrappedComponent: React.FC<P> = (props) = > {
      const [, hasAuth] = useAuth();

      constinstance = React.createElement(Component, { ... props, });// Calculate whether permissions are granted
      const authorized = hasAuth(auth, type);

      / /... slightly
    };

    WrappedComponent.displayName = `withAuth(${getDisplayName(Component)}) `;

    return WrappedComponent;
  };
}
Copy the code

1.3 Permission Package Components

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
  const [, hasAuth] = useAuth();
  // Calculate whether permissions are granted
  const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);

  / /... slightly
};
Copy the code

2. Control the outcome

Common control results are to control the display or hiding of components, or to do custom rendering of components depending on whether permissions are available.

For the convenience of the demonstration, the permission package component is used for the explanation.

2.1 Explicit and implicit control

Components with permissions are rendered directly, otherwise null is returned, and explicit and implicit control can be implemented. The permission package component is implemented as follows:

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
  const [, hasAuth] = useAuth();
  // Calculate whether permissions are granted
  const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);
  // Components with permissions are rendered directly, otherwise null is returned
  return authorized ? children : null;
};
Copy the code

Just wrap the permission component around the component that requires permission control.

const Cmpt: React.FC = () = > {
  <>
    <AuthWrapper auth="test">
      <Button>Hello World</Button>
    </AuthWrapper>
  </>;
};
Copy the code

2.2 Custom Rendering

In actual business scenarios, users need to be reminded that they do not have permissions. In this case, the permission package component needs to support custom rendering, which can be implemented in various ways:

  • Static props injection
  • Dynamic props mapping
  • render props

In contrast, the render props are more free. After completing the permission package component, the implementation is as follows:

type IProps = {
  /** Permission identifier */
  auth: string | string[];
  /** Authentication type */
  type? : EAuthType;/** subcontent */
};

const AuthWrapper: React.FC<IProps> = ({ auth, type, children }) = > {
  const [, hasAuth] = useAuth();
  // Calculate whether permissions are granted
  const authorized = useMemo(() = > hasAuth(auth, type), [auth, type, hasAuth]);

  // Custom render
  if (typeof children === "function") {
    return children(authorized);
  }

  // explicit and implicit control
  return authorized ? children : null;
};
Copy the code

It is now possible to render a prompt for an unauthorized component:

const Cmpt: React.FC = () = > {
  <>
    <AuthWrapper auth="test">
      {(authorized) => <Button disabled={! authorized}>Hello World</Button>}
    </AuthWrapper>
  </>;
};
Copy the code

3. Permission data

The granularity of front-end development component control depends on the type of permission data, which can be divided into static permission and dynamic permission.

Static rights can be obtained once login authentication is complete, while dynamic rights are verified during data operation.

It is not hard to see static permissions used to control routing, pages, or crude operation permissions. On the other hand, dynamic rights can control the operation rights of dynamic data in finer granularity (with the permission control capability of back-end data).

3.1 Static Rights

As described earlier, after login authentication, you can obtain the permission id from the user information. The previous chestnut is also a static permission example.

3.2 Dynamic Rights

Dynamic permission verification is also common in service systems. For example, users can view list data but cannot view details about unauthorized data.

It can be seen that for users, the acquired dynamic list data needs to be verified one by one. At this time, we transform the permission Hook and package components, and the code after transformation is as follows:

type IAuthDynamicConfig = {
  / /... slightly
};

/** permission Hook */
export function useAuth() {
  / /... slightly

  // Dynamic check whether permissions are available
  const dynamicAuth = useCallback(
    (auth: string | string[].type? : EAuthType, config? : IAuthDynamicConfig) = >
      // Batch asynchronous permission verification is omitted
      append2DynamicAuthTask(auth, type, config),
    []
  );

  const ret: [typeof authKeys, typeof hasAuth, typeof dynamicAuth] = [
    authKeys,
    hasAuth,
    dynamicAuth,
  ];
  return ret;
}

/** Permission package component */
const AuthWrapper: React.FC<IProps> = ({ auth, type, config, children }) = > {
  const [, hasAuth, dynamicAuth] = useAuth();

  const [authorized, setAuthorized] = useState(false);

  // Calculate whether permissions are granted
  useEffect(() = > {
    if (config === undefined) {
      setAuthorized(hasAuth(auth, type));
      return;
    }
    dynamicAuth(auth, type, config).then(setAuthorized);
  }, [auth, type, config, hasAuth, dynamicAuth]);

  / /... slightly
};
Copy the code

The usage is as follows:

const Cmpt: React.FC = () = > {
  / /... slightly

  <>
    {data.map((item) => (
      <div key={item.id}>
        <div>{item.title}</div>
        <div>
          <AuthWrapper
            auth="detail"
            config={{
              module: "demo",
              identify: item.id,}} >
            {(authorized) => (
              <Button disabled={! authorized} onClick={handleDetail}>details</Button>
            )}
          </AuthWrapper>
        </div>
      </div>
    ))}
  </>;
};
Copy the code