From ed51c4a786a3c548220953b755e5c20036457f27 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 4 Mar 2021 19:00:53 +0100 Subject: [PATCH] permissions for Signup with invite code --- .../src/middleware/permissionsMiddleware.js | 10 +- .../middleware/permissionsMiddleware.spec.js | 132 +++++++++++++++++- backend/src/schema/resolvers/inviteCodes.js | 4 +- .../resolvers/transactions/inviteCodes.js | 2 +- 4 files changed, 143 insertions(+), 5 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 1bca3bb26..25598a30f 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -1,6 +1,7 @@ import { rule, shield, deny, allow, or } from 'graphql-shield' import { getNeode } from '../db/neo4j' import CONFIG from '../config' +import { validateInviteCode } from '../schema/resolvers/transactions/inviteCodes' const debug = !!CONFIG.DEBUG const allowExternalErrors = true @@ -89,6 +90,13 @@ const noEmailFilter = rule({ const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION) +const inviteRegistration = rule()(async (_parent, args, { user, driver }) => { + if (!CONFIG.INVITE_REGISTRATION) return false + const { inviteCode } = args + const session = driver.session() + return validateInviteCode(session, inviteCode) +}) + // Permissions export default shield( { @@ -128,7 +136,7 @@ export default shield( Mutation: { '*': deny, login: allow, - Signup: or(publicRegistration, isAdmin), + Signup: or(publicRegistration, inviteRegistration, isAdmin), SignupVerification: allow, UpdateUser: onlyYourself, CreatePost: isAuthenticated, diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js index 775533867..8f311e8c2 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.js +++ b/backend/src/middleware/permissionsMiddleware.spec.js @@ -3,11 +3,13 @@ import createServer from '../server' import Factory, { cleanDatabase } from '../db/factories' import { gql } from '../helpers/jest' import { getDriver, getNeode } from '../db/neo4j' +import CONFIG from '../config' const instance = getNeode() const driver = getDriver() -let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator +let query, mutate, variables +let authenticatedUser, owner, anotherRegularUser, administrator, moderator describe('authorization', () => { beforeAll(async () => { @@ -20,6 +22,7 @@ describe('authorization', () => { }), }) query = createTestClient(server).query + mutate = createTestClient(server).mutate }) afterEach(async () => { @@ -159,5 +162,132 @@ describe('authorization', () => { }) }) }) + + describe('access Signup', () => { + const signupMutation = gql` + mutation($email: String!, $inviteCode: String) { + Signup(email: $email, inviteCode: $inviteCode) { + email + } + } + ` + + describe('admin invite only', () => { + beforeEach(async () => { + variables = { + email: 'some@email.org', + inviteCode: 'AAAAAA', + } + CONFIG.INVITE_REGISTRATION = false + CONFIG.PUBLIC_REGISTRATION = false + await Factory.build('inviteCode', { + code: 'AAAAAA', + }) + }) + + describe('as user', () => { + beforeEach(async () => { + authenticatedUser = await anotherRegularUser.toJson() + }) + + it('denies permission', async () => { + await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { Signup: null }, + }) + }) + }) + + describe('as admin', () => { + beforeEach(async () => { + authenticatedUser = await administrator.toJson() + }) + + it('returns an email', async () => { + await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ + errors: undefined, + data: { + Signup: { email: 'some@email.org' }, + }, + }) + }) + }) + }) + + describe('public registration', () => { + beforeEach(async () => { + variables = { + email: 'some@email.org', + inviteCode: 'AAAAAA', + } + CONFIG.INVITE_REGISTRATION = false + CONFIG.PUBLIC_REGISTRATION = true + await Factory.build('inviteCode', { + code: 'AAAAAA', + }) + }) + + describe('as anyone', () => { + beforeEach(async () => { + authenticatedUser = null + }) + + it('returns an email', async () => { + await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ + errors: undefined, + data: { + Signup: { email: 'some@email.org' }, + }, + }) + }) + }) + }) + + describe('invite registration', () => { + beforeEach(async () => { + CONFIG.INVITE_REGISTRATION = true + CONFIG.PUBLIC_REGISTRATION = false + await Factory.build('inviteCode', { + code: 'AAAAAA', + }) + }) + + describe('as anyone with valid invite code', () => { + beforeEach(async () => { + variables = { + email: 'some@email.org', + inviteCode: 'AAAAAA', + } + authenticatedUser = null + }) + + it('returns an email', async () => { + await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ + errors: undefined, + data: { + Signup: { email: 'some@email.org' }, + }, + }) + }) + }) + + describe('as anyone without valid invite', () => { + beforeEach(async () => { + variables = { + email: 'some@email.org', + inviteCode: 'no valid invite code', + } + authenticatedUser = null + }) + + it('denies permission', async () => { + await expect(mutate({ mutation: signupMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { Signup: null }, + }) + }) + }) + }) + }) }) }) diff --git a/backend/src/schema/resolvers/inviteCodes.js b/backend/src/schema/resolvers/inviteCodes.js index 35c8c6ece..6bb1401cd 100644 --- a/backend/src/schema/resolvers/inviteCodes.js +++ b/backend/src/schema/resolvers/inviteCodes.js @@ -39,8 +39,8 @@ export default { const { code } = args const session = context.driver.session() if (!code) return false - return await validateInviteCode(session, code) - } + return validateInviteCode(session, code) + }, }, Mutation: { GenerateInviteCode: async (_parent, args, context, _resolveInfo) => { diff --git a/backend/src/schema/resolvers/transactions/inviteCodes.js b/backend/src/schema/resolvers/transactions/inviteCodes.js index 0ae2e9ad3..554b15f86 100644 --- a/backend/src/schema/resolvers/transactions/inviteCodes.js +++ b/backend/src/schema/resolvers/transactions/inviteCodes.js @@ -11,7 +11,7 @@ export async function validateInviteCode(session, inviteCode) { inviteCode, }, ) - return result.records.map((record) => record.get('result')) + return result.records.map((record) => record.get('result')) }) try { const txResult = await readTxResultPromise