mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #1044 from Human-Connection/refactor_email_address
Refactoring: Split User and Email
This commit is contained in:
commit
7de73f861e
@ -8,5 +8,6 @@ module.exports = {
|
||||
relationship: 'BELONGS_TO',
|
||||
target: 'User',
|
||||
direction: 'out',
|
||||
eager: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ module.exports = {
|
||||
id: { type: 'string', primary: true, default: uuid }, // TODO: should be type: 'uuid' but simplified for our tests
|
||||
actorId: { type: 'string', allow: [null] },
|
||||
name: { type: 'string', min: 3 },
|
||||
email: { type: 'string', lowercase: true, email: true },
|
||||
slug: 'string',
|
||||
encryptedPassword: 'string',
|
||||
avatar: { type: 'string', allow: [null] },
|
||||
|
||||
@ -5,7 +5,7 @@ export async function createPasswordReset(options) {
|
||||
const { driver, code, email, issuedAt = new Date() } = options
|
||||
const session = driver.session()
|
||||
const cypher = `
|
||||
MATCH (u:User) WHERE u.email = $email
|
||||
MATCH (u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email:$email})
|
||||
CREATE(pr:PasswordReset {code: $code, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||
MERGE (u)-[:REQUESTED]->(pr)
|
||||
RETURN u
|
||||
@ -35,7 +35,7 @@ export default {
|
||||
const encryptedNewPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
const cypher = `
|
||||
MATCH (pr:PasswordReset {code: $code})
|
||||
MATCH (u:User {email: $email})-[:REQUESTED]->(pr)
|
||||
MATCH (e:EmailAddress {email: $email})<-[:PRIMARY_EMAIL]-(u:User)-[:REQUESTED]->(pr)
|
||||
WHERE duration.between(pr.issuedAt, datetime()).days <= 0 AND pr.usedAt IS NULL
|
||||
SET pr.usedAt = datetime()
|
||||
SET u.encryptedPassword = $encryptedNewPassword
|
||||
|
||||
@ -12,8 +12,8 @@ const instance = neode()
|
||||
*/
|
||||
const checkEmailDoesNotExist = async ({ email }) => {
|
||||
email = email.toLowerCase()
|
||||
const users = await instance.all('User', { email })
|
||||
if (users.length > 0) throw new UserInputError('User account with this email already exists.')
|
||||
const emails = await instance.all('EmailAddress', { email })
|
||||
if (emails.length > 0) throw new UserInputError('User account with this email already exists.')
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@ -166,11 +166,12 @@ describe('SignupByInvitation', () => {
|
||||
await expect(action()).rejects.toThrow('"email" must be a valid email')
|
||||
})
|
||||
|
||||
it('creates no EmailAddress node', async done => {
|
||||
it('creates no additional EmailAddress node', async done => {
|
||||
try {
|
||||
await action()
|
||||
} catch (e) {
|
||||
const emailAddresses = await instance.all('EmailAddress')
|
||||
let emailAddresses = await instance.all('EmailAddress')
|
||||
emailAddresses = await emailAddresses.toJson
|
||||
expect(emailAddresses).toHaveLength(0)
|
||||
done()
|
||||
}
|
||||
@ -191,16 +192,16 @@ describe('SignupByInvitation', () => {
|
||||
describe('creates a EmailAddress node', () => {
|
||||
it('with a `createdAt` attribute', async () => {
|
||||
await action()
|
||||
const emailAddresses = await instance.all('EmailAddress')
|
||||
const emailAddress = await emailAddresses.first().toJson()
|
||||
let emailAddress = await instance.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 action()
|
||||
const emailAddresses = await instance.all('EmailAddress')
|
||||
const emailAddress = await emailAddresses.first().toJson()
|
||||
let emailAddress = await instance.first('EmailAddress', { email: 'someuser@example.org' })
|
||||
emailAddress = await emailAddress.toJson()
|
||||
expect(emailAddress.nonce).toEqual(expect.any(String))
|
||||
})
|
||||
|
||||
@ -220,6 +221,7 @@ describe('SignupByInvitation', () => {
|
||||
it('rejects because codes can be used only once', async done => {
|
||||
await action()
|
||||
try {
|
||||
variables.email = 'yetanotheremail@example.org'
|
||||
await action()
|
||||
} catch (e) {
|
||||
expect(e.message).toMatch(/Invitation code already used/)
|
||||
@ -282,8 +284,8 @@ describe('Signup', () => {
|
||||
|
||||
it('creates a Signup with a cryptographic `nonce`', async () => {
|
||||
await action()
|
||||
const emailAddresses = await instance.all('EmailAddress')
|
||||
const emailAddress = await emailAddresses.first().toJson()
|
||||
let emailAddress = await instance.first('EmailAddress', { email: 'someuser@example.org' })
|
||||
emailAddress = await emailAddress.toJson()
|
||||
expect(emailAddress.nonce).toEqual(expect.any(String))
|
||||
})
|
||||
})
|
||||
|
||||
@ -2,6 +2,9 @@ import encode from '../../jwt/encode'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { AuthenticationError } from 'apollo-server'
|
||||
import { neo4jgraphql } from 'neo4j-graphql-js'
|
||||
import { neode } from '../../bootstrap/neo4j'
|
||||
|
||||
const instance = neode()
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
@ -21,8 +24,8 @@ export default {
|
||||
// }
|
||||
const session = driver.session()
|
||||
const result = await session.run(
|
||||
'MATCH (user:User {email: $userEmail}) ' +
|
||||
'RETURN user {.id, .slug, .name, .avatar, .email, .encryptedPassword, .role, .disabled} as user LIMIT 1',
|
||||
'MATCH (user:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: $userEmail})' +
|
||||
'RETURN user {.id, .slug, .name, .avatar, .encryptedPassword, .role, .disabled, email:e.email} as user LIMIT 1',
|
||||
{
|
||||
userEmail: email,
|
||||
},
|
||||
@ -46,41 +49,24 @@ export default {
|
||||
}
|
||||
},
|
||||
changePassword: async (_, { oldPassword, newPassword }, { driver, user }) => {
|
||||
const session = driver.session()
|
||||
let result = await session.run(
|
||||
`MATCH (user:User {email: $userEmail})
|
||||
RETURN user {.id, .email, .encryptedPassword}`,
|
||||
{
|
||||
userEmail: user.email,
|
||||
},
|
||||
)
|
||||
let currentUser = await instance.find('User', user.id)
|
||||
|
||||
const [currentUser] = result.records.map(function(record) {
|
||||
return record.get('user')
|
||||
})
|
||||
|
||||
if (!(await bcrypt.compareSync(oldPassword, currentUser.encryptedPassword))) {
|
||||
const encryptedPassword = currentUser.get('encryptedPassword')
|
||||
if (!(await bcrypt.compareSync(oldPassword, encryptedPassword))) {
|
||||
throw new AuthenticationError('Old password is not correct')
|
||||
}
|
||||
|
||||
if (await bcrypt.compareSync(newPassword, currentUser.encryptedPassword)) {
|
||||
if (await bcrypt.compareSync(newPassword, encryptedPassword)) {
|
||||
throw new AuthenticationError('Old password and new password should be different')
|
||||
} else {
|
||||
const newEncryptedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
session.run(
|
||||
`MATCH (user:User {email: $userEmail})
|
||||
SET user.encryptedPassword = $newEncryptedPassword
|
||||
RETURN user
|
||||
`,
|
||||
{
|
||||
userEmail: user.email,
|
||||
newEncryptedPassword,
|
||||
},
|
||||
)
|
||||
session.close()
|
||||
|
||||
return encode(currentUser)
|
||||
}
|
||||
|
||||
const newEncryptedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
await currentUser.update({
|
||||
encryptedPassword: newEncryptedPassword,
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
|
||||
return encode(await currentUser.toJson())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -65,6 +65,13 @@ export const hasOne = obj => {
|
||||
export default {
|
||||
Query: {
|
||||
User: async (object, args, context, resolveInfo) => {
|
||||
const { email } = args
|
||||
if (email) {
|
||||
const e = await instance.first('EmailAddress', { email })
|
||||
let user = e.get('belongsTo')
|
||||
user = await user.toJson()
|
||||
return [user.node]
|
||||
}
|
||||
return neo4jgraphql(object, args, context, resolveInfo, false)
|
||||
},
|
||||
},
|
||||
@ -104,6 +111,14 @@ export default {
|
||||
},
|
||||
},
|
||||
User: {
|
||||
email: async (parent, params, context, resolveInfo) => {
|
||||
if (typeof parent.email !== 'undefined') return parent.email
|
||||
const { id } = parent
|
||||
const statement = `MATCH(u:User {id: {id}})-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e`
|
||||
const result = await instance.cypher(statement, { id })
|
||||
let [{ email }] = result.records.map(r => r.get('e').properties)
|
||||
return email
|
||||
},
|
||||
...undefinedToNull([
|
||||
'actorId',
|
||||
'avatar',
|
||||
|
||||
@ -2,7 +2,7 @@ type User {
|
||||
id: ID!
|
||||
actorId: String
|
||||
name: String
|
||||
email: String!
|
||||
email: String! @cypher(statement: "MATCH (this)-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e.email")
|
||||
slug: String!
|
||||
avatar: String
|
||||
coverImg: String
|
||||
|
||||
@ -21,7 +21,11 @@ export default function create() {
|
||||
...args,
|
||||
}
|
||||
args = await encryptPassword(args)
|
||||
return neodeInstance.create('User', args)
|
||||
const user = await neodeInstance.create('User', args)
|
||||
const email = await neodeInstance.create('EmailAddress', { email: args.email })
|
||||
await user.relateTo(email, 'primaryEmail')
|
||||
await email.relateTo(user, 'belongsTo')
|
||||
return user
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,6 +111,13 @@ u.createdAt = user.createdAt.`$date`,
|
||||
u.updatedAt = user.updatedAt.`$date`,
|
||||
u.deleted = user.deletedAt IS NOT NULL,
|
||||
u.disabled = false
|
||||
MERGE (e:EmailAddress {
|
||||
email: user.email,
|
||||
createdAt: toString(datetime()),
|
||||
verifiedAt: toString(datetime())
|
||||
})
|
||||
MERGE (e)-[:BELONGS_TO]->(u)
|
||||
MERGE (u)<-[:PRIMARY_EMAIL]-(e)
|
||||
WITH u, user, user.badgeIds AS badgeIds
|
||||
UNWIND badgeIds AS badgeId
|
||||
MATCH (b:Badge {id: badgeId})
|
||||
|
||||
@ -34,6 +34,8 @@ CREATE CONSTRAINT ON (p:Post) ASSERT p.slug IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (c:Category) ASSERT c.slug IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (u:User) ASSERT u.slug IS UNIQUE;
|
||||
CREATE CONSTRAINT ON (o:Organization) ASSERT o.slug IS UNIQUE;
|
||||
|
||||
CREATE CONSTRAINT ON (e:EmailAddress) ASSERT e.email IS UNIQUE;
|
||||
' | cypher-shell
|
||||
|
||||
echo '
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user