Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

Retrieving user info #13

Open
dabit1 opened this issue Nov 10, 2019 · 6 comments
Open

Retrieving user info #13

dabit1 opened this issue Nov 10, 2019 · 6 comments

Comments

@dabit1
Copy link

dabit1 commented Nov 10, 2019

Hi everyone,

How can I get the user info once I'm authenticated? I expected that it would be inside of context but seems that it is not in anyplace.

I've checked the source code and I see that decoded data is not attached to any place. Is there another way to get it?

Thanks

@dabit1
Copy link
Author

dabit1 commented Nov 10, 2019

I created a pull request to solve it: #14

@thelovesmith
Copy link

hey @dabit1 , do you think you coul dhelp me out with getting authenticated. I don't know how to get it working on the server. I've been piecing together what I could find online but I can't find a complete working solution using auth in GRANDstack. here's my Repo

@dabit1
Copy link
Author

dabit1 commented Dec 5, 2019

@thelovesmith you can take a look at the examples you'll find in this repo. What problem do you have?

@thelovesmith
Copy link

thelovesmith commented Dec 5, 2019

I can't seem to figure out how to send a query with an authorization token. I've set up my graphql server to look for auth token so I can't query certain data without it. However, I do not know how to send an auth token with a request. I'm trying it out in Graphql playground. Im essentially just trying to set up a signup/signin for the grand stack starter where users have to sign up or login before accessing data.
here is my index.js for the api:

import { typeDefs } from "./graphql-schema";
import { ApolloServer } from "apollo-server-express";
import express from "express";
import { v1 as neo4j } from "neo4j-driver";
import { makeAugmentedSchema, neo4jgraphql } from "neo4j-graphql-js";
import dotenv from "dotenv";
import {
  IsAuthenticatedDirective,
  HasRoleDirective
} from "graphql-auth-directives";
const bcrypt = require("bcryptjs");
const jsonwebtoken = require("jsonwebtoken");
// set environment variables from ../.env
dotenv.config();

//settign app as Express middleware
const app = express();
/*
 * Create an executable GraphQL schema object from GraphQL type definitions
 * including autogenerated queries and mutations.
 * Optionally a config object can be included to specify which types to include
 * in generated queries and/or mutations. Read more in the docs:
 * https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
 */
// typedefs are coming from the graphql-schema & schema.grapql
// AugmentSchema auto generates Mutations and Queries
const schema = makeAugmentedSchema({
  typeDefs,
  resolvers,
  schemaDirectives: {
    isAuthenticated: IsAuthenticatedDirective,
    hasRole: HasRoleDirective
  }
});

/*
 * Create a Neo4j driver instance to connect to the database
 * using credentials specified as environment variables
 * with fallback to defaults
 */
const driver = neo4j.driver(
  process.env.NEO4J_URI || "bolt://localhost:7687",
  neo4j.auth.basic(
    process.env.NEO4J_USER || "neo4j",
    process.env.NEO4J_PASSWORD || "neo4j"
  )
);

/*
 * Create a new ApolloServer instance, serving the GraphQL schema
 * created using makeAugmentedSchema above and injecting the Neo4j driver
 * instance into the context object so it is available in the
 * generated resolvers to connect to the database.
 */
//! assuming our middleware has added a user object
//! to the requet if it si authenrticated
//! inject the reqiest object into the context
//! so it will be available in the resolver
const server = new ApolloServer({
  schema: schema,
  context: ({ req }) => {
    return {
      driver,
      req
    };
  },
  introspection: true,
  playground: true
});
//! then in the resolver check for the exisctence of the user object
const resolvers = {
  Query: {
    Business(object, params, ctx, resolveInfo) {
      if (
        !ctx ||
        !ctx.headers ||
        (!ctx.headers.authorization && !ctx.headers.Authorization)
      ) {
        throw new Error("No authorization token");
      } else {
        // request is authenticated, fetch the data
        return neo4jgraphql(object, params, ctx, resolveInfo);
      }
    }
  },
  // Mutation: {
  //   // Handle user signup
  //   async signup({ username, email, password }) {
  //     const user = await User.create({
  //       username,
  //       email,
  //       password: await bcrypt.hash(password, 10)
  //     });

  //     // return json web token
  //     return jsonwebtoken.sign(
  //       { id: user.id, email: user.email },
  //       process.env.JWT_SECRET,
  //       { expiresIn: "1y" }
  //     );
  //   },
  //   // Handles user login
  //   async login({ email, password }) {
  //     const user = await User.findOne({ where: { email } });

  //     if (!user) {
  //       throw new Error("No user with that email");
  //     }

  //     const valid = await bcrypt.compare(password, user.password);

  //     if (!valid) {
  //       throw new Error("Incorrect password");
  //     }

  //     // return json web token
  //     return jsonwebtoken.sign(
  //       { id: user.id, email: user.email },
  //       process.env.JWT_SECRET,
  //       { expiresIn: "1d" }
  //     );
  //   }
  // }
};

// Specify port and path for GraphQL endpoint
const port = process.env.GRAPHQL_LISTEN_PORT || 4001;
const path = "/graphql";

/*
 * Optionally, apply Express middleware for authentication, etc
 * This also allows us to specify a path for the GraphQL endpoint
 */
server.applyMiddleware({ app, path });

app.listen({ port, path }, () => {
  console.log(`GraphQL server ready at http://localhost:${port}${path}`);
});

Here is the schema:

# directive @hasScope(scopes: [String]) on OBJECT | FIELD_DEFINITION
directive @hasRole(roles: [Role]) on OBJECT | FIELD_DEFINITION
directive @isAuthenticated on OBJECT | FIELD_DEFINITION

type User
  @isAuthenticated { #using graphql authentication directive,, User data should only be available when the GraphQL request is authenticated.
  id: ID!
  name: String
  email: String!
  password: String!
  friends: [User] @relation(name: "FRIENDS", direction: "BOTH")
  reviews: [Review] @relation(name: "WROTE", direction: "OUT")
  avgStars: Float
    @cypher(
      #Cypher extends graphQL by accesses computations using Cypher query language,  Schema Directive to find all reviews that this user wrote and return the average number of stars they left on reviews
      statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN toFloat(avg(r.stars))"
    )
  numReviews: Int
    @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
  recommendations(first: Int = 3): [Business]
    @cypher(
      statement: "MATCH (this)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Business)<-[:REVIEWS]-(:Review)<-[:WROTE]-(:User)-[:WROTE]->(:Review)-[:REVIEWS]->(rec:Business) WHERE NOT EXISTS( (this)-[:WROTE]->(:Review)-[:REVIEWS]->(rec) )WITH rec, COUNT(*) AS num ORDER BY num DESC LIMIT $first RETURN rec"
    )
}

type Business
  @hasRole(roles: [admin]) { #only users with the role admin can wuery business data
  id: ID!
  name: String
  address: String
  city: String
  state: String
  avgStars: Float
    @cypher(
      statement: "MATCH (this)<-[:REVIEWS]-(r:Review) RETURN coalesce(avg(r.stars),0.0)"
    )
  reviews: [Review] @relation(name: "REVIEWS", direction: "IN")
  categories: [Category] @relation(name: "IN_CATEGORY", direction: "OUT")
}

type Review {
  id: ID!
  stars: Int
  text: String
  date: Date
  business: Business @relation(name: "REVIEWS", direction: "OUT")
  user: User @relation(name: "WROTE", direction: "IN")
}

type Category {
  name: ID!
  businesses: [Business] @relation(name: "IN_CATEGORY", direction: "IN")
}

type Query {
  usersBySubstring(substring: String): [User]
    @cypher(
      statement: "MATCH (u:User) WHERE u.name CONTAINS $substring RETURN u"
    )

  starsByCategory(category: String): [StarCount]
    @cypher(
      statement: """
      MATCH (c:Category)<-[:IN_CATEGORY]-(:Business)<-[:REVIEWS]-(r:Review)
      WHERE toLower(c.name) CONTAINS toLower($category)
      WITH toString(r.stars) AS stars, COUNT(*) AS num
      RETURN {star: stars, count: num}
      """
    )
}
type Mutation {
  signup(username: String!, email: String!, password: String!): String
  login(email: String!, password: String!): String
}

type StarCount {
  star: Int
  count: Int
}

# First we add an enum with our different available roles to the GraphQL schema below:
# Then we use one or more of these roles in the directive for the Business typeDef above
enum Role {
  admin
  reader
  user
}

# Scopes are especially useful when we need to declare authorization rules on mutations. And in fact a role could map to a group of scopes. Using a @hasScope directive to declare authorization rules for a mutation would look something like this:

# type Mutation {
#   CreateBusiness(id: ID!, name: String): Business
#     @hasScope(scope: ["user:write"])
# }

# In order to create a Business, the user making the request must have the user:write scope.

and I've also attached a screenshot
Screen Shot 2019-12-05 at 10 15 02 AM
of the playground

@thelovesmith
Copy link

I commented out some resolvers I tried to set up to handle sign up and login but I have no idea how to do that with Neo4j and graphql. But the Business Query resolver is working because i'm getting the "{no auth token" message. I just don't know how to authorize the request.

@bionicles
Copy link

Don’t forget to pass the token from the sender https://www.apollographql.com/docs/react/v3.0-beta/networking/network-layer/#middleware

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants