Steams / ra-data-hasura-graphql

React-admin data provider for Hasura GraphQL endpoints
MIT License
211 stars 33 forks source link

Help requested: authentication code block #54

Closed caston1981 closed 4 years ago

caston1981 commented 4 years ago

Hello,

I am trying to get up and running with a test RA/Hasura dashboard. I have used a mix of the code in the readme.md and a code snippet example given by webdeb here.

For now instead of bearer I have just enabled the secret key. I don't mind changing this and expect I will need to at some point based on the authentication and security model used by Hasura.

For context here is my full github repo. Here is the code snippet from the App.js in my repo.

Any assistance would be much appreciated and I will pay it forward by helping write documentation for beginners.

I have changed the code-block to:

/* testing this code  */

componentDidMount() { 
  const httpLink = createHttpLink({ uri: 'http://hasura:8080/v1/graphql' });
  const middlewareLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
      'x-hasura-admin-secret': `myadminsecretkey`,
        /* Authorization: "Bearer " + yourToken, // <--- SET THE TOKEN */
      },
    });
    return forward(operation);
  });

  // use with apollo-client
  const link = middlewareLink.concat(httpLink);

  buildHasuraProvider({
    clientOptions: { link: link },
  }).then((dataProvider) =>
    this.setState({
      dataProvider: enhanceDataProvider(dataProvider),
    })
  );
/* end test  */

Please note I haven't tried building it yet because the first '{' after componentDidMount() has red underline squiggle and gives the message: ';' expected. ts(1005)
This is in visual studio code.

If we look at the instructions and example code in the readme.md for "ra-data-hasura-graphql" we have:

Authentication

To send authentication headers, declare your own apollo client.

import ApolloClient from 'apollo-boost';

const client = new ApolloClient({
  uri: 'http://localhost:8080/v1/graphql',
  headers: {
    'x-hasura-admin-secret': `myadminsecretkey`,
    // 'Authorization': `Bearer xxxx`,
  }
});

// When building your provider inside your component
// set up the client like this
buildHasuraProvider({ client })

So I don't know what is meant to be in 'client' so I have referred to the code snippet provided by webdeb. However, that code snippet hasn't actually called ApolloClient and I can tell as the "import" line is still still faded in visual studio code.

gmatht is helping with the project too. @gmatht perhaps these will help us? Set up a GraphQL client with Apollo React componentDidMount

caston1981 commented 4 years ago

OK I know this code still doesn't work (as the '}' after render and "default app" at the end has the red underline squiggle in visual studio code) but am I at least on the right track?

I noticed I had buildHasuraProvider twice so I commented out the first one. I moved the render and return functions to after the code block we are testing.

I have copied the imports from the "Set up a GraphQL client with Apollo" part of the tutorial but most of them aren't used yet as they are still faded. I added createHttpLink and ApolloLink as I noticed them used in the code block we are testing. They don't fade which is a huge bonus.

// in App.js
import React, { Component } from 'react';
import buildHasuraProvider from 'ra-data-hasura-graphql';
import { Admin, Resource } from 'react-admin';
import { ItemList, ItemEdit, ItemCreate } from './items';
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink, createHttpLink, ApolloLink } from 'apollo-link-http';
import { ApolloProvider } from '@apollo/react-hooks';

class App extends Component {
    constructor() {
        super();
        this.state = { dataProvider: null };
    }
    /* componentDidMount() {
        buildHasuraProvider({
            clientOptions: { uri: 'http://hasura:8080/v1/graphql' },
        }).then((dataProvider) => this.setState({ dataProvider }));
    } */

/* testing this code  */

componentDidMount() {
  const httpLink = createHttpLink({ uri: 'http://hasura:8080/v1/graphql' });
  const middlewareLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
      'x-hasura-admin-secret': `myadminsecretkey`,
        /* Authorization: "Bearer " + yourToken, // <--- SET THE TOKEN */
      },
    });
    return forward(operation);
  });

  // use with apollo-client
  const link = middlewareLink.concat(httpLink);

  buildHasuraProvider({
    clientOptions: { link: link },
  }).then((dataProvider) =>
    this.setState({
      dataProvider: enhanceDataProvider(dataProvider),
    })
  );
/* end test  */

render() {
  const { dataProvider } = this.state;

  if (!dataProvider) {
      return <div>Loading</div>;
  }

  return (
      <Admin dataProvider={dataProvider}>
          <Resource name="items" list={ItemList} edit={ItemEdit} create={ItemCreate} />
      </Admin>
  );
}
}

export default App;
caston1981 commented 4 years ago

@gmatht advised me: "I think that parse error is because "componentDidMount() {" is not inside the class."

I tried ESLint but that doesn't seem to do much more than my vs code does anyway.

webdeb commented 4 years ago

you are missing a curly bracket after the componentDidMount block, also you are using my example with enhanceDataProvider which is not defined in your code. so just remove it.

caston1981 commented 4 years ago

Like this?

// in App.js
import React, { Component } from 'react';
import buildHasuraProvider from 'ra-data-hasura-graphql';
import { Admin, Resource } from 'react-admin';
import { ItemList, ItemEdit, ItemCreate } from './items';
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink, createHttpLink, ApolloLink } from 'apollo-link-http';
import { ApolloProvider } from '@apollo/react-hooks';

class App extends Component {
    constructor() {
        super();
        this.state = { dataProvider: null };
    }
    /* componentDidMount() {
        buildHasuraProvider({
            clientOptions: { uri: 'http://hasura:8080/v1/graphql' },
        }).then((dataProvider) => this.setState({ dataProvider }));
    } */

/* testing this code  */

componentDidMount() {
  const httpLink = createHttpLink({ uri: 'http://hasura:8080/v1/graphql' });
  const middlewareLink = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
      'x-hasura-admin-secret': `myadminsecretkey`,
        /* Authorization: "Bearer " + yourToken, // <--- SET THE TOKEN */
      },
      },
    });
    return forward(operation);
  });

  // use with apollo-client
  const link = middlewareLink.concat(httpLink);

  buildHasuraProvider({
    clientOptions: { link: link },

    })
  );
/* end test  */

render() {
  const { dataProvider } = this.state;

  if (!dataProvider) {
      return <div>Loading</div>;
  }

  return (
      <Admin dataProvider={dataProvider}>
          <Resource name="items" list={ItemList} edit={ItemEdit} create={ItemCreate} />
      </Admin>
  );
}
}

export default App;
webdeb commented 4 years ago

the issue board is not for this kind of questions. There are platforms where people help others to fix their code and explain basics.

caston1981 commented 4 years ago

sure, can I please ask what is the simplest possible App.js file that will work with ra-data-hasura-graphql?

For example, this is from the readme.md file:

// in App.js
import React, { Component } from 'react';
import buildHasuraProvider from 'ra-data-hasura-graphql';
import { Admin, Resource } from 'react-admin';

import { PostCreate, PostEdit, PostList } from './posts';

class App extends Component {
    constructor() {
        super();
        this.state = { dataProvider: null };
    }
    componentDidMount() {
        buildHasuraProvider({
            clientOptions: { uri: 'http://localhost:4000' },
        }).then((dataProvider) => this.setState({ dataProvider }));
    }

    render() {
        const { dataProvider } = this.state;

        if (!dataProvider) {
            return <div>Loading</div>;
        }

        return (
            <Admin dataProvider={dataProvider}>
                <Resource
                    name="Post"
                    list={PostList}
                    edit={PostEdit}
                    create={PostCreate}
                />
            </Admin>
        );
    }
}

export default App;

It then says under authentication

import ApolloClient from 'apollo-boost';

const client = new ApolloClient({
  uri: 'http://localhost:8080/v1/graphql',
  headers: {
    'x-hasura-admin-secret': `myadminsecretkey`,
    // 'Authorization': `Bearer xxxx`,
  }
});

// When building your provider inside your component
// set up the client like this
buildHasuraProvider({ client })

So something like this?

// in App.js
import React, { Component } from 'react';
import buildHasuraProvider from 'ra-data-hasura-graphql';
import { Admin, Resource } from 'react-admin';
import ApolloClient from 'apollo-boost';

import { PostCreate, PostEdit, PostList } from './posts';

const client = new ApolloClient({
    uri: 'http://localhost:8080/v1/graphql',
    headers: {
      'x-hasura-admin-secret': `myadminsecretkey`,
      // 'Authorization': `Bearer xxxx`,
    }
  });

  // When building your provider inside your component
  // set up the client like this
  buildHasuraProvider({ client })

class App extends Component {
    constructor() {
        super();
        this.state = { dataProvider: null };
    }
    componentDidMount() {
        buildHasuraProvider({
            clientOptions: { uri: 'http://localhost:4000' },
        }).then((dataProvider) => this.setState({ dataProvider }));
    }

    render() {
        const { dataProvider } = this.state;

        if (!dataProvider) {
            return <div>Loading</div>;
        }

        return (
            <Admin dataProvider={dataProvider}>
                <Resource
                    name="Post"
                    list={PostList}
                    edit={PostEdit}
                    create={PostCreate}
                />
            </Admin>
        );
    }
}

export default App;

That's what I wanted to ask initially but I wanted to show people that I had at least gone to some effort to try to get it working myself. I will most likely just start again with a clean slate. I won't use any of your code snippet if you prefer webdeb. And I will still make good on my promise to "pay it forward by helping write documentation for beginners."

Additionally @gmatht is here to help if you are frustrated with my stupidity. I took a ridiculously large number of direct head-hits from bullying and fights in highschool. I have tried to get through life as best I can but it hasn't been easy.

Having said that I do want to thank you and express my gratitude for helping me as much as you have. I do believe I am getting closer and progress is being made.

caston1981 commented 4 years ago

I do see the following in the readme.md:

Options

Customize the Apollo client

You can either supply the client options by calling buildGraphQLProvider like this:

buildGraphQLProvider({
    clientOptions: { uri: 'http://localhost:4000', ...otherApolloOptions },
});

Or supply your client directly with:

buildGraphQLProvider({ client: myClient });

It might be easier for me to try the otherApolloOptions option on the first example.

That doesn't seem to be a recent update so I apologize that I missed it and ended up creating a lot of unnecessary confusion.

This particular stack-overflow topic may shed some light on this too. The original poster advised they are using the following instructions with the relevant part being:

Customize the Apollo client

You can specify the client options by calling buildGraphQLProvider like this:

import { createNetworkInterface } from 'react-apollo';

buildGraphQLProvider({
    client: {
        networkInterface: createNetworkInterface({
            uri: 'http://api.myproduct.com/graphql',
        }),
    },
});

You can pass any options supported by the ApolloClient contructor with the addition of uri which can be specified so that we create the network interface for you.

You can also supply your own ApolloClient instance directly with:

buildGraphQLProvider({ client: myClient });

I also noticed that one of the udemy courses I bought when they were on discount (although unfortunately Prisma related) has a tutorial on setting up the Apollo client. The code is here

cpursley commented 4 years ago

Take a look at these following repos:

Or any of these "Used By" repos:

https://github.com/Steams/ra-data-hasura-graphql/network/dependents?package_id=UGFja2FnZS03NzE0NzgxNjA%3D

caston1981 commented 4 years ago

Thanks cpursely. I have been looking at the auth file from ra-hasura-typescript-boilerplate.

And it is interesting how it details the following in the readme.md: services/auth: Authentication webhook services/data: Hasura GraphQL project with migrations services/events: Event triggers services/actions: Hasura actions

and how it uses Next.js + Tailwind for the back-end as my project uses Express.js and I was considering using Bulma at some in the future. The thought had crossed my mind to spin it in on a VM and see if I can just work with it. I will give it a try this weekend.

I am also particularly interested in the docker-compose file from react-admin-hasura-firebase because of the environment options and commands it supplies to the graphql-engine container.

I wasn't sure where to find the auth code in react-admin-low-code https://github.com/cpursley/react-admin-low-code/ which is ironically in your repo.

That "Used By" link will come in handy and I was looking for that. Thank you.

Perhaps one day I can help make an "installer" similar to what you get with many CMS packages. Or perhaps a init script similar to the one for ESLint. Now that would be something!

caston1981 commented 4 years ago

Would something like this work?

componentDidMount() {
    buildHasuraProvider({ clientOptions: {
       uri: 'http://hasura:8080/v1/graphql',
       headers: {'x-hasura-admin-secret': `myadminsecretkey`},  
      } })
      .then(dataProvider => this.setState({ dataProvider }));
  }
webdeb commented 4 years ago

Just do it like its shown in the readme https://github.com/Steams/ra-data-hasura-graphql#authentication

caston1981 commented 4 years ago

Just do it like its shown in the readme https://github.com/Steams/ra-data-hasura-graphql#authentication

Yes, I didn't quite understand the readme at first but I am going to try the code tomorrow to see if it works. Thanks webdeb.

edit: I haven't yet tested the code but I believe the reasons I have not been able to render have more to do with the authentication from the project I forked than anything else. I think webdeb was trying to tell me this. If I continue to have further issues I can test against a clean install of RA.

On the bright-side I have begun to learn a lot more about Hasura, Apollo and GraphQL and I would like to show my gratitude and appreciation to the developers of these fine projects as well as ra-data-hasura-graphql.

Thank you very much to everyone that has assisted me. I will close the ticket now.