Merge pull request #88 from Human-Connection/49_fix_no_user_with_that_email

Login: Friendly error message if email is incorrect
This commit is contained in:
Robert Schäfer 2019-01-04 00:22:33 +01:00 committed by GitHub
commit 407e5bfdeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1740 additions and 179 deletions

View File

@ -4,6 +4,7 @@ module.exports = {
"env": {
"es6": true,
"node": true,
"jest/globals": true
},
"rules": {
"indent": [
@ -14,5 +15,6 @@ module.exports = {
"error",
"single"
]
}
},
"plugins": ["jest"]
};

View File

@ -131,6 +131,9 @@ npm run db:reset
```
## Run Tests
**Beware**: We have no multiple database setup at the moment. We clean the database after each test, running the tests will wipe out all your data!
```bash
yarn run test
# -or-

View File

@ -11,9 +11,10 @@
"start": "node dist/",
"dev": "nodemon --exec babel-node src/index.js",
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
"mocha": "mocha --require @babel/register src/**/*.test.js",
"lint": "eslint src --config .eslintrc.js",
"test": "nyc --reporter=text-lcov mocha --require @babel/register src/**/*.test.js",
"test": "nyc --reporter=text-lcov yarn run test:jest",
"test:jest": "$npm_package_config_no_auth run-p --race start test:cmd:jest",
"test:cmd:jest": "jest --forceExit",
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
"db:script:reset": "wait-on tcp:4001 && babel-node src/seed/reset-db.js",
@ -32,6 +33,7 @@
"cross-env": "~5.2.0",
"date-fns": "^2.0.0-alpha.26",
"dotenv": "~6.2.0",
"faker": "~4.1.0",
"graphql": "~0.13.0",
"graphql-custom-directives": "~0.2.14",
"graphql-iso-date": "~3.6.1",
@ -46,32 +48,36 @@
"neo4j-driver": "~1.7.2",
"neo4j-graphql-js": "~2.1.1",
"node-fetch": "~2.3.0",
"npm-run-all": "~4.1.5",
"passport": "~0.4.0",
"passport-jwt": "~4.0.0",
"sanitize-html": "~1.20.0",
"faker": "~4.1.0",
"slug": "~0.9.3",
"trunc-html": "~1.1.2",
"npm-run-all": "~4.1.5",
"wait-on": "~3.2.0"
},
"devDependencies": {
"@babel/cli": "~7.2.3",
"@babel/core": "~7.2.0",
"@babel/core": "^7.2.2",
"@babel/node": "~7.2.2",
"@babel/preset-env": "~7.2.3",
"@babel/register": "~7.0.0",
"apollo-server-testing": "~2.2.6",
"babel-core": "^7.0.0-0",
"babel-eslint": "~10.0.1",
"babel-jest": "^23.6.0",
"chai": "~4.2.0",
"eslint": "~5.11.1",
"eslint-config-standard": "~12.0.0",
"eslint-plugin-import": "~2.14.0",
"eslint-plugin-jest": "^22.1.2",
"eslint-plugin-node": "~8.0.0",
"eslint-plugin-promise": "~4.0.1",
"eslint-plugin-standard": "~4.0.0",
"mocha": "~5.2.0",
"graphql-request": "^1.8.2",
"jest": "^23.6.0",
"nodemon": "~1.18.9",
"nyc": "^13.1.0"
"nyc": "^13.1.0",
"supertest": "^3.3.0"
}
}

View File

@ -2,9 +2,9 @@
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'
import { AuthenticationError } from 'apollo-server'
export const typeDefs =
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql'))
@ -95,32 +95,24 @@ export const resolvers = {
// 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)
return session.run(
'MATCH (user:User {email: "' + email + '"}) ' +
'RETURN user {.id, .slug, .name, .avatar, .email, .password, .role} as user LIMIT 1')
.then(async (result) => {
session.close()
const [currentUser] = await result.records.map(function (record) {
console.log(record.get('user'))
return record.get('user')
})
}
session.close()
throw new Error('Incorrect password.')
}
session.close()
throw new Error('No Such User exists.')
if (currentUser && await bcrypt.compareSync(password, currentUser.password)) {
delete currentUser.password
currentUser.avatar = fixUrl(currentUser.avatar)
return Object.assign(currentUser, {
token: generateJwt(currentUser)
})
} else throw new AuthenticationError('Incorrect email address or password.')
})
}
}
}

View File

@ -0,0 +1,78 @@
import { request } from 'graphql-request'
import createServer from './server'
import mocks from './mocks'
import { create, cleanDatabase } from './seed/factories'
import jwt from 'jsonwebtoken'
let getHost
let app
let port
beforeEach(async () => {
const server = createServer({ mocks })
app = await server.start({ port: 0 })
port = app.address().port
getHost = () => `http://127.0.0.1:${port}`
})
afterEach(async () => {
await app.close()
})
describe.only('login', () => {
const mutation = (params) => {
const { email, password } = params
return `
mutation {
login(email:"${email}", password:"${password}"){
token
}
}`
}
describe('given an existing user', () => {
beforeEach(async () => {
await create('user', {
email: 'test@example.org',
password: '1234'
})
})
afterEach(async () => {
await cleanDatabase()
})
describe('asking for a `token`', () => {
describe('with valid email/password combination', () => {
it('responds with a JWT token', async () => {
const data = await request(getHost(), mutation({ email: 'test@example.org', password: '1234' }))
const { token } = data.login
jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
expect(data.email).toEqual('test@example.org')
expect(err).toBeNull()
})
})
})
describe('with a valid email but incorrect password', () => {
it('responds with "Incorrect email address or password."', async () => {
try {
await request(getHost(), mutation({ email: 'test@example.org', password: 'wrong' }))
} catch (error) {
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
}
})
})
describe('with a non-existing email', () => {
it('responds with "Incorrect email address or password."', async () => {
try {
await request(getHost(), mutation({ email: 'non-existent@example.org', password: 'wrong' }))
} catch (error) {
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
}
})
})
})
})
})

View File

@ -1,4 +1,4 @@
import server from './server'
import createServer from './server'
const serverConfig = {
port: process.env.GRAPHQL_PORT || 4000
@ -8,6 +8,7 @@ const serverConfig = {
// }
}
const server = createServer()
server.start(serverConfig, options => {
/* eslint-disable-next-line no-console */
console.log(`Server ready at ${process.env.GRAPHQL_URI} 🚀`)

View File

@ -1,16 +1,14 @@
import { describe, it } from 'mocha'
describe('query', () => {
describe('statistics', () => {
describe('authenticated user', () => {
describe('read', () => {
it('is forbidden')
xit('is forbidden', () => {})
})
})
describe('admin', () => {
describe('read', () => {
it('is permitted')
xit('is permitted', () => {})
})
})
})

View File

@ -0,0 +1,44 @@
import ApolloClient from 'apollo-client'
import gql from 'graphql-tag'
import dotenv from 'dotenv'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import neo4j from '../../bootstrap/neo4j'
import { query } from '../../graphql-schema'
import fetch from 'node-fetch'
dotenv.config()
if (process.env.NODE_ENV === 'production') {
throw new Error('YOU CAN`T RUN FACTORIES IN PRODUCTION MODE')
}
const client = new ApolloClient({
link: new HttpLink({ uri: process.env.GRAPHQL_URI, fetch }),
cache: new InMemoryCache()
})
const driver = neo4j().getDriver()
const session = driver.session()
const builders = {
'user': require('./users.js').default
}
const buildMutation = (model, parameters) => {
return builders[model](parameters)
}
const create = async (model, parameters) => {
await client.mutate({ mutation: gql(buildMutation(model, parameters)) })
}
const cleanDatabase = async () => {
await query('MATCH (n) DETACH DELETE n', session)
}
export {
create,
buildMutation,
cleanDatabase
}

View File

@ -0,0 +1,30 @@
import faker from 'faker'
export default function (params) {
const {
name = faker.name.findName(),
email = faker.internet.email(),
password = '1234',
avatar = faker.internet.avatar()
} = params
return `
mutation {
u1: CreateUser(
id: "u1",
name: "${name}",
password: "${password}",
email: "${email}",
avatar: "${avatar}",
role: admin,
disabled: false,
deleted: false) {
id
name
email
avatar
role
}
}
`
}

View File

@ -22,10 +22,6 @@ let schema = makeExecutableSchema({
const driver = neo4j().getDriver()
const MOCK = (process.env.MOCK === 'true')
/* eslint-disable-next-line no-console */
console.log('MOCK:', MOCK)
schema = augmentSchema(schema, {
query: {
exclude: ['Statistics', 'LoggedInUser']
@ -36,31 +32,35 @@ schema = augmentSchema(schema, {
})
schema = applyScalars(applyDirectives(schema))
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) {
// nothing
}
const createServer = (options) => {
const defaults = {
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) {
// nothing
}
return payload
},
schema: schema,
tracing: true,
middlewares: middleware(schema),
mocks: MOCK ? mocks : false
})
return payload
},
schema: schema,
tracing: true,
middlewares: middleware(schema),
mocks: (process.env.MOCK === 'true') ? mocks : false
}
const server = new GraphQLServer(Object.assign({}, defaults, options))
passport.use('jwt', jwtStrategy())
server.express.use(passport.initialize())
passport.use('jwt', jwtStrategy())
server.express.use(passport.initialize())
server.express.post('/graphql', passport.authenticate(['jwt'], { session: false }))
server.express.post('/graphql', passport.authenticate(['jwt'], { session: false }))
return server
}
export default server
export default createServer

1633
yarn.lock

File diff suppressed because it is too large Load Diff