From d0df4e8df0d2620b9b375c9a77340c45c59ec184 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Tue, 26 Feb 2019 15:16:38 +0000 Subject: [PATCH 01/14] Bump @babel/core from 7.3.3 to 7.3.4 Bumps [@babel/core](https://github.com/babel/babel) from 7.3.3 to 7.3.4. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) - [Commits](https://github.com/babel/babel/compare/v7.3.3...v7.3.4) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 58 ++++++++++++++++++++++++++-------------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 39c9c14c6..65db47e1f 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ }, "devDependencies": { "@babel/cli": "~7.2.3", - "@babel/core": "~7.3.3", + "@babel/core": "~7.3.4", "@babel/node": "~7.2.2", "@babel/preset-env": "~7.3.1", "@babel/register": "~7.0.0", diff --git a/yarn.lock b/yarn.lock index 2fe610fee..34884e22b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,18 +38,18 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@^7.1.0", "@babel/core@~7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.3.tgz#d090d157b7c5060d05a05acaebc048bd2b037947" - integrity sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ== +"@babel/core@^7.1.0", "@babel/core@~7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" + integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.3.3" + "@babel/generator" "^7.3.4" "@babel/helpers" "^7.2.0" - "@babel/parser" "^7.3.3" + "@babel/parser" "^7.3.4" "@babel/template" "^7.2.2" - "@babel/traverse" "^7.2.2" - "@babel/types" "^7.3.3" + "@babel/traverse" "^7.3.4" + "@babel/types" "^7.3.4" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -58,12 +58,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.2.2", "@babel/generator@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e" - integrity sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A== +"@babel/generator@^7.0.0", "@babel/generator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e" + integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg== dependencies: - "@babel/types" "^7.3.3" + "@babel/types" "^7.3.4" jsesc "^2.5.1" lodash "^4.17.11" source-map "^0.5.0" @@ -253,10 +253,10 @@ lodash "^4.17.10" v8flags "^3.1.1" -"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.3.tgz#092d450db02bdb6ccb1ca8ffd47d8774a91aef87" - integrity sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg== +"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" + integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -636,25 +636,25 @@ "@babel/parser" "^7.2.2" "@babel/types" "^7.2.2" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" + integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" + "@babel/generator" "^7.3.4" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.2.3" - "@babel/types" "^7.2.2" + "@babel/parser" "^7.3.4" + "@babel/types" "^7.3.4" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.10" + lodash "^4.17.11" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.3.tgz#6c44d1cdac2a7625b624216657d5bc6c107ab436" - integrity sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" + integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== dependencies: esutils "^2.0.2" lodash "^4.17.11" From ce28de893bd6fe3faaae3ac4701437b0a03f8e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 27 Feb 2019 16:41:18 +0100 Subject: [PATCH 02/14] Write a test for #27 Moderators are allowed to see disabled or deleted posts if they ask for it. --- src/middleware/softDeleteMiddleware.spec.js | 119 ++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/middleware/softDeleteMiddleware.spec.js diff --git a/src/middleware/softDeleteMiddleware.spec.js b/src/middleware/softDeleteMiddleware.spec.js new file mode 100644 index 000000000..2e5dbe054 --- /dev/null +++ b/src/middleware/softDeleteMiddleware.spec.js @@ -0,0 +1,119 @@ +import Factory from '../seed/factories' +import { host, login } from '../jest/helpers' +import { GraphQLClient } from 'graphql-request' + +const factory = Factory() +let client +let query +let action + +beforeEach(async () => { + await Promise.all([ + factory.create('User', { role: 'user', email: 'user@example.org', password: '1234' }), + factory.create('User', { role: 'moderator', email: 'moderator@example.org', password: '1234' }) + ]) + await factory.authenticateAs({email: 'user@example.org', password: '1234'}) + await Promise.all([ + factory.create('Post', { title: 'Deleted post', deleted: true, disabled: false }), + factory.create('Post', { title: 'Disabled post', deleted: false, disabled: true}), + factory.create('Post', { title: 'Publicly visible post', deleted: false, disabled: false }) + ]) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('softDeleteMiddleware', () => { + describe('Post', () => { + action = () => { + return client.request(query) + } + + beforeEach(() => { + query = '{ Post { title } }' + }) + + describe('as user', () => { + beforeEach(async () => { + const headers = await login({ email: 'user@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('hides deleted or disabled posts', async () => { + const expected = {Post: [{title: 'Publicly visible post'}]} + await expect(action()).resolves.toEqual(expected) + }) + }) + + describe('as moderator', () => { + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('hides deleted or disabled posts', async () => { + const expected = {Post: [{title: 'Publicly visible post'}]} + await expect(action()).resolves.toEqual(expected) + }) + }) + + describe('filter (deleted: true)', () => { + beforeEach(() => { + query = '{ Post(deleted: true) { title } }' + }) + + describe('as user', () => { + beforeEach(async () => { + const headers = await login({ email: 'user@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorisation error', async () => { + await expect(action()).rejects.toThrow('Not Authorised!') + }) + }) + + describe('as moderator', () => { + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('shows deleted posts', async () => { + const expected = {Post: [{title: 'Deleted post'}]} + await expect(action()).resolves.toEqual(expected) + }) + }) + }) + + describe('filter (disabled: true)', () => { + beforeEach(() => { + query = '{ Post(disabled: true) { title } }' + }) + + describe('as user', () => { + beforeEach(async () => { + const headers = await login({ email: 'user@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorisation error', async () => { + await expect(action()).rejects.toThrow('Not Authorised!') + }) + }) + + describe('as moderator', () => { + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('shows disabled posts', async () => { + const expected = {Post: [{title: 'Disabled post'}]} + await expect(action()).resolves.toEqual(expected) + }) + }) + }) + }) +}) From 738ba4f51ca4dec9e61cb2372be64d767f4f3d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 27 Feb 2019 16:48:43 +0100 Subject: [PATCH 03/14] DRY softDeleteMiddleware --- src/middleware/softDeleteMiddleware.js | 43 +++++++++----------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/middleware/softDeleteMiddleware.js b/src/middleware/softDeleteMiddleware.js index 79e4a7d08..abc742bb3 100644 --- a/src/middleware/softDeleteMiddleware.js +++ b/src/middleware/softDeleteMiddleware.js @@ -1,38 +1,23 @@ +const normalize = (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, normalize(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, normalize(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, normalize(args), context, info) } } } From 911500a3bd3df99e9ca2cda3cddc2cd2743ad02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 27 Feb 2019 16:49:03 +0100 Subject: [PATCH 04/14] Don't override given { deleted, disabled } = args @appinteractive I guess this was done unintentionally? --- src/middleware/dateTimeMiddleware.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/middleware/dateTimeMiddleware.js b/src/middleware/dateTimeMiddleware.js index 97e6e2767..473dbf444 100644 --- a/src/middleware/dateTimeMiddleware.js +++ b/src/middleware/dateTimeMiddleware.js @@ -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 }, From f3ab671f2146b4bf597a6f4f6dda523f07048592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 28 Feb 2019 01:19:39 +0100 Subject: [PATCH 05/14] Soft delete middleware test passes --- src/middleware/permissionsMiddleware.js | 22 ++++++++++++---------- src/middleware/softDeleteMiddleware.js | 8 ++++---- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0c6723b4b..c070e536d 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -1,4 +1,4 @@ -import { rule, shield, allow } from 'graphql-shield' +import { rule, shield, allow, or } from 'graphql-shield' /* * TODO: implement @@ -11,36 +11,38 @@ const isAuthenticated = rule()(async (parent, args, ctx, info) => { 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 isModerator = rule()(async (parent, args, { user }, info) => { + return user && (user.role === 'moderator' || user.role === 'admin') +}) + const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => { return ctx.user.id === parent.id }) +const onlyEnabledContent = rule({ cache: 'strict' })(async (parent, args, ctx, info) => { + const { disabled, deleted } = args + return !(disabled || deleted) +}) + // Permissions const permissions = shield({ Query: { statistics: allow, - currentUser: allow - // fruits: and(isAuthenticated, or(isAdmin, isModerator)), - // customers: and(isAuthenticated, isAdmin) + currentUser: allow, + Post: or(onlyEnabledContent, isModerator) }, Mutation: { CreatePost: isAuthenticated, // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, report: isAuthenticated - // addFruitToBasket: isAuthenticated - // CreateUser: allow, }, User: { email: isMyOwn, password: isMyOwn } - // Post: isAuthenticated }) export default permissions diff --git a/src/middleware/softDeleteMiddleware.js b/src/middleware/softDeleteMiddleware.js index abc742bb3..bed7b6ca0 100644 --- a/src/middleware/softDeleteMiddleware.js +++ b/src/middleware/softDeleteMiddleware.js @@ -1,4 +1,4 @@ -const normalize = (args) => { +const setDefaults = (args) => { if (typeof args.deleted !== 'boolean') { args.deleted = false } @@ -11,13 +11,13 @@ const normalize = (args) => { export default { Query: { Post: (resolve, root, args, context, info) => { - return resolve(root, normalize(args), context, info) + return resolve(root, setDefaults(args), context, info) }, Comment: async (resolve, root, args, context, info) => { - return resolve(root, normalize(args), context, info) + return resolve(root, setDefaults(args), context, info) }, User: async (resolve, root, args, context, info) => { - return resolve(root, normalize(args), context, info) + return resolve(root, setDefaults(args), context, info) } } } From 8febf147cef2dd3eccb3b3331f47add0c02d6988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 28 Feb 2019 02:53:11 +0100 Subject: [PATCH 06/14] Fix lint --- src/middleware/softDeleteMiddleware.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/middleware/softDeleteMiddleware.spec.js b/src/middleware/softDeleteMiddleware.spec.js index 2e5dbe054..925a03ccc 100644 --- a/src/middleware/softDeleteMiddleware.spec.js +++ b/src/middleware/softDeleteMiddleware.spec.js @@ -9,13 +9,13 @@ let action beforeEach(async () => { await Promise.all([ - factory.create('User', { role: 'user', email: 'user@example.org', password: '1234' }), + factory.create('User', { role: 'user', email: 'user@example.org', password: '1234' }), factory.create('User', { role: 'moderator', email: 'moderator@example.org', password: '1234' }) ]) - await factory.authenticateAs({email: 'user@example.org', password: '1234'}) + await factory.authenticateAs({ email: 'user@example.org', password: '1234' }) await Promise.all([ - factory.create('Post', { title: 'Deleted post', deleted: true, disabled: false }), - factory.create('Post', { title: 'Disabled post', deleted: false, disabled: true}), + factory.create('Post', { title: 'Deleted post', deleted: true, disabled: false }), + factory.create('Post', { title: 'Disabled post', deleted: false, disabled: true }), factory.create('Post', { title: 'Publicly visible post', deleted: false, disabled: false }) ]) }) @@ -41,7 +41,7 @@ describe('softDeleteMiddleware', () => { }) it('hides deleted or disabled posts', async () => { - const expected = {Post: [{title: 'Publicly visible post'}]} + const expected = { Post: [{ title: 'Publicly visible post' }] } await expect(action()).resolves.toEqual(expected) }) }) @@ -53,7 +53,7 @@ describe('softDeleteMiddleware', () => { }) it('hides deleted or disabled posts', async () => { - const expected = {Post: [{title: 'Publicly visible post'}]} + const expected = { Post: [{ title: 'Publicly visible post' }] } await expect(action()).resolves.toEqual(expected) }) }) @@ -81,7 +81,7 @@ describe('softDeleteMiddleware', () => { }) it('shows deleted posts', async () => { - const expected = {Post: [{title: 'Deleted post'}]} + const expected = { Post: [{ title: 'Deleted post' }] } await expect(action()).resolves.toEqual(expected) }) }) @@ -110,7 +110,7 @@ describe('softDeleteMiddleware', () => { }) it('shows disabled posts', async () => { - const expected = {Post: [{title: 'Disabled post'}]} + const expected = { Post: [{ title: 'Disabled post' }] } await expect(action()).resolves.toEqual(expected) }) }) From 5e592f666b78ffe12581639c55635426a6d4bb85 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 28 Feb 2019 15:00:45 -0300 Subject: [PATCH 07/14] Write unit test badge creation - for unauthenticated - for authenticated admin Co-authored-by: Wolfgang Huss --- src/middleware/permissionsMiddleware.js | 3 +- src/resolvers/badges.spec.js | 85 +++++++++++++++++++++++++ src/seed/factories/badges.js | 18 +++--- 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 src/resolvers/badges.spec.js diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0c6723b4b..928137847 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -32,7 +32,8 @@ const permissions = shield({ CreatePost: isAuthenticated, // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, - report: isAuthenticated + report: isAuthenticated, + CreateBadge: isAuthenticated // addFruitToBasket: isAuthenticated // CreateUser: allow, }, diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js new file mode 100644 index 000000000..abaa85995 --- /dev/null +++ b/src/resolvers/badges.spec.js @@ -0,0 +1,85 @@ +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() + +describe('report', () => { + beforeEach(async () => { + await factory.create('User', { + email: 'user@example.org', + password: '1234' + }) + await factory.create('User', { + id: 'u2', + name: 'moderator', + role: 'moderator', + email: 'moderator@example.org' + }) + await factory.create('User', { + id: 'u3', + name: 'admin', + role: 'moderator', + email: 'admin@example.org' + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + const params = { + id: 'b1', + key: 'indiegogo_en_racoon', + type: 'crowdfunding', + status: 'permanent', + icon: '/img/badges/indiegogo_en_racoon.svg' + } + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + let { id, key, type, status, icon } = params + await expect( + client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`) + ).rejects.toThrow('Not Authorised') + }) + + describe('authenticated admin', () => { + let headers + let response + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + response = await client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + ) + }) + it('creates a badge', () => { + let { id } = response.CreateBadge + expect(response).toEqual({ + CreateBadge: { id } + }) + }) + }) + }) +}) diff --git a/src/seed/factories/badges.js b/src/seed/factories/badges.js index b34442521..e24a67c21 100644 --- a/src/seed/factories/badges.js +++ b/src/seed/factories/badges.js @@ -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 } + } ` } From ec648113798b91d8bf96ffcc41c164a468b1ff2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 1 Mar 2019 13:17:07 +0100 Subject: [PATCH 08/14] Add to .gitignore `.DS_Store` --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b909223f8..81a29c8e6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ coverage.lcov .nyc_output/ public/uploads/* !.gitkeep + +# Apple macOS folder attribute file +.DS_Store \ No newline at end of file From 6271c6e8a37fd0cd4067e575aeec803d77505f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 1 Mar 2019 13:38:14 +0100 Subject: [PATCH 09/14] Add to .gitignore `.DS_Store` (#196) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b909223f8..81a29c8e6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ coverage.lcov .nyc_output/ public/uploads/* !.gitkeep + +# Apple macOS folder attribute file +.DS_Store \ No newline at end of file From 6937c60ef8de53458a8ea09e0ec311594462b217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 1 Mar 2019 15:49:11 +0100 Subject: [PATCH 10/14] Only admins are allowed to create badges --- src/middleware/permissionsMiddleware.js | 10 +++- src/resolvers/badges.spec.js | 79 ++++++++++++++++--------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 928137847..d2dc45094 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -16,8 +16,12 @@ const isModerator = rule()(async (parent, args, ctx, info) => { }) */ -const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => { - return ctx.user.id === parent.id +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 }) // Permissions @@ -33,7 +37,7 @@ const permissions = shield({ // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, report: isAuthenticated, - CreateBadge: isAuthenticated + CreateBadge: isAdmin // addFruitToBasket: isAuthenticated // CreateUser: allow, }, diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index abaa85995..3574dae6d 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -4,22 +4,21 @@ import { host, login } from '../jest/helpers' const factory = Factory() -describe('report', () => { +describe('Badge', () => { beforeEach(async () => { await factory.create('User', { email: 'user@example.org', + role: 'user', password: '1234' }) await factory.create('User', { id: 'u2', - name: 'moderator', role: 'moderator', email: 'moderator@example.org' }) await factory.create('User', { id: 'u3', - name: 'admin', - role: 'moderator', + role: 'admin', email: 'admin@example.org' }) }) @@ -54,32 +53,56 @@ describe('report', () => { }`) ).rejects.toThrow('Not Authorised') }) + }) - describe('authenticated admin', () => { - let headers - let response - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - response = await client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - ) - }) - it('creates a badge', () => { - let { id } = response.CreateBadge - expect(response).toEqual({ - CreateBadge: { id } - }) + describe('authenticated admin', () => { + let client + let headers + let response + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + response = await client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + ) + }) + it('creates a badge', () => { + let { id } = response.CreateBadge + expect(response).toEqual({ + CreateBadge: { id } }) }) }) + + describe('authenticated moderator', () => { + let client + let headers + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('throws authorization error', async () => { + await expect(client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + )).rejects.toThrow('Not Authorised') + }) + }) }) From f25708875a8c75f36f3b88f20f7e302ee53261ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:01:50 +0100 Subject: [PATCH 11/14] Refactor badges test CC @Tirokk @grenzfrequence * the top level block should correspond with the name of the resolver * the block below should be `CreatePost` or `UpdatePost` * the arguments of client.request are `query/mutation`, `variables` but you passed in the `headers` which should go into `new GraphQlClient(host, options)` * re-use the very same mutation to avoid bugs in the tests * use `await expect(someAsyncMethod).resolves.toEqual(expected)` style for extra test assurance --- src/resolvers/badges.spec.js | 128 ++++++++++++++++------------------- 1 file changed, 57 insertions(+), 71 deletions(-) diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index 3574dae6d..f6d8c06dc 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -4,7 +4,7 @@ import { host, login } from '../jest/helpers' const factory = Factory() -describe('Badge', () => { +describe('badges', () => { beforeEach(async () => { await factory.create('User', { email: 'user@example.org', @@ -27,82 +27,68 @@ describe('Badge', () => { await factory.cleanDatabase() }) - const params = { - id: 'b1', - key: 'indiegogo_en_racoon', - type: 'crowdfunding', - status: 'permanent', - icon: '/img/badges/indiegogo_en_racoon.svg' - } + describe('CreateBadge', () => { + const params = { + id: 'b1', + key: 'indiegogo_en_racoon', + type: 'crowdfunding', + status: 'permanent', + icon: '/img/badges/indiegogo_en_racoon.svg' + } - describe('unauthenticated', () => { - let client + const mutation = ` + mutation( + $id: ID + $key: String! + $type: BadgeTypeEnum! + $status: BadgeStatusEnum! + $icon: String! + ) { + CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) { + id + } + } + ` - it('throws authorization error', async () => { - client = new GraphQLClient(host) - let { id, key, type, status, icon } = params - await expect( - client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`) - ).rejects.toThrow('Not Authorised') + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, params) + ).rejects.toThrow('Not Authorised') + }) }) - }) - describe('authenticated admin', () => { - let client - let headers - let response - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - response = await client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - ) + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('creates a badge', async () => { + const expected = { + CreateBadge: { + id: 'b1' + } + } + await expect(client.request(mutation, params)).resolves.toEqual(expected) + }) }) - it('creates a badge', () => { - let { id } = response.CreateBadge - expect(response).toEqual({ - CreateBadge: { id } + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, params) + ).rejects.toThrow('Not Authorised') }) }) }) - - describe('authenticated moderator', () => { - let client - let headers - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'moderator@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) - it('throws authorization error', async () => { - await expect(client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - )).rejects.toThrow('Not Authorised') - }) - }) }) From fb2b407be029f8e08cfaaf21be72cc6c088aff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:32:29 +0100 Subject: [PATCH 12/14] Extend @Tirokk 's test to Create and Update --- src/resolvers/badges.spec.js | 141 +++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index f6d8c06dc..e38f54381 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -28,7 +28,7 @@ describe('badges', () => { }) describe('CreateBadge', () => { - const params = { + const variables = { id: 'b1', key: 'indiegogo_en_racoon', type: 'crowdfunding', @@ -45,7 +45,11 @@ describe('badges', () => { $icon: String! ) { CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) { - id + id, + key, + type, + status, + icon } } ` @@ -56,7 +60,7 @@ describe('badges', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) await expect( - client.request(mutation, params) + client.request(mutation, variables) ).rejects.toThrow('Not Authorised') }) }) @@ -70,10 +74,14 @@ describe('badges', () => { it('creates a badge', async () => { const expected = { CreateBadge: { - id: 'b1' + icon: '/img/badges/indiegogo_en_racoon.svg', + id: 'b1', + key: 'indiegogo_en_racoon', + status: 'permanent', + type: 'crowdfunding' } } - await expect(client.request(mutation, params)).resolves.toEqual(expected) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) }) @@ -86,9 +94,130 @@ describe('badges', () => { it('throws authorization error', async () => { await expect( - client.request(mutation, params) + client.request(mutation, variables) ).rejects.toThrow('Not Authorised') }) }) }) + + describe('UpdateBadge', () => { + beforeEach(async () => { + await factory.authenticateAs({ email: 'admin@example.org', password: '1234' }) + await factory.create('Badge', { id: 'b1' }) + }) + const variables = { + id: 'b1', + key: 'whatever' + } + + const mutation = ` + mutation($id: ID!, $key: String!) { + UpdateBadge(id: $id, key: $key) { + id + key + } + } + ` + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('updates a badge', async () => { + const expected = { + UpdateBadge: { + id: 'b1', + key: 'whatever' + } + } + await expect(client.request(mutation, variables)).resolves.toEqual(expected) + }) + }) + }) + + describe('DeleteBadge', () => { + beforeEach(async () => { + await factory.authenticateAs({ email: 'admin@example.org', password: '1234' }) + await factory.create('Badge', { id: 'b1' }) + }) + const variables = { + id: 'b1' + } + + const mutation = ` + mutation($id: ID!) { + DeleteBadge(id: $id) { + id + } + } + ` + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('deletes a badge', async () => { + const expected = { + DeleteBadge: { + id: 'b1' + } + } + await expect(client.request(mutation, variables)).resolves.toEqual(expected) + }) + }) + }) }) From 8d1eb6026a8b34ad82c22f6ab8d0c29dae8b4c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:35:08 +0100 Subject: [PATCH 13/14] Let all tests pass :green_heart: --- src/middleware/permissionsMiddleware.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index d2dc45094..f10b30cb6 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -37,7 +37,9 @@ const permissions = shield({ // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, report: isAuthenticated, - CreateBadge: isAdmin + CreateBadge: isAdmin, + UpdateBadge: isAdmin, + DeleteBadge: isAdmin // addFruitToBasket: isAuthenticated // CreateUser: allow, }, From b23380d5938369f2f6cead131e2b6dc7519e1ecf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Mon, 4 Mar 2019 04:21:10 +0000 Subject: [PATCH 14/14] Bump eslint from 5.14.1 to 5.15.0 Bumps [eslint](https://github.com/eslint/eslint) from 5.14.1 to 5.15.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v5.14.1...v5.15.0) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 570841630..cefa46953 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "babel-eslint": "~10.0.1", "babel-jest": "~24.1.0", "chai": "~4.2.0", - "eslint": "~5.14.1", + "eslint": "~5.15.0", "eslint-config-standard": "~12.0.0", "eslint-plugin-import": "~2.16.0", "eslint-plugin-jest": "~22.3.0", diff --git a/yarn.lock b/yarn.lock index fd3c53297..eb0f3efd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2564,10 +2564,10 @@ eslint-scope@3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== +eslint-scope@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e" + integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -2582,10 +2582,10 @@ eslint-visitor-keys@^1.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== -eslint@~5.14.1: - version "5.14.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.14.1.tgz#490a28906be313685c55ccd43a39e8d22efc04ba" - integrity sha512-CyUMbmsjxedx8B0mr79mNOqetvkbij/zrXnFeK2zc3pGRn3/tibjiNAv/3UxFEyfMDjh+ZqTrJrEGBFiGfD5Og== +eslint@~5.15.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.15.0.tgz#f313a2f7c7628d39adeefdba4a9c41f842012c9e" + integrity sha512-xwG7SS5JLeqkiR3iOmVgtF8Y6xPdtr6AAsN6ph7Q6R/fv+3UlKYoika8SmNzmb35qdRF+RfTY35kMEdtbi+9wg== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.9.1" @@ -2593,7 +2593,7 @@ eslint@~5.14.1: cross-spawn "^6.0.5" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^4.0.0" + eslint-scope "^4.0.2" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" espree "^5.0.1"