mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Updates to get in line with master
This commit is contained in:
parent
5c91962808
commit
45cf16d07d
@ -8,3 +8,5 @@ MOCK=false
|
||||
|
||||
JWT_SECRET="b/&&7b78BF&fv/Vd"
|
||||
MAPBOX_TOKEN="pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ"
|
||||
|
||||
PRIVATE_KEY_PASSPHRASE="a7dsf78sadg87ad87sfagsadg78"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -8,3 +8,6 @@ coverage.lcov
|
||||
.nyc_output/
|
||||
public/uploads/*
|
||||
!.gitkeep
|
||||
|
||||
# Apple macOS folder attribute file
|
||||
.DS_Store
|
||||
17
README.md
17
README.md
@ -1,5 +1,12 @@
|
||||
# Human-Connection - NITRO Backend
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Backend)
|
||||
<p align="center">
|
||||
<a href="https://human-connection.org"><img align="center" src="humanconnection.png" height="200" alt="Human Connection" /></a>
|
||||
</p>
|
||||
|
||||
# NITRO Backend
|
||||
[](https://travis-ci.com/Human-Connection/Nitro-Backend)
|
||||
[](https://github.com/Human-Connection/Nitro-Backend/blob/backend/LICENSE.md)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_shield)
|
||||
[](https://discord.gg/6ub73U3)
|
||||
|
||||
> This Prototype tries to resolve the biggest hurdle of connecting
|
||||
> our services together. This is not possible in a sane way using
|
||||
@ -158,9 +165,13 @@ npm run test:cucumber
|
||||
|
||||
- [x] add jwt authentication
|
||||
- [ ] get directives working correctly (@toLower, @auth, @role, etc.)
|
||||
- [ ] check if search is working
|
||||
- [x] check if search is working
|
||||
- [x] check if sorting is working
|
||||
- [x] check if pagination is working
|
||||
- [ ] check if upload is working (using graphql-yoga?)
|
||||
- [x] evaluate middleware
|
||||
- [ ] ignore Posts and Comments by blacklisted Users
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FHuman-Connection%2FNitro-Backend?ref=badge_large)
|
||||
|
||||
@ -8,11 +8,7 @@ services:
|
||||
- 7687:7687
|
||||
- 7474:7474
|
||||
backend:
|
||||
ports:
|
||||
- 4001:4001
|
||||
- 4123:4123
|
||||
image: humanconnection/nitro-backend:builder
|
||||
build:
|
||||
context: .
|
||||
target: builder
|
||||
command: yarn run test:cypress
|
||||
|
||||
50
package.json
50
package.json
@ -9,9 +9,9 @@
|
||||
"scripts": {
|
||||
"build": "babel src/ -d dist/ --copy-files",
|
||||
"start": "node dist/",
|
||||
"dev": "nodemon --exec babel-node src/",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
|
||||
"lint": "eslint src --config .eslintrc.js --fix",
|
||||
"dev": "nodemon --exec babel-node src/ -e js,graphql",
|
||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js -e js,graphql",
|
||||
"lint": "eslint src --config .eslintrc.js",
|
||||
"test": "nyc --reporter=text-lcov yarn test:jest",
|
||||
"test:cypress": "run-p --race test:before:*",
|
||||
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null",
|
||||
@ -36,19 +36,19 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"activitystrea.ms": "^2.1.3",
|
||||
"apollo-cache-inmemory": "~1.4.3",
|
||||
"apollo-client": "~2.4.13",
|
||||
"apollo-link-context": "^1.0.14",
|
||||
"apollo-link-http": "~1.5.11",
|
||||
"apollo-server": "~2.4.2",
|
||||
"activitystrea.ms": "~2.1.3",
|
||||
"apollo-cache-inmemory": "~1.5.1",
|
||||
"apollo-client": "~2.5.1",
|
||||
"apollo-link-context": "~1.0.14",
|
||||
"apollo-link-http": "~1.5.12",
|
||||
"apollo-server": "~2.4.8",
|
||||
"bcryptjs": "~2.4.3",
|
||||
"cheerio": "~1.0.0-rc.2",
|
||||
"cors": "^2.8.5",
|
||||
"cors": "~2.8.5",
|
||||
"cross-env": "~5.2.0",
|
||||
"date-fns": "2.0.0-alpha.26",
|
||||
"dotenv": "~6.2.0",
|
||||
"express": "^4.16.4",
|
||||
"express": "~4.16.4",
|
||||
"faker": "~4.1.0",
|
||||
"graphql": "~14.1.1",
|
||||
"graphql-custom-directives": "~0.2.14",
|
||||
@ -57,39 +57,37 @@
|
||||
"graphql-shield": "~5.3.0",
|
||||
"graphql-tag": "~2.10.1",
|
||||
"graphql-yoga": "~1.17.4",
|
||||
"helmet": "^3.15.1",
|
||||
"helmet": "~3.15.1",
|
||||
"jsonwebtoken": "~8.5.0",
|
||||
"linkifyjs": "~2.1.8",
|
||||
"lodash": "~4.17.11",
|
||||
"ms": "~2.1.1",
|
||||
"neo4j-driver": "~1.7.2",
|
||||
"neo4j-graphql-js": "~2.3.1",
|
||||
"neo4j-driver": "~1.7.3",
|
||||
"neo4j-graphql-js": "~2.4.1",
|
||||
"node-fetch": "~2.3.0",
|
||||
"npm-run-all": "~4.1.5",
|
||||
"passport": "~0.4.0",
|
||||
"passport-jwt": "~4.0.0",
|
||||
"request": "^2.88.0",
|
||||
"request": "~2.88.0",
|
||||
"sanitize-html": "~1.20.0",
|
||||
"slug": "~1.0.0",
|
||||
"trunc-html": "~1.1.2",
|
||||
"uuid": "^3.3.2",
|
||||
"uuid": "~3.3.2",
|
||||
"wait-on": "~3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.2.3",
|
||||
"@babel/core": "~7.3.3",
|
||||
"@babel/core": "~7.3.4",
|
||||
"@babel/node": "~7.2.2",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.2.0",
|
||||
"@babel/preset-env": "~7.3.1",
|
||||
"@babel/preset-env": "~7.3.4",
|
||||
"@babel/register": "~7.0.0",
|
||||
"apollo-server-testing": "~2.4.2",
|
||||
"apollo-server-testing": "~2.4.8",
|
||||
"babel-core": "~7.0.0-0",
|
||||
"babel-eslint": "~10.0.1",
|
||||
"babel-jest": "~24.1.0",
|
||||
"babel-jest": "~24.3.1",
|
||||
"chai": "~4.2.0",
|
||||
"cucumber": "^5.1.0",
|
||||
"debug": "^4.1.1",
|
||||
"eslint": "~5.13.0",
|
||||
"cucumber": "~5.1.0",
|
||||
"debug": "~4.1.1",
|
||||
"eslint": "~5.15.1",
|
||||
"eslint-config-standard": "~12.0.0",
|
||||
"eslint-plugin-import": "~2.16.0",
|
||||
"eslint-plugin-jest": "~22.3.0",
|
||||
@ -97,7 +95,7 @@
|
||||
"eslint-plugin-promise": "~4.0.1",
|
||||
"eslint-plugin-standard": "~4.0.0",
|
||||
"graphql-request": "~1.8.2",
|
||||
"jest": "~24.1.0",
|
||||
"jest": "~24.3.1",
|
||||
"nodemon": "~1.18.10",
|
||||
"nyc": "~13.3.0",
|
||||
"supertest": "~3.4.2"
|
||||
|
||||
@ -21,7 +21,7 @@ import fetch from 'node-fetch'
|
||||
import { ApolloClient } from 'apollo-client'
|
||||
import dotenv from 'dotenv'
|
||||
import uuid from 'uuid'
|
||||
import generateJwtToken from '../jwt/generateToken'
|
||||
import encode from '../jwt/encode'
|
||||
import { resolve } from 'path'
|
||||
import trunc from 'trunc-html'
|
||||
const debug = require('debug')('ea:nitro-datasource')
|
||||
@ -41,7 +41,7 @@ export default class NitroDataSource {
|
||||
const cache = new InMemoryCache()
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
// generate the authentication token (maybe from env? Which user?)
|
||||
const token = generateJwtToken({ name: 'ActivityPub', id: uuid() })
|
||||
const token = encode({ name: 'ActivityPub', id: uuid() })
|
||||
// return the headers to the context so httpLink can read them
|
||||
return {
|
||||
headers: {
|
||||
|
||||
@ -1,219 +1,25 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import generateJwt from './jwt/generateToken'
|
||||
import uuid from 'uuid/v4'
|
||||
import { fixUrl } from './middleware/fixImageUrlsMiddleware'
|
||||
import { AuthenticationError } from 'apollo-server'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import { activityPub } from './activitypub/ActivityPub'
|
||||
import as from 'activitystrea.ms'
|
||||
/*
|
||||
import as from 'activitystrea.ms'
|
||||
import request from 'request'
|
||||
*/
|
||||
|
||||
const debug = require('debug')('backend:schema')
|
||||
import userManagement from './resolvers/user_management.js'
|
||||
import statistics from './resolvers/statistics.js'
|
||||
import reports from './resolvers/reports.js'
|
||||
import posts from './resolvers/posts.js'
|
||||
import moderation from './resolvers/moderation.js'
|
||||
|
||||
export const typeDefs =
|
||||
fs.readFileSync(process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql'))
|
||||
.toString('utf-8')
|
||||
|
||||
export const query = (cypher, session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = []
|
||||
session
|
||||
.run(cypher)
|
||||
.subscribe({
|
||||
onNext: function (record) {
|
||||
let item = {}
|
||||
record.keys.forEach(key => {
|
||||
item[key] = record.get(key)
|
||||
})
|
||||
data.push(item)
|
||||
},
|
||||
onCompleted: function () {
|
||||
session.close()
|
||||
resolve(data)
|
||||
},
|
||||
onError: function (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
const queryOne = (cypher, session) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
query(cypher, session)
|
||||
.then(res => {
|
||||
resolve(res.length ? res.pop() : {})
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const resolvers = {
|
||||
Query: {
|
||||
isLoggedIn: (parent, args, { driver, user }) => {
|
||||
return Boolean(user && user.id)
|
||||
},
|
||||
statistics: async (parent, args, { driver, user }) => {
|
||||
return new Promise(async (resolve) => {
|
||||
const session = driver.session()
|
||||
const queries = {
|
||||
countUsers: 'MATCH (r:User) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countUsers',
|
||||
countPosts: 'MATCH (r:Post) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countPosts',
|
||||
countComments: 'MATCH (r:Comment) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countComments',
|
||||
countNotifications: 'MATCH (r:Notification) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countNotifications',
|
||||
countOrganizations: 'MATCH (r:Organization) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countOrganizations',
|
||||
countProjects: 'MATCH (r:Project) WHERE r.deleted <> true OR NOT exists(r.deleted) RETURN COUNT(r) AS countProjects',
|
||||
countInvites: 'MATCH (r:Invite) WHERE r.wasUsed <> true OR NOT exists(r.wasUsed) RETURN COUNT(r) AS countInvites',
|
||||
countFollows: 'MATCH (:User)-[r:FOLLOWS]->(:User) RETURN COUNT(r) AS countFollows',
|
||||
countShouts: 'MATCH (:User)-[r:SHOUTED]->(:Post) RETURN COUNT(r) AS countShouts'
|
||||
}
|
||||
let data = {
|
||||
countUsers: (await queryOne(queries.countUsers, session)).countUsers.low,
|
||||
countPosts: (await queryOne(queries.countPosts, session)).countPosts.low,
|
||||
countComments: (await queryOne(queries.countComments, session)).countComments.low,
|
||||
countNotifications: (await queryOne(queries.countNotifications, session)).countNotifications.low,
|
||||
countOrganizations: (await queryOne(queries.countOrganizations, session)).countOrganizations.low,
|
||||
countProjects: (await queryOne(queries.countProjects, session)).countProjects.low,
|
||||
countInvites: (await queryOne(queries.countInvites, session)).countInvites.low,
|
||||
countFollows: (await queryOne(queries.countFollows, session)).countFollows.low,
|
||||
countShouts: (await queryOne(queries.countShouts, session)).countShouts.low
|
||||
}
|
||||
resolve(data)
|
||||
})
|
||||
}
|
||||
// usersBySubstring: neo4jgraphql
|
||||
...statistics.Query,
|
||||
...userManagement.Query
|
||||
},
|
||||
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),
|
||||
// }
|
||||
|
||||
return true
|
||||
},
|
||||
login: async (parent, { email, password }, { driver, req, user }) => {
|
||||
// if (user && user.id) {
|
||||
// throw new Error('Already logged in.')
|
||||
// }
|
||||
const session = driver.session()
|
||||
return session.run(
|
||||
'MATCH (user:User {email: $userEmail}) ' +
|
||||
'RETURN user {.id, .slug, .name, .avatar, .locationName, .about, .email, .password, .role} as user LIMIT 1', {
|
||||
userEmail: email
|
||||
})
|
||||
.then(async (result) => {
|
||||
session.close()
|
||||
const [currentUser] = await result.records.map(function (record) {
|
||||
return record.get('user')
|
||||
})
|
||||
|
||||
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.')
|
||||
})
|
||||
},
|
||||
report: async (parent, { resource, description }, { driver, req, user }, resolveInfo) => {
|
||||
const contextId = uuid()
|
||||
const session = driver.session()
|
||||
const data = {
|
||||
id: contextId,
|
||||
type: resource.type,
|
||||
createdAt: (new Date()).toISOString(),
|
||||
description: resource.description
|
||||
}
|
||||
await session.run(
|
||||
'CREATE (r:Report $report) ' +
|
||||
'RETURN r.id, r.type, r.description', {
|
||||
report: data
|
||||
}
|
||||
)
|
||||
let contentType
|
||||
|
||||
switch (resource.type) {
|
||||
case 'post':
|
||||
case 'contribution':
|
||||
contentType = 'Post'
|
||||
break
|
||||
case 'comment':
|
||||
contentType = 'Comment'
|
||||
break
|
||||
case 'user':
|
||||
contentType = 'User'
|
||||
break
|
||||
}
|
||||
|
||||
await session.run(
|
||||
`MATCH (author:User {id: $userId}), (context:${contentType} {id: $resourceId}), (report:Report {id: $contextId}) ` +
|
||||
'MERGE (report)<-[:REPORTED]-(author) ' +
|
||||
'MERGE (context)<-[:REPORTED]-(report) ' +
|
||||
'RETURN context', {
|
||||
resourceId: resource.id,
|
||||
userId: user.id,
|
||||
contextId: contextId
|
||||
}
|
||||
)
|
||||
session.close()
|
||||
|
||||
// TODO: output Report compatible object
|
||||
return data
|
||||
},
|
||||
CreatePost: async (object, params, ctx, resolveInfo) => {
|
||||
params.activityId = uuid()
|
||||
const result = await neo4jgraphql(object, params, ctx, resolveInfo, false)
|
||||
debug(`user = ${JSON.stringify(ctx.user, null, 2)}`)
|
||||
const session = ctx.driver.session()
|
||||
const author = await session.run(
|
||||
'MATCH (author:User {slug: $slug}), (post:Post {id: $postId}) ' +
|
||||
'MERGE (post)<-[:WROTE]-(author) ' +
|
||||
'RETURN author', {
|
||||
slug: ctx.user.slug,
|
||||
postId: result.id
|
||||
})
|
||||
// debug(`result = ${JSON.stringify(author, null, 2)}`)
|
||||
debug(`actorId = ${author.records[0]._fields[0].properties.actorId}`)
|
||||
if (Array.isArray(author.records) && author.records.length > 0) {
|
||||
const actorId = author.records[0]._fields[0].properties.actorId
|
||||
const createActivity = await new Promise((resolve, reject) => {
|
||||
as.create()
|
||||
.id(`${actorId}/status/${params.activityId}`)
|
||||
.actor(`${actorId}`)
|
||||
.object(
|
||||
as.article()
|
||||
.id(`${actorId}/status/${result.id}`)
|
||||
.content(result.content)
|
||||
.to('https://www.w3.org/ns/activitystreams#Public')
|
||||
.publishedNow()
|
||||
.attributedTo(`${actorId}`)
|
||||
).prettyWrite((err, doc) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
debug(doc)
|
||||
const parsedDoc = JSON.parse(doc)
|
||||
parsedDoc.send = true
|
||||
resolve(JSON.stringify(parsedDoc))
|
||||
}
|
||||
})
|
||||
})
|
||||
try {
|
||||
await activityPub.sendActivity(createActivity)
|
||||
} catch (e) {
|
||||
debug(`error sending post activity = ${JSON.stringify(e, null, 2)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
...userManagement.Mutation,
|
||||
...reports.Mutation,
|
||||
...moderation.Mutation,
|
||||
...posts.Mutation
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
import Factory from './seed/factories'
|
||||
import { GraphQLClient, request } from 'graphql-request'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { host, login } from './jest/helpers'
|
||||
|
||||
const factory = Factory()
|
||||
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('isLoggedIn', () => {
|
||||
describe('unauthenticated', () => {
|
||||
it('returns false', async () => {
|
||||
const query = '{ isLoggedIn }'
|
||||
await expect(request(host, query)).resolves.toEqual({ isLoggedIn: false })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('login', () => {
|
||||
const mutation = (params) => {
|
||||
const { email, password } = params
|
||||
return `
|
||||
mutation {
|
||||
login(email:"${email}", password:"${password}"){
|
||||
token
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
describe('ask for a `token`', () => {
|
||||
describe('with valid email/password combination', () => {
|
||||
it('responds with a JWT token', async () => {
|
||||
const data = await request(host, 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 () => {
|
||||
await expect(
|
||||
request(host, mutation({
|
||||
email: 'test@example.org',
|
||||
password: 'wrong'
|
||||
}))
|
||||
).rejects.toThrow('Incorrect email address or password.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a non-existing email', () => {
|
||||
it('responds with "Incorrect email address or password."', async () => {
|
||||
await expect(
|
||||
request(host, mutation({
|
||||
email: 'non-existent@example.org',
|
||||
password: 'wrong'
|
||||
}))
|
||||
).rejects.toThrow('Incorrect email address or password.')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('CreatePost', () => {
|
||||
describe('unauthenticated', () => {
|
||||
let client
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(client.request(`mutation {
|
||||
CreatePost(
|
||||
title: "I am a post",
|
||||
content: "Some content"
|
||||
) { slug }
|
||||
}`)).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
let response
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
response = await client.request(`mutation {
|
||||
CreatePost(
|
||||
title: "A title",
|
||||
content: "Some content"
|
||||
) { title, content }
|
||||
}`, { headers })
|
||||
})
|
||||
|
||||
it('creates a post', () => {
|
||||
expect(response).toEqual({ CreatePost: { title: 'A title', content: 'Some content' } })
|
||||
})
|
||||
|
||||
it('assigns the authenticated user as author', async () => {
|
||||
const { User } = await client.request(`{
|
||||
User(email:"test@example.org") {
|
||||
contributions {
|
||||
title
|
||||
}
|
||||
}
|
||||
}`, { headers })
|
||||
expect(User).toEqual([ { contributions: [ { title: 'A title' } ] } ])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('report', () => {
|
||||
beforeEach(async () => {
|
||||
await factory.create('User', {
|
||||
email: 'test@example.org',
|
||||
password: '1234'
|
||||
})
|
||||
await factory.create('User', {
|
||||
id: 'u2',
|
||||
name: 'abusive-user',
|
||||
role: 'user',
|
||||
email: 'abusive-user@example.org'
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await factory.cleanDatabase()
|
||||
})
|
||||
|
||||
describe('unauthenticated', () => {
|
||||
let client
|
||||
it('throws authorization error', async () => {
|
||||
client = new GraphQLClient(host)
|
||||
await expect(
|
||||
client.request(`mutation {
|
||||
report(
|
||||
description: "I don't like this user",
|
||||
resource: {
|
||||
id: "u2",
|
||||
type: user
|
||||
}
|
||||
) { id, createdAt }
|
||||
}`)
|
||||
).rejects.toThrow('Not Authorised')
|
||||
})
|
||||
|
||||
describe('authenticated', () => {
|
||||
let headers
|
||||
let response
|
||||
beforeEach(async () => {
|
||||
headers = await login({ email: 'test@example.org', password: '1234' })
|
||||
client = new GraphQLClient(host, { headers })
|
||||
response = await client.request(`mutation {
|
||||
report(
|
||||
description: "I don't like this user",
|
||||
resource: {
|
||||
id: "u2",
|
||||
type: user
|
||||
}
|
||||
) { id, createdAt }
|
||||
}`,
|
||||
{ headers }
|
||||
)
|
||||
})
|
||||
it('creates a report', () => {
|
||||
let { id, createdAt } = response.report
|
||||
expect(response).toEqual({
|
||||
report: { id, createdAt }
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -7,12 +7,10 @@ export const host = 'http://127.0.0.1:4123'
|
||||
export async function login ({ email, password }) {
|
||||
const mutation = `
|
||||
mutation {
|
||||
login(email:"${email}", password:"${password}"){
|
||||
token
|
||||
}
|
||||
login(email:"${email}", password:"${password}")
|
||||
}`
|
||||
const response = await request(host, mutation)
|
||||
return {
|
||||
authorization: `Bearer ${response.login.token}`
|
||||
authorization: `Bearer ${response.login}`
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
|
||||
import jwt from 'jsonwebtoken'
|
||||
import ms from 'ms'
|
||||
|
||||
// Generate an Access Token for the given User ID
|
||||
export default function 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.verifySignature(token, process.env.JWT_SECRET, (err, data) => {
|
||||
// console.log('token verification:', err, data)
|
||||
// })
|
||||
return token
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
import { Strategy } from 'passport-jwt'
|
||||
import { fixUrl } from '../middleware/fixImageUrlsMiddleware'
|
||||
|
||||
const cookieExtractor = (req) => {
|
||||
var token = null
|
||||
if (req && req.cookies) {
|
||||
token = req.cookies['jwt']
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
export default (driver) => {
|
||||
const options = {
|
||||
jwtFromRequest: cookieExtractor,
|
||||
secretOrKey: process.env.JWT_SECRET,
|
||||
issuer: process.env.GRAPHQL_URI,
|
||||
audience: process.env.CLIENT_URI
|
||||
}
|
||||
|
||||
return new Strategy(options,
|
||||
async (JWTPayload, next) => {
|
||||
const session = driver.session()
|
||||
const result = await session.run(
|
||||
'MATCH (user:User {id: $userId}) ' +
|
||||
'RETURN user {.id, .slug, .name, .avatar, .email, .role} as user LIMIT 1',
|
||||
{
|
||||
userId: JWTPayload.id
|
||||
}
|
||||
)
|
||||
session.close()
|
||||
const [currentUser] = await result.records.map((record) => {
|
||||
return record.get('user')
|
||||
})
|
||||
|
||||
if (currentUser) {
|
||||
currentUser.avatar = fixUrl(currentUser.avatar)
|
||||
return next(null, currentUser)
|
||||
} else {
|
||||
return next(null, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -2,29 +2,21 @@ export default {
|
||||
Mutation: {
|
||||
CreateUser: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
args.disabled = false
|
||||
args.deleted = false
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreatePost: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
args.disabled = false
|
||||
args.deleted = false
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreateComment: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
args.disabled = false
|
||||
args.deleted = false
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
CreateOrganization: async (resolve, root, args, context, info) => {
|
||||
args.createdAt = (new Date()).toISOString()
|
||||
args.disabled = false
|
||||
args.deleted = false
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { rule, shield, allow } from 'graphql-shield'
|
||||
import { rule, shield, allow, or } from 'graphql-shield'
|
||||
|
||||
/*
|
||||
* TODO: implement
|
||||
@ -7,31 +7,58 @@ import { rule, shield, allow } from 'graphql-shield'
|
||||
const isAuthenticated = rule()(async (parent, args, ctx, info) => {
|
||||
return ctx.user !== null
|
||||
})
|
||||
/*
|
||||
const isAdmin = rule()(async (parent, args, ctx, info) => {
|
||||
return ctx.user.role === 'ADMIN'
|
||||
})
|
||||
const isModerator = rule()(async (parent, args, ctx, info) => {
|
||||
return ctx.user.role === 'MODERATOR'
|
||||
})
|
||||
*/
|
||||
|
||||
const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => {
|
||||
return ctx.user.id === parent.id
|
||||
const isModerator = rule()(async (parent, args, { user }, info) => {
|
||||
return user && (user.role === 'moderator' || user.role === 'admin')
|
||||
})
|
||||
|
||||
const isAdmin = rule()(async (parent, args, { user }, info) => {
|
||||
return user && (user.role === 'admin')
|
||||
})
|
||||
|
||||
const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info) => {
|
||||
return context.user.id === parent.id
|
||||
})
|
||||
|
||||
const onlyEnabledContent = rule({ cache: 'strict' })(async (parent, args, ctx, info) => {
|
||||
const { disabled, deleted } = args
|
||||
return !(disabled || deleted)
|
||||
})
|
||||
|
||||
const isAuthor = rule({ cache: 'no_cache' })(async (parent, args, { user, driver }) => {
|
||||
if (!user) return false
|
||||
const session = driver.session()
|
||||
const { id: postId } = args
|
||||
const result = await session.run(`
|
||||
MATCH (post:Post {id: $postId})<-[:WROTE]-(author)
|
||||
RETURN author
|
||||
`, { postId })
|
||||
const [author] = result.records.map((record) => {
|
||||
return record.get('author')
|
||||
})
|
||||
const { properties: { id: authorId } } = author
|
||||
session.close()
|
||||
return authorId === user.id
|
||||
})
|
||||
|
||||
// Permissions
|
||||
const permissions = shield({
|
||||
Query: {
|
||||
statistics: allow
|
||||
// fruits: and(isAuthenticated, or(isAdmin, isModerator)),
|
||||
// customers: and(isAuthenticated, isAdmin)
|
||||
statistics: allow,
|
||||
currentUser: allow,
|
||||
Post: or(onlyEnabledContent, isModerator)
|
||||
},
|
||||
Mutation: {
|
||||
CreatePost: isAuthenticated,
|
||||
// TODO UpdatePost: isOwner,
|
||||
// TODO DeletePost: isOwner,
|
||||
report: isAuthenticated
|
||||
UpdatePost: isAuthor,
|
||||
DeletePost: isAuthor,
|
||||
report: isAuthenticated,
|
||||
CreateBadge: isAdmin,
|
||||
UpdateBadge: isAdmin,
|
||||
DeleteBadge: isAdmin,
|
||||
|
||||
enable: isModerator,
|
||||
disable: isModerator
|
||||
// addFruitToBasket: isAuthenticated
|
||||
// CreateUser: allow,
|
||||
},
|
||||
@ -39,7 +66,6 @@ const permissions = shield({
|
||||
email: isMyOwn,
|
||||
password: isMyOwn
|
||||
}
|
||||
// Post: isAuthenticated
|
||||
})
|
||||
|
||||
export default permissions
|
||||
|
||||
@ -1,38 +1,26 @@
|
||||
const setDefaults = (args) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
if (typeof args.disabled !== 'boolean') {
|
||||
args.disabled = false
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
Post: async (resolve, root, args, context, info) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
if (typeof args.disabled !== 'boolean') {
|
||||
args.disabled = false
|
||||
}
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
Post: (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
},
|
||||
Comment: async (resolve, root, args, context, info) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
if (typeof args.disabled !== 'boolean') {
|
||||
args.disabled = false
|
||||
}
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
},
|
||||
User: async (resolve, root, args, context, info) => {
|
||||
if (typeof args.deleted !== 'boolean') {
|
||||
args.deleted = false
|
||||
}
|
||||
if (typeof args.disabled !== 'boolean') {
|
||||
args.disabled = false
|
||||
}
|
||||
// console.log('ROOT', root)
|
||||
// console.log('ARGS', args)
|
||||
// console.log('CONTEXT', context)
|
||||
// console.log('info', info.fieldNodes[0].arguments)
|
||||
const result = await resolve(root, args, context, info)
|
||||
return result
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
}
|
||||
},
|
||||
Mutation: async (resolve, root, args, context, info) => {
|
||||
return resolve(root, setDefaults(args), context, info)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,46 @@
|
||||
type Query {
|
||||
isLoggedIn: Boolean!
|
||||
"Get the currently logged in User based on the given JWT Token"
|
||||
currentUser: User
|
||||
"Get the latest Network Statistics"
|
||||
statistics: Statistics!
|
||||
}
|
||||
type Mutation {
|
||||
login(email: String!, password: String!): LoggedInUser
|
||||
"Get a JWT Token for the given Email and password"
|
||||
login(email: String!, password: String!): String!
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
report(resource: Resource!, description: String): Report
|
||||
}
|
||||
type LoggedInUser {
|
||||
id: ID!
|
||||
slug: String!
|
||||
name: String!
|
||||
avatar:String!
|
||||
email: String!
|
||||
role: String!
|
||||
locationName: String
|
||||
about: String
|
||||
token: String!
|
||||
"Shout the given Type and ID"
|
||||
shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId})
|
||||
WHERE $type IN labels(n) AND NOT wu.id = $cypherParams.currentUserId
|
||||
MERGE (u)-[r:SHOUTED]->(n)
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
"Unshout the given Type and ID"
|
||||
unshout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (:User {id: $cypherParams.currentUserId})-[r:SHOUTED]->(n {id: $id})
|
||||
WHERE $type IN labels(n)
|
||||
DELETE r
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
|
||||
"Follow the given Type and ID"
|
||||
follow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (n {id: $id}), (u:User {id: $cypherParams.currentUserId})
|
||||
WHERE $type IN labels(n) AND NOT $id = $cypherParams.currentUserId
|
||||
MERGE (u)-[r:FOLLOWS]->(n)
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
"Unfollow the given Type and ID"
|
||||
unfollow(id: ID!, type: FollowTypeEnum): Boolean! @cypher(statement: """
|
||||
MATCH (:User {id: $cypherParams.currentUserId})-[r:FOLLOWS]->(n {id: $id})
|
||||
WHERE $type IN labels(n)
|
||||
DELETE r
|
||||
RETURN COUNT(r) > 0
|
||||
""")
|
||||
disable(resource: Resource!): Boolean!
|
||||
enable(resource: Resource!): Boolean!
|
||||
}
|
||||
|
||||
type Statistics {
|
||||
@ -85,6 +109,7 @@ type User {
|
||||
avatar: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
role: UserGroupEnum
|
||||
publicKey: String
|
||||
privateKey: String
|
||||
@ -97,13 +122,19 @@ type User {
|
||||
updatedAt: String
|
||||
|
||||
friends: [User]! @relation(name: "FRIENDS", direction: "BOTH")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(r)")
|
||||
friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
following: [User]! @relation(name: "FOLLOWS", direction: "OUT")
|
||||
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(r)")
|
||||
followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(r)")
|
||||
followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)")
|
||||
|
||||
"Is the currently logged in user following that user?"
|
||||
followedByCurrentUser: Boolean! @cypher(statement: """
|
||||
MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
""")
|
||||
|
||||
#contributions: [WrittenPost]!
|
||||
#contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]!
|
||||
@ -122,7 +153,7 @@ type User {
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(r:Comment) WHERE NOT r.deleted = true RETURN COUNT(r)")
|
||||
|
||||
shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
|
||||
organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT")
|
||||
organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT")
|
||||
@ -147,6 +178,7 @@ type Post {
|
||||
visibility: VisibilityEnum
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
createdAt: String
|
||||
updatedAt: String
|
||||
|
||||
@ -163,7 +195,13 @@ type Post {
|
||||
commentsCount: Int! @cypher(statement: "MATCH (this)<-[:COMMENTS]-(r:Comment) RETURN COUNT(r)")
|
||||
|
||||
shoutedBy: [User]! @relation(name: "SHOUTED", direction: "IN")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(r)")
|
||||
shoutedCount: Int! @cypher(statement: "MATCH (this)<-[:SHOUTED]-(r:User) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)")
|
||||
|
||||
"Has the currently logged in user shouted that post?"
|
||||
shoutedByCurrentUser: Boolean! @cypher(statement: """
|
||||
MATCH (this)<-[:SHOUTED]-(u:User {id: $cypherParams.currentUserId})
|
||||
RETURN COUNT(u) >= 1
|
||||
""")
|
||||
}
|
||||
|
||||
type Comment {
|
||||
@ -177,6 +215,7 @@ type Comment {
|
||||
updatedAt: String
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
disabledBy: User @relation(name: "DISABLED", direction: "IN")
|
||||
}
|
||||
|
||||
type Report {
|
||||
@ -217,6 +256,16 @@ enum BadgeStatusEnum {
|
||||
permanent
|
||||
temporary
|
||||
}
|
||||
enum ShoutTypeEnum {
|
||||
Post
|
||||
Organization
|
||||
Project
|
||||
}
|
||||
enum FollowTypeEnum {
|
||||
User
|
||||
Organization
|
||||
Project
|
||||
}
|
||||
|
||||
type Organization {
|
||||
id: ID!
|
||||
@ -238,7 +287,7 @@ type Tag {
|
||||
name: String!
|
||||
taggedPosts: [Post]! @relation(name: "TAGGED", direction: "IN")
|
||||
taggedOrganizations: [Organization]! @relation(name: "TAGGED", direction: "IN")
|
||||
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(p)")
|
||||
taggedCount: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p) RETURN COUNT(DISTINCT p)")
|
||||
taggedCountUnique: Int! @cypher(statement: "MATCH (this)<-[:TAGGED]-(p)<-[:WROTE]-(u:User) RETURN COUNT(DISTINCT u)")
|
||||
deleted: Boolean
|
||||
disabled: Boolean
|
||||
|
||||
@ -10,14 +10,14 @@ export default function (params) {
|
||||
} = params
|
||||
|
||||
return `
|
||||
mutation {
|
||||
CreateBadge(
|
||||
id: "${id}",
|
||||
key: "${key}",
|
||||
type: ${type},
|
||||
status: ${status},
|
||||
icon: "${icon}"
|
||||
) { id }
|
||||
}
|
||||
mutation {
|
||||
CreateBadge(
|
||||
id: "${id}",
|
||||
key: "${key}",
|
||||
type: ${type},
|
||||
status: ${status},
|
||||
icon: "${icon}"
|
||||
) { id }
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
@ -15,13 +15,11 @@ export const seedServerHost = 'http://127.0.0.1:4001'
|
||||
const authenticatedHeaders = async ({ email, password }, host) => {
|
||||
const mutation = `
|
||||
mutation {
|
||||
login(email:"${email}", password:"${password}"){
|
||||
token
|
||||
}
|
||||
login(email:"${email}", password:"${password}")
|
||||
}`
|
||||
const response = await request(host, mutation)
|
||||
return {
|
||||
authorization: `Bearer ${response.login.token}`
|
||||
authorization: `Bearer ${response.login}`
|
||||
}
|
||||
}
|
||||
const factories = {
|
||||
@ -88,6 +86,36 @@ export default function Factory (options = {}) {
|
||||
this.lastResponse = await this.graphQLClient.request(mutation)
|
||||
return this
|
||||
},
|
||||
async mutate (mutation, variables) {
|
||||
this.lastResponse = await this.graphQLClient.request(mutation, variables)
|
||||
return this
|
||||
},
|
||||
async shout (properties) {
|
||||
const { id, type } = properties
|
||||
const mutation = `
|
||||
mutation {
|
||||
shout(
|
||||
id: "${id}",
|
||||
type: ${type}
|
||||
)
|
||||
}
|
||||
`
|
||||
this.lastResponse = await this.graphQLClient.request(mutation)
|
||||
return this
|
||||
},
|
||||
async follow (properties) {
|
||||
const { id, type } = properties
|
||||
const mutation = `
|
||||
mutation {
|
||||
follow(
|
||||
id: "${id}",
|
||||
type: ${type}
|
||||
)
|
||||
}
|
||||
`
|
||||
this.lastResponse = await this.graphQLClient.request(mutation)
|
||||
return this
|
||||
},
|
||||
async cleanDatabase () {
|
||||
this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver })
|
||||
return this
|
||||
@ -96,6 +124,7 @@ export default function Factory (options = {}) {
|
||||
result.authenticateAs.bind(result)
|
||||
result.create.bind(result)
|
||||
result.relate.bind(result)
|
||||
result.mutate.bind(result)
|
||||
result.cleanDatabase.bind(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import uuid from 'uuid/v4'
|
||||
export default function create (params) {
|
||||
const {
|
||||
id = uuid(),
|
||||
name = faker.comany.companyName(),
|
||||
name = faker.company.companyName(),
|
||||
description = faker.company.catchPhrase(),
|
||||
disabled = false,
|
||||
deleted = false
|
||||
|
||||
@ -14,7 +14,6 @@ export default function (params) {
|
||||
].join('. '),
|
||||
image = faker.image.image(),
|
||||
visibility = 'public',
|
||||
disabled = false,
|
||||
deleted = false
|
||||
} = params
|
||||
|
||||
@ -26,7 +25,6 @@ export default function (params) {
|
||||
content: "${content}",
|
||||
image: "${image}",
|
||||
visibility: ${visibility},
|
||||
disabled: ${disabled},
|
||||
deleted: ${deleted}
|
||||
) { title, content }
|
||||
}
|
||||
|
||||
@ -25,10 +25,13 @@ export default function create (params) {
|
||||
disabled: ${disabled},
|
||||
deleted: ${deleted}
|
||||
) {
|
||||
id
|
||||
name
|
||||
email
|
||||
avatar
|
||||
role
|
||||
deleted
|
||||
disabled
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
@ -23,6 +23,15 @@ import Factory from './factories'
|
||||
f.create('User', { id: 'u7', name: 'Dagobert', role: 'user', email: 'dagobert@example.org' })
|
||||
])
|
||||
|
||||
const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([
|
||||
Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'user@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'track@example.org', password: '1234' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
f.relate('User', 'Badges', { from: 'b6', to: 'u1' }),
|
||||
f.relate('User', 'Badges', { from: 'b5', to: 'u2' }),
|
||||
@ -30,12 +39,6 @@ import Factory from './factories'
|
||||
f.relate('User', 'Badges', { from: 'b3', to: 'u4' }),
|
||||
f.relate('User', 'Badges', { from: 'b2', to: 'u5' }),
|
||||
f.relate('User', 'Badges', { from: 'b1', to: 'u6' }),
|
||||
f.relate('User', 'Following', { from: 'u1', to: 'u2' }),
|
||||
f.relate('User', 'Following', { from: 'u2', to: 'u3' }),
|
||||
f.relate('User', 'Following', { from: 'u3', to: 'u4' }),
|
||||
f.relate('User', 'Following', { from: 'u4', to: 'u5' }),
|
||||
f.relate('User', 'Following', { from: 'u5', to: 'u6' }),
|
||||
f.relate('User', 'Following', { from: 'u6', to: 'u7' }),
|
||||
f.relate('User', 'Friends', { from: 'u1', to: 'u2' }),
|
||||
f.relate('User', 'Friends', { from: 'u1', to: 'u3' }),
|
||||
f.relate('User', 'Friends', { from: 'u2', to: 'u3' }),
|
||||
@ -44,6 +47,21 @@ import Factory from './factories'
|
||||
f.relate('User', 'Blacklisted', { from: 'u7', to: 'u6' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
asAdmin
|
||||
.follow({ id: 'u3', type: 'User' }),
|
||||
asModerator
|
||||
.follow({ id: 'u4', type: 'User' }),
|
||||
asUser
|
||||
.follow({ id: 'u4', type: 'User' }),
|
||||
asTick
|
||||
.follow({ id: 'u6', type: 'User' }),
|
||||
asTrick
|
||||
.follow({ id: 'u4', type: 'User' }),
|
||||
asTrack
|
||||
.follow({ id: 'u3', type: 'User' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
f.create('Category', { id: 'cat1', name: 'Just For Fun', slug: 'justforfun', icon: 'smile' }),
|
||||
f.create('Category', { id: 'cat2', name: 'Happyness & Values', slug: 'happyness-values', icon: 'heart-o' }),
|
||||
@ -70,19 +88,10 @@ import Factory from './factories'
|
||||
f.create('Tag', { id: 't4', name: 'Freiheit' })
|
||||
])
|
||||
|
||||
const [ asAdmin, asModerator, asUser, asTick, asTrick, asTrack ] = await Promise.all([
|
||||
Factory().authenticateAs({ email: 'admin@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'moderator@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'user@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'tick@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'trick@example.org', password: '1234' }),
|
||||
Factory().authenticateAs({ email: 'track@example.org', password: '1234' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
asAdmin.create('Post', { id: 'p0' }),
|
||||
asModerator.create('Post', { id: 'p1' }),
|
||||
asUser.create('Post', { id: 'p2' }),
|
||||
asUser.create('Post', { id: 'p2', deleted: true }),
|
||||
asTick.create('Post', { id: 'p3' }),
|
||||
asTrick.create('Post', { id: 'p4' }),
|
||||
asTrack.create('Post', { id: 'p5' }),
|
||||
@ -98,6 +107,16 @@ import Factory from './factories'
|
||||
asTick.create('Post', { id: 'p15' })
|
||||
])
|
||||
|
||||
const disableMutation = `
|
||||
mutation {
|
||||
disable(resource: {
|
||||
id: "p11"
|
||||
type: contribution
|
||||
})
|
||||
}
|
||||
`
|
||||
await asModerator.mutate(disableMutation)
|
||||
|
||||
await Promise.all([
|
||||
f.relate('Post', 'Categories', { from: 'p0', to: 'cat16' }),
|
||||
f.relate('Post', 'Categories', { from: 'p1', to: 'cat1' }),
|
||||
@ -133,13 +152,26 @@ import Factory from './factories'
|
||||
f.relate('Post', 'Tags', { from: 'p14', to: 't2' }),
|
||||
f.relate('Post', 'Tags', { from: 'p15', to: 't3' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
f.relate('User', 'Shouted', { from: 'u1', to: 'p2' }),
|
||||
f.relate('User', 'Shouted', { from: 'u1', to: 'p3' }),
|
||||
f.relate('User', 'Shouted', { from: 'u2', to: 'p1' }),
|
||||
f.relate('User', 'Shouted', { from: 'u3', to: 'p1' }),
|
||||
f.relate('User', 'Shouted', { from: 'u3', to: 'p4' }),
|
||||
f.relate('User', 'Shouted', { from: 'u4', to: 'p1' })
|
||||
asAdmin
|
||||
.shout({ id: 'p2', type: 'Post' }),
|
||||
asAdmin
|
||||
.shout({ id: 'p6', type: 'Post' }),
|
||||
asModerator
|
||||
.shout({ id: 'p0', type: 'Post' }),
|
||||
asModerator
|
||||
.shout({ id: 'p6', type: 'Post' }),
|
||||
asUser
|
||||
.shout({ id: 'p6', type: 'Post' }),
|
||||
asUser
|
||||
.shout({ id: 'p7', type: 'Post' }),
|
||||
asTick
|
||||
.shout({ id: 'p8', type: 'Post' }),
|
||||
asTick
|
||||
.shout({ id: 'p9', type: 'Post' }),
|
||||
asTrack
|
||||
.shout({ id: 'p10', type: 'Post' })
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
|
||||
@ -8,11 +8,8 @@ import middleware from './middleware'
|
||||
import applyDirectives from './bootstrap/directives'
|
||||
import applyScalars from './bootstrap/scalars'
|
||||
import { getDriver } from './bootstrap/neo4j'
|
||||
|
||||
import passport from 'passport'
|
||||
import jwtStrategy from './jwt/strategy'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import helmet from 'helmet'
|
||||
import decode from './jwt/decode'
|
||||
|
||||
dotenv.config()
|
||||
// check env and warn
|
||||
@ -43,20 +40,17 @@ schema = applyScalars(applyDirectives(schema))
|
||||
|
||||
const createServer = (options) => {
|
||||
const defaults = {
|
||||
context: async (req) => {
|
||||
const payload = {
|
||||
context: async ({ request }) => {
|
||||
const authorizationHeader = request.headers.authorization || ''
|
||||
const user = await decode(driver, authorizationHeader)
|
||||
return {
|
||||
driver,
|
||||
user: null,
|
||||
req: req.request
|
||||
user,
|
||||
req: request,
|
||||
cypherParams: {
|
||||
currentUserId: user ? user.id : null
|
||||
}
|
||||
}
|
||||
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,
|
||||
debug: debug,
|
||||
@ -66,12 +60,8 @@ const createServer = (options) => {
|
||||
}
|
||||
const server = new GraphQLServer(Object.assign({}, defaults, options))
|
||||
|
||||
passport.use('jwt', jwtStrategy(driver))
|
||||
server.express.use(helmet())
|
||||
server.express.use(passport.initialize())
|
||||
server.express.use(express.static('public'))
|
||||
|
||||
server.express.post('/graphql', passport.authenticate(['jwt'], { session: false }))
|
||||
return server
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user