From 1e427a823d10aca3a35f9efd9670e299eebca87f Mon Sep 17 00:00:00 2001 From: kachulio1 Date: Tue, 12 Mar 2019 23:31:07 +0300 Subject: [PATCH 1/2] add addSocialMediaAccount mutation --- src/jwt/decode.js | 2 +- src/middleware/permissionsMiddleware.js | 3 +- src/resolvers/user_management.js | 57 ++++++++++++++++++------- src/resolvers/user_management.spec.js | 30 +++++++++++++ src/schema.graphql | 3 +- 5 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/jwt/decode.js b/src/jwt/decode.js index 0ab1e45..df3d68b 100644 --- a/src/jwt/decode.js +++ b/src/jwt/decode.js @@ -7,7 +7,7 @@ export default async (driver, authorizationHeader) => { try { const decoded = await jwt.verify(token, process.env.JWT_SECRET) id = decoded.sub - } catch { + } catch (e) { return null } const session = driver.session() diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 7fb6e75..2cce7c1 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -56,7 +56,8 @@ const permissions = shield({ CreateBadge: isAdmin, UpdateBadge: isAdmin, DeleteBadge: isAdmin, - + addSocialMediaAccount: isAuthenticated, + enable: isModerator, disable: isModerator // addFruitToBasket: isAuthenticated diff --git a/src/resolvers/user_management.js b/src/resolvers/user_management.js index ec4ae7c..3d45fd6 100644 --- a/src/resolvers/user_management.js +++ b/src/resolvers/user_management.js @@ -1,7 +1,7 @@ -import encode from '../jwt/encode' -import bcrypt from 'bcryptjs' -import { AuthenticationError } from 'apollo-server' -import { neo4jgraphql } from 'neo4j-graphql-js' +import encode from "../jwt/encode" +import bcrypt from "bcryptjs" +import { AuthenticationError } from "apollo-server" +import { neo4jgraphql } from "neo4j-graphql-js" export default { Query: { @@ -25,27 +25,54 @@ export default { return true }, - login: async (parent, { email, password }, { driver, req, user }) => { + login: async (_, { 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, .email, .password, .role} as user LIMIT 1', { - userEmail: email - }) - .then(async (result) => { + return session + .run( + "MATCH (user:User {email: $userEmail}) " + + "RETURN user {.id, .slug, .name, .avatar, .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') + const [currentUser] = await result.records.map(function(record) { + return record.get("user") }) - if (currentUser && await bcrypt.compareSync(password, currentUser.password)) { + if ( + currentUser && + (await bcrypt.compareSync(password, currentUser.password)) + ) { delete currentUser.password return encode(currentUser) - } else throw new AuthenticationError('Incorrect email address or password.') + } else + throw new AuthenticationError( + "Incorrect email address or password." + ) }) + }, + addSocialMediaAccount: async (_, { url }, { driver, user }) => { + const session = driver.session() + const { email } = user + const result = await session.run( + `MATCH (user:User {email: $userEmail}) + SET user.socialMedia = $url + RETURN user {.socialMedia}`, + { + userEmail: email, + url + } + ) + session.close() + const [currentUser] = result.records.map(record => { + return record.get("user") + }) + return !!currentUser.socialMedia } } } diff --git a/src/resolvers/user_management.spec.js b/src/resolvers/user_management.spec.js index a3bf6fd..81db24b 100644 --- a/src/resolvers/user_management.spec.js +++ b/src/resolvers/user_management.spec.js @@ -176,4 +176,34 @@ describe('login', () => { }) }) }) + + describe('addSocialMediaAccount', () => { + const mutation = (params) => { + const { url } = params + return ` + mutation { + addSocialMediaAccount(url:"${url}") + }` + } + describe('unauthenticated', () => { + it('returns not authorised', async () => { + await expect(request(host, mutation({ + url: 'https://freeradical.zone/@mattwr18' + }))).rejects.toMatch('Authorised!') + }) + }) + + describe('with valid JWT Bearer Token', () => { + let client + let headers + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('returns true', async () => { + await expect(client.request(query)).rejects.toMatch('Please enter a valid URL') + }) + }) + }) + }) }) diff --git a/src/schema.graphql b/src/schema.graphql index a542e12..5f88a3c 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -12,6 +12,7 @@ type Mutation { report(id: ID!, description: String): Report disable(id: ID!): ID enable(id: ID!): ID + addSocialMediaAccount(url: String!): Boolean! "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}) @@ -99,10 +100,10 @@ type User { disabled: Boolean disabledBy: User @relation(name: "DISABLED", direction: "IN") role: UserGroupEnum - location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") locationName: String about: String + socialMedia: [String] createdAt: String updatedAt: String From 506cb1d64758d015dc086814a9923b0eb0f02b73 Mon Sep 17 00:00:00 2001 From: kachulio1 Date: Thu, 21 Mar 2019 20:03:11 +0300 Subject: [PATCH 2/2] add socialmedia mutation --- src/middleware/permissionsMiddleware.js | 2 +- src/resolvers/user_management.js | 3 +- src/resolvers/user_management.spec.js | 336 +++++++++++++----------- src/schema.graphql | 2 +- 4 files changed, 186 insertions(+), 157 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 2cce7c1..9084654 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -56,7 +56,7 @@ const permissions = shield({ CreateBadge: isAdmin, UpdateBadge: isAdmin, DeleteBadge: isAdmin, - addSocialMediaAccount: isAuthenticated, + addSocialMedia: isAuthenticated, enable: isModerator, disable: isModerator diff --git a/src/resolvers/user_management.js b/src/resolvers/user_management.js index 3d45fd6..6e5956f 100644 --- a/src/resolvers/user_management.js +++ b/src/resolvers/user_management.js @@ -56,8 +56,9 @@ export default { ) }) }, - addSocialMediaAccount: async (_, { url }, { driver, user }) => { + addSocialMedia: async (_, { url }, { driver, user }) => { const session = driver.session() + const { email } = user const result = await session.run( `MATCH (user:User {email: $userEmail}) diff --git a/src/resolvers/user_management.spec.js b/src/resolvers/user_management.spec.js index 81db24b..0ce6016 100644 --- a/src/resolvers/user_management.spec.js +++ b/src/resolvers/user_management.spec.js @@ -1,9 +1,9 @@ -import Factory from '../seed/factories' -import { GraphQLClient, request } from 'graphql-request' -import jwt from 'jsonwebtoken' -import { host, login } from '../jest/helpers' +import Factory from "../seed/factories"; +import { GraphQLClient, request } from "graphql-request"; +import jwt from "jsonwebtoken"; +import { host, login } from "../jest/helpers"; -const factory = Factory() +const factory = Factory(); // here is the decoded JWT token: // { @@ -21,59 +21,71 @@ const factory = Factory() // iss: 'http://localhost:4000', // sub: 'u3' // } -const jennyRostocksHeaders = { authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc' } +const jennyRostocksHeaders = { + authorization: + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciIsImxvY2F0aW9uTmFtZSI6bnVsbCwibmFtZSI6Ikplbm55IFJvc3RvY2siLCJhYm91dCI6bnVsbCwiYXZhdGFyIjoiaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3VpZmFjZXMvZmFjZXMvdHdpdHRlci9zYXNoYV9zaGVzdGFrb3YvMTI4LmpwZyIsImlkIjoidTMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5vcmciLCJzbHVnIjoiamVubnktcm9zdG9jayIsImlhdCI6MTU1MDg0NjY4MCwiZXhwIjoxNjM3MjQ2NjgwLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjQwMDAiLCJzdWIiOiJ1MyJ9.eZ_mVKas4Wzoc_JrQTEWXyRn7eY64cdIg4vqQ-F_7Jc" +}; beforeEach(async () => { - await factory.create('User', { - avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/seyedhossein1/128.jpg', - id: 'acb2d923-f3af-479e-9f00-61b12e864666', - name: 'Matilde Hermiston', - slug: 'matilde-hermiston', - role: 'user', - email: 'test@example.org', - password: '1234' - }) -}) + await factory.create("User", { + avatar: + "https://s3.amazonaws.com/uifaces/faces/twitter/seyedhossein1/128.jpg", + id: "acb2d923-f3af-479e-9f00-61b12e864666", + name: "Matilde Hermiston", + slug: "matilde-hermiston", + role: "user", + email: "test@example.org", + password: "1234" + }); +}); afterEach(async () => { - await factory.cleanDatabase() -}) - -describe('isLoggedIn', () => { - const query = '{ isLoggedIn }' - describe('unauthenticated', () => { - it('returns false', async () => { - await expect(request(host, query)).resolves.toEqual({ isLoggedIn: false }) - }) - }) - - describe('with malformed JWT Bearer token', () => { - const headers = { authorization: 'blah' } - const client = new GraphQLClient(host, { headers }) - - it('returns false', async () => { - await expect(client.request(query)).resolves.toEqual({ isLoggedIn: false }) - }) - }) - - describe('with valid JWT Bearer token', () => { - const client = new GraphQLClient(host, { headers: jennyRostocksHeaders }) - - it('returns false', async () => { - await expect(client.request(query)).resolves.toEqual({ isLoggedIn: false }) - }) - - describe('and a corresponding user in the database', () => { - it('returns true', async () => { + await factory.cleanDatabase(); +}); + +describe("isLoggedIn", () => { + const query = "{ isLoggedIn }"; + describe("unauthenticated", () => { + it("returns false", async () => { + await expect(request(host, query)).resolves.toEqual({ + isLoggedIn: false + }); + }); + }); + + describe("with malformed JWT Bearer token", () => { + const headers = { authorization: "blah" }; + const client = new GraphQLClient(host, { headers }); + + it("returns false", async () => { + await expect(client.request(query)).resolves.toEqual({ + isLoggedIn: false + }); + }); + }); + + describe("with valid JWT Bearer token", () => { + const client = new GraphQLClient(host, { headers: jennyRostocksHeaders }); + + it("returns false", async () => { + await expect(client.request(query)).resolves.toEqual({ + isLoggedIn: false + }); + }); + + describe("and a corresponding user in the database", () => { + it("returns true", async () => { // see the decoded token above - await factory.create('User', { id: 'u3' }) - await expect(client.request(query)).resolves.toEqual({ isLoggedIn: true }) - }) - }) - }) -}) - -describe('currentUser', () => { + await factory.create("User", { id: "u3" }); + await expect(client.request(query)).resolves.toEqual({ + isLoggedIn: true + }); + }); + }); + }); +}); + +describe("currentUser", () => { const query = `{ currentUser { id @@ -83,127 +95,143 @@ describe('currentUser', () => { email role } - }` + }`; - describe('unauthenticated', () => { - it('returns null', async () => { - const expected = { currentUser: null } - await expect(request(host, query)).resolves.toEqual(expected) - }) - }) + describe("unauthenticated", () => { + it("returns null", async () => { + const expected = { currentUser: null }; + await expect(request(host, query)).resolves.toEqual(expected); + }); + }); - describe('with valid JWT Bearer Token', () => { - let client - let headers + describe("with valid JWT Bearer Token", () => { + let client; + let headers; - describe('but no corresponding user in the database', () => { + describe("but no corresponding user in the database", () => { beforeEach(async () => { - client = new GraphQLClient(host, { headers: jennyRostocksHeaders }) - }) + client = new GraphQLClient(host, { headers: jennyRostocksHeaders }); + }); - it('returns null', async () => { - const expected = { currentUser: null } - await expect(client.request(query)).resolves.toEqual(expected) - }) - }) + it("returns null", async () => { + const expected = { currentUser: null }; + await expect(client.request(query)).resolves.toEqual(expected); + }); + }); - describe('and corresponding user in the database', () => { + describe("and corresponding user in the database", () => { beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) + headers = await login({ email: "test@example.org", password: "1234" }); + client = new GraphQLClient(host, { headers }); + }); - it('returns the whole user object', async () => { + it("returns the whole user object", async () => { const expected = { currentUser: { - avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/seyedhossein1/128.jpg', - email: 'test@example.org', - id: 'acb2d923-f3af-479e-9f00-61b12e864666', - name: 'Matilde Hermiston', - slug: 'matilde-hermiston', - role: 'user' + avatar: + "https://s3.amazonaws.com/uifaces/faces/twitter/seyedhossein1/128.jpg", + email: "test@example.org", + id: "acb2d923-f3af-479e-9f00-61b12e864666", + name: "Matilde Hermiston", + slug: "matilde-hermiston", + role: "user" } - } - await expect(client.request(query)).resolves.toEqual(expected) - }) - }) - }) -}) - -describe('login', () => { - const mutation = (params) => { - const { email, password } = params + }; + await expect(client.request(query)).resolves.toEqual(expected); + }); + }); + }); +}); + +describe("login", () => { + const mutation = params => { + const { email, password } = params; return ` mutation { login(email:"${email}", password:"${password}") - }` - } - - 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 + }`; + }; + + 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() - }) - }) - }) + expect(data.email).toEqual("test@example.org"); + expect(err).toBeNull(); + }); + }); + }); - describe('with a valid email but incorrect password', () => { + 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', () => { + 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('addSocialMediaAccount', () => { - const mutation = (params) => { - const { url } = params + request( + host, + mutation({ + email: "non-existent@example.org", + password: "wrong" + }) + ) + ).rejects.toThrow("Incorrect email address or password."); + }); + }); + }); + + describe("addSocialMedia", () => { + const mutation = params => { + const { url } = params; return ` mutation { - addSocialMediaAccount(url:"${url}") - }` - } - describe('unauthenticated', () => { - it('returns not authorised', async () => { - await expect(request(host, mutation({ - url: 'https://freeradical.zone/@mattwr18' - }))).rejects.toMatch('Authorised!') - }) - }) - - describe('with valid JWT Bearer Token', () => { - let client - let headers + addSocialMedia(url:"${url}") + }`; + }; + describe("unauthenticated", () => { + it("returns not authorised", async () => { + await expect( + request( + host, + mutation({ + url: "https://freeradical.zone/@mattwr18" + }) + ) + ).rejects.toMatch("Authorised!"); + }); + }); + + describe("with valid JWT Bearer Token", () => { + let client; + let headers; beforeEach(async () => { - headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) - it('returns true', async () => { - await expect(client.request(query)).rejects.toMatch('Please enter a valid URL') - }) - }) - }) - }) -}) + headers = await login({ email: "test@example.org", password: "1234" }); + client = new GraphQLClient(host, { headers }); + }); + it("returns true", async () => { + await expect(client.request(query)).rejects.toMatch( + "Please enter a valid URL" + ); + }); + }); + }); +}); diff --git a/src/schema.graphql b/src/schema.graphql index 5f88a3c..e429472 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -12,7 +12,7 @@ type Mutation { report(id: ID!, description: String): Report disable(id: ID!): ID enable(id: ID!): ID - addSocialMediaAccount(url: String!): Boolean! + addSocialMedia(url: String!): Boolean! "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})