mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
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:
commit
407e5bfdeb
@ -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"]
|
||||
};
|
||||
|
||||
@ -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-
|
||||
|
||||
20
package.json
20
package.json
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
78
src/graphql-schema.test.js
Normal file
78
src/graphql-schema.test.js
Normal 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.')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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} 🚀`)
|
||||
|
||||
@ -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', () => {})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
44
src/seed/factories/index.js
Normal file
44
src/seed/factories/index.js
Normal 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
|
||||
}
|
||||
30
src/seed/factories/users.js
Normal file
30
src/seed/factories/users.js
Normal 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
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user