Search code examples
reactjspermissionsreact-hooksreact-adminauthprovider

How do I configure the getPermissions() method in the AuthProvider in react-admin?


I have tried creating custom hooks to call the DataProvider getOne() method to fetch the permissions based on the username I get from the authProvider after login, but the constant requirement of calling the hook from the body of the function throws an error because I call it from a method.

Where is the permissions coming from? How is 'ra-core' calling this getPermissions()? Why is there an error called .then() not a function in getPermissions()?

There needs to be better documentation on this aspect of the AuthProvider to help even experienced react-admin folks. Just saying.

Hook to fetch permissions:

const useFetchPermissions = ({ emailId }) => {
  const dataProvider = useDataProvider();
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState();
  console.log("fetching permissions");

  const [permissions, setPermissions] = useState();

  useEffect(() => {
    dataProvider
      .getOne("users/permissions", { id: emailId })
      .then(({ data }) => {
        setPermissions(data);
        setLoading(false);
      })
      .catch((error) => {
        setError(error);
        setLoading(false);
      });
  }, []);

  if (loading) return <Loading />;
  if (error) return <Error />;
  if (!permissions) return null;

  return permissions;
};

AuthPRovider login method:

login: () => {
    /* ... */
    console.log("Logging in..");
    //localStorage.setItem("permissions", "CREATE_ITEM");
    return tfsAuthProvider.login();

AuthProvider getPermissions method:

getPermissions: () => {
    const role = localStorage.getItem("permissions");
    console.log(role);
    //useFetchPermissions(authProvider.getAccount().userName); throw error
    return role === null ? Promise.resolve() : role;
  },

App.js dataProvider(url,useHttpClient) calls this:

const useHttpClient = (url, options = {}) => {
  if (!options.headers) {
    options.headers = new Headers({ Accept: "application/json" });
  }
  // add your own headers here
  //options.headers.set("Access-Control-Allow-Origin", "true");
  //const token = localStorage.getItem("token");
  //const userName = authProvider.getAccount().userName;
  //const [permissions, setPermissions] = useState();
  //const permissions = useFetchPermissions(userName);  throws error

  //localStorage.setItem("permissions", permissions);
  options.headers.set(
    "Authorization",
    `Bearer ${authProvider.getAccount().userName}`
  );
  return fetchUtils.fetchJson(url, options);
};

Solution

  • Thanks to @KiaKaha 's answer I was able to implement something that worked for me that's a workaround the exceptions of react-hooks.

    EDIT: Here's an improved solution

    login: () => {
        /* ... */
        console.log("Logging in..");
    
        console.log(tfsAuthProvider.authenticationState);
    
       
        return tfsAuthProvider.login().then(async () => {
          const result = await fetch(
            process.env.REACT_APP_BASE_URL +
              "/users/permissions/" +
              tfsAuthProvider.getAccount().userName
          );
          
          const body = await result.json();
          console.log(body);
          localStorage.setItem("permissions", body);
          return Promise.resolve();
        });
       
      },
    
    1. The dataProvider.getOne() method is a react-hook and hence I was not able to call it. So used fetch(). Used environment variable to fetch the URL that dataProvider uses. (This still stays true).

    <-- Everything after this is unnecessary , may be helpful to solve other problems.-->

    1. The response could not be converted to string directly so I used the response.body.getReader() and UTF8 decoder to decode the result of read().

      login: () => {
      
       console.log("Logging in..");
      
       console.log(tfsAuthProvider.authenticationState);
      
       const utf8Decoder = new TextDecoder("utf-8");
      
       return tfsAuthProvider
         .login()
         .then(() =>
           fetch(
             new Request(
               process.env.REACT_APP_BASE_URL +
                 "/users/permissions/" +
                 tfsAuthProvider.getAccount().userName,
               {
                 method: "GET",
                 headers: new Headers({ "Content-Type": "application/json" }),
               }
             )
           )
         )
         .then((response) => {
           if (response.status < 200 || response.status >= 300) {
             throw new Error(response.statusText);
           }
           return response.body.getReader();
         })
         .then((reader) => reader.read())
         .then(({ done, value }) => {
           // if (done) {
           //   controller.close();
           //   return;
           // }
      
           const permissions = utf8Decoder.decode(value);
      
           console.log(permissions);
           localStorage.setItem("permissions", permissions);
           return Promise.resolve();
         });
      },
      

    Hope this helps anyone coming through this rabbit hole.

    More on Readable Stream Objects - https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams

    More on the login implementation - https://marmelab.com/react-admin/Authorization.html