using graphql-yoga and implemented jwt login

This commit is contained in:
Grzegorz Leoniec 2018-10-17 20:07:58 +02:00
parent e7a59d303a
commit 43707a4fcb
12 changed files with 695 additions and 162 deletions

4
.env
View File

@ -1,6 +1,10 @@
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=letmein
GRAPHQL_LISTEN_PORT=4000
GRAPHQL_URI=http://localhost:4000
CLIENT_URI=http://localhost:3000
JWT_SECRET=b/&&7b78BF&fv/Vd
MOCK=false

1
.gitignore vendored
View File

@ -1 +1,2 @@
node_modules/
.vscode

View File

@ -16,20 +16,27 @@
"apollo-client": "^2.3.2",
"apollo-link-http": "^1.5.4",
"apollo-server": "^2.0.4",
"bcryptjs": "^2.4.3",
"dotenv": "^6.0.0",
"graphql-custom-directives": "^0.2.13",
"graphql-middleware": "^1.7.6",
"graphql-tag": "^2.9.2",
"graphql-yoga": "^1.16.2",
"jsonwebtoken": "^8.3.0",
"lodash": "^4.17.11",
"ms": "^2.1.1",
"neo4j-driver": "^1.6.1",
"neo4j-graphql-js": "^1.0.2",
"node-fetch": "^2.1.2",
"passport": "^0.4.0",
"passport-jwt": "^4.0.0",
"slug": "^0.9.1",
"trunc-html": "^1.1.2"
},
"devDependencies": {
"faker": "^4.1.0",
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.7.0",
"nodemon": "^1.17.5"
"faker": "^4.1.0",
"nodemon": "^1.18.4"
}
}

View File

@ -1,17 +1,62 @@
// import { neo4jgraphql } from "neo4j-graphql-js"
import fs from 'fs'
import path from 'path'
import bcrypt from 'bcryptjs'
import zipObject from 'lodash/zipObject'
import generateJwt from './jwt/generateToken'
import { fixUrl } from './middleware/fixImageUrlsMiddleware'
export const typeDefs =
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, "schema.graphql"))
.toString('utf-8')
export const resolvers = {
// Query: {
// usersBySubstring: neo4jgraphql
// }
}
Query: {
isLoggedIn: (parent, args, { user }) => {
console.log(user)
return Boolean(user && user.id)
}
// usersBySubstring: neo4jgraphql
},
Mutation: {
signup: async (parent, { email, password }, { req }) => {
// if (data[email]) {
// throw new Error('Another User with same email exists.')
// }
// data[email] = {
// password: await bcrypt.hashSync(password, 10),
// }
export const mutations = {
return true
},
login: async (parent, { email, password }, { driver, req, user }) => {
// if (user && user.id) {
// throw new Error('Already logged in.')
// }
const session = driver.session()
const res = await session.run('MATCH (u:User {email: "' + email + '"}) RETURN u.id, u.slug, u.name, u.avatar, u.email, u.password, u.role LIMIT 1')
let u = res.records[0]._fields ? zipObject([
'id',
'slug',
'name',
'avatar',
'email',
'password',
'role'
], res.records[0]._fields) : null
if (u) {
if (await bcrypt.compareSync(password, u.password)) {
delete u.password
u.avatar = fixUrl(u.avatar)
return Object.assign(u, {
token: generateJwt(u)
})
}
throw new Error('Incorrect password.')
}
throw new Error('No Such User exists.')
}
}
}

View File

@ -1,21 +1,21 @@
// import { GraphQLServer } from 'graphql-yoga'
import { applyMiddleware } from 'graphql-middleware'
import { ApolloServer, makeExecutableSchema } from 'apollo-server'
import { GraphQLServer } from 'graphql-yoga'
import { makeExecutableSchema } from 'apollo-server'
import { augmentSchema } from 'neo4j-graphql-js'
import { typeDefs, resolvers } from './graphql-schema'
import { v1 as neo4j } from 'neo4j-driver'
import passwordMiddleware from './middleware/passwordMiddleware'
import softDeleteMiddleware from './middleware/softDeleteMiddleware'
import sluggifyMiddleware from './middleware/sluggifyMiddleware'
import fixImageUrlsMiddleware from './middleware/fixImageUrlsMiddleware'
import excerptMiddleware from './middleware/excerptMiddleware'
import dotenv from 'dotenv'
import {
GraphQLLowerCaseDirective,
GraphQLTrimDirective,
GraphQLDefaultToDirective
} from 'graphql-custom-directives';
import faker from 'faker'
import mocks from './mocks'
import middleware from './middleware'
import passport from 'passport'
import jwtStrategy from './jwt/strategy'
import jwt from 'jsonwebtoken'
// import {
// GraphQLLowerCaseDirective,
// GraphQLTrimDirective,
// GraphQLDefaultToDirective
// } from 'graphql-custom-directives';
dotenv.config()
@ -25,15 +25,15 @@ const schema = makeExecutableSchema({
})
// augmentSchema will add auto generated mutations based on types in schema
const augmentedSchema = augmentSchema(schema)
// const augmentedSchema = augmentSchema(schema)
// add custom directives
const directives = [
GraphQLLowerCaseDirective,
GraphQLTrimDirective,
GraphQLDefaultToDirective
]
augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
// const directives = [
// GraphQLLowerCaseDirective,
// GraphQLTrimDirective,
// GraphQLDefaultToDirective
// ]
// augmentedSchema._directives.push.apply(augmentedSchema._directives, directives)
const driver = neo4j.driver(
process.env.NEO4J_URI || 'bolt://localhost:7687',
@ -46,25 +46,51 @@ const driver = neo4j.driver(
const MOCK = (process.env.MOCK === 'true')
console.log('MOCK:', MOCK)
const server = new ApolloServer({
context: {
driver
const server = new GraphQLServer({
context: async (req) => {
const payload = {
driver,
user: null,
req: req.request
}
try {
const token = payload.req.headers.authorization.replace('Bearer ', '')
payload.user = await jwt.verify(token, process.env.JWT_SECRET)
} catch (err) {}
return payload
},
schema: augmentSchema(schema),
tracing: true,
schema: applyMiddleware(augmentedSchema, passwordMiddleware, sluggifyMiddleware, excerptMiddleware, fixImageUrlsMiddleware, softDeleteMiddleware),
mocks: MOCK ? {
User: () => ({
name: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
email: () => `${faker.internet.email()}`
}),
Post: () => ({
title: () => faker.lorem.lines(1),
slug: () => faker.lorem.slug(3),
content: () => faker.lorem.paragraphs(5),
contentExcerpt: () => faker.lorem.paragraphs(1)
})
} : false
middlewares: middleware,
mocks: MOCK ? mocks : false
})
server.listen().then(({ url }) => {
console.log(`Server ready at ${url} 🚀`);
passport.use('jwt', jwtStrategy())
server.express.use(passport.initialize())
server.express.post('/graphql', passport.authenticate(['jwt'], { session: false }))
// session middleware
// server.express.use(session({
// name: 'qid',
// secret: process.env.JWT_SECRET,
// resave: true,
// saveUninitialized: true,
// cookie: {
// secure: process.env.NODE_ENV === 'production',
// maxAge: ms('1d')
// }
// }))
const serverConfig = {
port: 4000
// cors: {
// credentials: true,
// origin: [process.env.CLIENT_URI] // your frontend url.
// }
}
server.start(serverConfig, options => {
console.log(`Server ready at ${process.env.GRAPHQL_URI} 🚀`);
})

18
src/jwt/generateToken.js Normal file
View File

@ -0,0 +1,18 @@
import jwt from 'jsonwebtoken'
import ms from 'ms'
// Generate an Access Token for the given User ID
export default function generateJwt(user) {
console.log('generateJwt', user)
const token = jwt.sign(user, process.env.JWT_SECRET, {
expiresIn: ms('1d'),
issuer: process.env.GRAPHQL_URI,
audience: process.env.CLIENT_URI,
subject: user.id.toString()
})
jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
console.log('token verification:', err, data)
})
return token
}

30
src/jwt/strategy.js Normal file
View File

@ -0,0 +1,30 @@
import { Strategy } from 'passport-jwt'
const cookieExtractor = (req) => {
var token = null
if (req && req.cookies) {
token = req.cookies['jwt']
}
return token
}
export default () => {
const options = {
jwtFromRequest: cookieExtractor,
secretOrKey: process.env.JWT_SECRET,
issuer: process.env.GRAPHQL_URI,
audience: process.env.CLIENT_URI
}
return new Strategy(options,
(JWTPayload, next) => {
console.log('JWT Payload Received:', JWTPayload)
// usually this would be a database call:
// var user = users[_.findIndex(users, {id: JWTPayload.id})]
if (true) {
next(null, {})
} else {
next(null, false)
}
})
}

View File

@ -1,21 +1,24 @@
const replaceURL = (url) => {
export const fixUrl = (url) => {
return url.replace('https://api-alpha.human-connection.org/uploads', 'http://localhost:3000/uploads')
}
const fixImageURLs = (result, resolve, root, args, context, info) => {
if (result && typeof result === 'string' && result.indexOf('https://api-alpha.human-connection.org/uploads') === 0) {
result = replaceURL(result)
result = fixUrl(result)
} else if (result && typeof result === 'object') {
Object.keys(result).forEach(key => {
result[key] = fixImageURLs(result[key])
})
}
return result
}
export default {
Mutation: async (resolve, root, args, context, info) => {
const result = await resolve(root, args, context, info)
return fixImageURLs(result, resolve, root, args, context, info)
},
Query: async (resolve, root, args, context, info) => {
let result = await resolve(root, args, context, info)

13
src/middleware/index.js Normal file
View File

@ -0,0 +1,13 @@
import passwordMiddleware from './passwordMiddleware'
import softDeleteMiddleware from './softDeleteMiddleware'
import sluggifyMiddleware from './sluggifyMiddleware'
import fixImageUrlsMiddleware from './fixImageUrlsMiddleware'
import excerptMiddleware from './excerptMiddleware'
export default [
passwordMiddleware,
sluggifyMiddleware,
excerptMiddleware,
fixImageUrlsMiddleware,
softDeleteMiddleware
]

15
src/mocks/index.js Normal file
View File

@ -0,0 +1,15 @@
import faker from 'faker'
export default {
User: () => ({
name: () => `${faker.name.firstName()} ${faker.name.lastName()}`,
email: () => `${faker.internet.email()}`
}),
Post: () => ({
title: () => faker.lorem.lines(1),
slug: () => faker.lorem.slug(3),
content: () => faker.lorem.paragraphs(5),
contentExcerpt: () => faker.lorem.paragraphs(1)
})
}

View File

@ -1,3 +1,20 @@
type Query {
isLoggedIn: Boolean!
}
type Mutation {
login(email: String!, password: String!): LoggedInUser
signup(email: String!, password: String!): Boolean!
}
type LoggedInUser {
id: ID!
slug: String!
name: String!
avatar:String!
email: String!
role: String!,
token: String!
}
enum VisibilityEnum {
Public
Friends

580
yarn.lock

File diff suppressed because it is too large Load Diff