mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #2333 from Human-Connection/remove_obsolete_invitation_code
refactor: Remove obsolete code about invitation codes
This commit is contained in:
commit
2e54a43417
@ -1,4 +1,4 @@
|
|||||||
import { rule, shield, deny, allow, and, or, not } from 'graphql-shield'
|
import { rule, shield, deny, allow, or } from 'graphql-shield'
|
||||||
import { neode } from '../bootstrap/neo4j'
|
import { neode } from '../bootstrap/neo4j'
|
||||||
import CONFIG from '../config'
|
import CONFIG from '../config'
|
||||||
|
|
||||||
@ -41,27 +41,6 @@ const isMySocialMedia = rule({
|
|||||||
return socialMedia.ownedBy.node.id === user.id
|
return socialMedia.ownedBy.node.id === user.id
|
||||||
})
|
})
|
||||||
|
|
||||||
const invitationLimitReached = rule({
|
|
||||||
cache: 'no_cache',
|
|
||||||
})(async (parent, args, { user, driver }) => {
|
|
||||||
const session = driver.session()
|
|
||||||
try {
|
|
||||||
const result = await session.run(
|
|
||||||
`
|
|
||||||
MATCH (user:User {id:$id})-[:GENERATED]->(i:InvitationCode)
|
|
||||||
RETURN COUNT(i) >= 3 as limitReached
|
|
||||||
`,
|
|
||||||
{ id: user.id },
|
|
||||||
)
|
|
||||||
const [limitReached] = result.records.map(record => {
|
|
||||||
return record.get('limitReached')
|
|
||||||
})
|
|
||||||
return limitReached
|
|
||||||
} finally {
|
|
||||||
session.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const isAuthor = rule({
|
const isAuthor = rule({
|
||||||
cache: 'no_cache',
|
cache: 'no_cache',
|
||||||
})(async (_parent, args, { user, driver }) => {
|
})(async (_parent, args, { user, driver }) => {
|
||||||
@ -129,7 +108,6 @@ export default shield(
|
|||||||
SignupByInvitation: allow,
|
SignupByInvitation: allow,
|
||||||
Signup: or(publicRegistration, isAdmin),
|
Signup: or(publicRegistration, isAdmin),
|
||||||
SignupVerification: allow,
|
SignupVerification: allow,
|
||||||
CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)),
|
|
||||||
UpdateUser: onlyYourself,
|
UpdateUser: onlyYourself,
|
||||||
CreatePost: isAuthenticated,
|
CreatePost: isAuthenticated,
|
||||||
UpdatePost: isAuthor,
|
UpdatePost: isAuthor,
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
export default {
|
export default {
|
||||||
Badge: require('./Badge.js'),
|
Badge: require('./Badge.js'),
|
||||||
User: require('./User.js'),
|
User: require('./User.js'),
|
||||||
InvitationCode: require('./InvitationCode.js'),
|
|
||||||
EmailAddress: require('./EmailAddress.js'),
|
EmailAddress: require('./EmailAddress.js'),
|
||||||
UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js'),
|
UnverifiedEmailAddress: require('./UnverifiedEmailAddress.js'),
|
||||||
SocialMedia: require('./SocialMedia.js'),
|
SocialMedia: require('./SocialMedia.js'),
|
||||||
|
|||||||
@ -10,7 +10,6 @@ export default makeAugmentedSchema({
|
|||||||
exclude: [
|
exclude: [
|
||||||
'Badge',
|
'Badge',
|
||||||
'Embed',
|
'Embed',
|
||||||
'InvitationCode',
|
|
||||||
'EmailAddress',
|
'EmailAddress',
|
||||||
'Notfication',
|
'Notfication',
|
||||||
'Statistics',
|
'Statistics',
|
||||||
|
|||||||
@ -10,25 +10,6 @@ const instance = neode()
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
CreateInvitationCode: async (_parent, args, context, _resolveInfo) => {
|
|
||||||
args.token = generateNonce()
|
|
||||||
const {
|
|
||||||
user: { id: userId },
|
|
||||||
} = context
|
|
||||||
let response
|
|
||||||
try {
|
|
||||||
const [user, invitationCode] = await Promise.all([
|
|
||||||
instance.find('User', userId),
|
|
||||||
instance.create('InvitationCode', args),
|
|
||||||
])
|
|
||||||
await invitationCode.relateTo(user, 'generatedBy')
|
|
||||||
response = invitationCode.toJson()
|
|
||||||
response.generatedBy = user.toJson()
|
|
||||||
} catch (e) {
|
|
||||||
throw new UserInputError(e)
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
},
|
|
||||||
Signup: async (_parent, args, context) => {
|
Signup: async (_parent, args, context) => {
|
||||||
args.nonce = generateNonce()
|
args.nonce = generateNonce()
|
||||||
args.email = normalizeEmail(args.email)
|
args.email = normalizeEmail(args.email)
|
||||||
@ -41,35 +22,6 @@ export default {
|
|||||||
throw new UserInputError(e.message)
|
throw new UserInputError(e.message)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SignupByInvitation: async (_parent, args, context) => {
|
|
||||||
const { token } = args
|
|
||||||
args.nonce = generateNonce()
|
|
||||||
args.email = normalizeEmail(args.email)
|
|
||||||
let emailAddress = await existingEmailAddress({ args, context })
|
|
||||||
if (emailAddress) return emailAddress
|
|
||||||
try {
|
|
||||||
const result = await instance.cypher(
|
|
||||||
`
|
|
||||||
MATCH (invitationCode:InvitationCode {token:{token}})
|
|
||||||
WHERE NOT (invitationCode)-[:ACTIVATED]->()
|
|
||||||
RETURN invitationCode
|
|
||||||
`,
|
|
||||||
{ token },
|
|
||||||
)
|
|
||||||
const validInvitationCode = instance.hydrateFirst(
|
|
||||||
result,
|
|
||||||
'invitationCode',
|
|
||||||
instance.model('InvitationCode'),
|
|
||||||
)
|
|
||||||
if (!validInvitationCode)
|
|
||||||
throw new UserInputError('Invitation code already used or does not exist.')
|
|
||||||
emailAddress = await instance.create('EmailAddress', args)
|
|
||||||
await validInvitationCode.relateTo(emailAddress, 'activated')
|
|
||||||
return emailAddress.toJson()
|
|
||||||
} catch (e) {
|
|
||||||
throw new UserInputError(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
SignupVerification: async (_parent, args) => {
|
SignupVerification: async (_parent, args) => {
|
||||||
const { termsAndConditionsAgreedVersion } = args
|
const { termsAndConditionsAgreedVersion } = args
|
||||||
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
|
||||||
|
|||||||
@ -9,7 +9,6 @@ const neode = getNeode()
|
|||||||
|
|
||||||
let mutate
|
let mutate
|
||||||
let authenticatedUser
|
let authenticatedUser
|
||||||
let user
|
|
||||||
let variables
|
let variables
|
||||||
const driver = getDriver()
|
const driver = getDriver()
|
||||||
|
|
||||||
@ -34,243 +33,6 @@ afterEach(async () => {
|
|||||||
await factory.cleanDatabase()
|
await factory.cleanDatabase()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('CreateInvitationCode', () => {
|
|
||||||
const mutation = gql`
|
|
||||||
mutation {
|
|
||||||
CreateInvitationCode {
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
authenticatedUser = null
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws Authorization error', async () => {
|
|
||||||
await expect(mutate({ mutation })).resolves.toMatchObject({
|
|
||||||
errors: [{ message: 'Not Authorised!' }],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('authenticated', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
user = await factory.create('User', {
|
|
||||||
id: 'i123',
|
|
||||||
name: 'Inviter',
|
|
||||||
email: 'inviter@example.org',
|
|
||||||
password: '1234',
|
|
||||||
termsAndConditionsAgreedVersion: null,
|
|
||||||
})
|
|
||||||
authenticatedUser = await user.toJson()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('resolves', async () => {
|
|
||||||
await expect(mutate({ mutation })).resolves.toMatchObject({
|
|
||||||
data: { CreateInvitationCode: { token: expect.any(String) } },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('creates an InvitationCode with a `createdAt` attribute', async () => {
|
|
||||||
await mutate({ mutation })
|
|
||||||
const codes = await neode.all('InvitationCode')
|
|
||||||
const invitation = await codes.first().toJson()
|
|
||||||
expect(invitation.createdAt).toBeTruthy()
|
|
||||||
expect(Date.parse(invitation.createdAt)).toEqual(expect.any(Number))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('relates inviting User to InvitationCode', async () => {
|
|
||||||
await mutate({ mutation })
|
|
||||||
const result = await neode.cypher(
|
|
||||||
'MATCH(code:InvitationCode)<-[:GENERATED]-(user:User) RETURN user',
|
|
||||||
)
|
|
||||||
const inviter = neode.hydrateFirst(result, 'user', neode.model('User'))
|
|
||||||
await expect(inviter.toJson()).resolves.toEqual(expect.objectContaining({ name: 'Inviter' }))
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('who has invited a lot of users already', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await Promise.all([mutate({ mutation }), mutate({ mutation }), mutate({ mutation })])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('as ordinary `user`', () => {
|
|
||||||
it('throws `Not Authorised` because of maximum number of invitations', async () => {
|
|
||||||
await expect(mutate({ mutation })).resolves.toMatchObject({
|
|
||||||
errors: [{ message: 'Not Authorised!' }],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('creates no additional invitation codes', async () => {
|
|
||||||
await mutate({ mutation })
|
|
||||||
const invitationCodes = await neode.all('InvitationCode')
|
|
||||||
await expect(invitationCodes.toJson()).resolves.toHaveLength(3)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('as a strong donator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// What is the setup?
|
|
||||||
})
|
|
||||||
|
|
||||||
it.todo('can invite more people')
|
|
||||||
// it('can invite more people', async () => {
|
|
||||||
// await action()
|
|
||||||
// const invitationQuery = `{ User { createdAt } }`
|
|
||||||
// const { User: users } = await client.request(invitationQuery )
|
|
||||||
// expect(users).toHaveLength(3 + 1 + 1)
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('SignupByInvitation', () => {
|
|
||||||
const mutation = gql`
|
|
||||||
mutation($email: String!, $token: String!) {
|
|
||||||
SignupByInvitation(email: $email, token: $token) {
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
describe('with valid email but invalid InvitationCode', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
email: 'any-email@example.org',
|
|
||||||
token: 'wut?',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws UserInputError', async () => {
|
|
||||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
|
||||||
errors: [{ message: 'UserInputError: Invitation code already used or does not exist.' }],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with valid InvitationCode', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
const inviter = await factory.create('User', {
|
|
||||||
name: 'Inviter',
|
|
||||||
email: 'inviter@example.org',
|
|
||||||
password: '1234',
|
|
||||||
})
|
|
||||||
authenticatedUser = await inviter.toJson()
|
|
||||||
const invitationMutation = gql`
|
|
||||||
mutation {
|
|
||||||
CreateInvitationCode {
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
CreateInvitationCode: { token },
|
|
||||||
},
|
|
||||||
} = await mutate({ mutation: invitationMutation })
|
|
||||||
authenticatedUser = null
|
|
||||||
variables = {
|
|
||||||
...variables,
|
|
||||||
token,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given an invalid email', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
variables = { ...variables, email: 'someuser' }
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws `email is not a valid email`', async () => {
|
|
||||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
|
||||||
errors: [{ message: expect.stringContaining('"email" must be a valid email') }],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('creates no additional EmailAddress node', async () => {
|
|
||||||
let emailAddresses = await neode.all('EmailAddress')
|
|
||||||
emailAddresses = await emailAddresses.toJson()
|
|
||||||
expect(emailAddresses).toHaveLength(1)
|
|
||||||
await mutate({ mutation, variables })
|
|
||||||
emailAddresses = await neode.all('EmailAddress')
|
|
||||||
emailAddresses = await emailAddresses.toJson()
|
|
||||||
expect(emailAddresses).toHaveLength(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('given a valid email', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
variables = { ...variables, email: 'someUser@example.org' }
|
|
||||||
})
|
|
||||||
|
|
||||||
it('resolves', async () => {
|
|
||||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
|
||||||
data: { SignupByInvitation: { email: 'someuser@example.org' } },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('creates a EmailAddress node', () => {
|
|
||||||
it('with a `createdAt` attribute', async () => {
|
|
||||||
await mutate({ mutation, variables })
|
|
||||||
let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' })
|
|
||||||
emailAddress = await emailAddress.toJson()
|
|
||||||
expect(emailAddress.createdAt).toBeTruthy()
|
|
||||||
expect(Date.parse(emailAddress.createdAt)).toEqual(expect.any(Number))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('with a cryptographic `nonce`', async () => {
|
|
||||||
await mutate({ mutation, variables })
|
|
||||||
let emailAddress = await neode.first('EmailAddress', { email: 'someuser@example.org' })
|
|
||||||
emailAddress = await emailAddress.toJson()
|
|
||||||
expect(emailAddress.nonce).toEqual(expect.any(String))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('connects inviter through invitation code', async () => {
|
|
||||||
await mutate({ mutation, variables })
|
|
||||||
const result = await neode.cypher(
|
|
||||||
'MATCH(inviter:User)-[:GENERATED]->(:InvitationCode)-[:ACTIVATED]->(email:EmailAddress {email: {email}}) RETURN inviter',
|
|
||||||
{ email: 'someuser@example.org' },
|
|
||||||
)
|
|
||||||
const inviter = neode.hydrateFirst(result, 'inviter', neode.model('User'))
|
|
||||||
await expect(inviter.toJson()).resolves.toEqual(
|
|
||||||
expect.objectContaining({ name: 'Inviter' }),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('using the same InvitationCode twice', () => {
|
|
||||||
it('rejects because codes can be used only once', async () => {
|
|
||||||
await mutate({ mutation, variables })
|
|
||||||
variables = { ...variables, email: 'yetanotheremail@example.org' }
|
|
||||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
|
||||||
errors: [
|
|
||||||
{ message: 'UserInputError: Invitation code already used or does not exist.' },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('if a user account with the given email already exists', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await factory.create('User', { email: 'someuser@example.org' })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws unique violation error', async () => {
|
|
||||||
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
|
|
||||||
errors: [{ message: 'A user account with this email already exists.' }],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('if the EmailAddress already exists but without user account', () => {
|
|
||||||
it.todo('shall we re-send the registration email?')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Signup', () => {
|
describe('Signup', () => {
|
||||||
const mutation = gql`
|
const mutation = gql`
|
||||||
mutation($email: String!) {
|
mutation($email: String!) {
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
type InvitationCode {
|
|
||||||
id: ID!
|
|
||||||
token: String
|
|
||||||
generatedBy: User @relation(name: "GENERATED", direction: "IN")
|
|
||||||
createdAt: String
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mutation {
|
|
||||||
CreateInvitationCode: InvitationCode
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user