mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Refactor backend
This commit is contained in:
parent
69434cdc7f
commit
ba185bcb65
@ -1,15 +1,16 @@
|
||||
import uuid from 'uuid/v4'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
export async function createPasswordReset({ driver, token, email, validUntil }) {
|
||||
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
|
||||
CREATE(pr:PasswordReset {token: $token, validUntil: $validUntil, redeemedAt: NULL})
|
||||
CREATE(pr:PasswordReset {code: $code, issuedAt: datetime($issuedAt), usedAt: NULL})
|
||||
MERGE (u)-[:REQUESTED]->(pr)
|
||||
RETURN u,pr
|
||||
RETURN pr
|
||||
`
|
||||
const transactionRes = await session.run(cypher, { token, email, validUntil })
|
||||
const transactionRes = await session.run(cypher, { issuedAt: issuedAt.toISOString(), code, email })
|
||||
const resets = transactionRes.records.map(record => record.get('pr'))
|
||||
session.close()
|
||||
return resets
|
||||
@ -18,27 +19,26 @@ export async function createPasswordReset({ driver, token, email, validUntil })
|
||||
export default {
|
||||
Mutation: {
|
||||
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||
let validUntil = new Date()
|
||||
validUntil += 3 * 60 * 1000
|
||||
const token = uuid()
|
||||
await createPasswordReset({ driver, token, email, validUntil })
|
||||
const code = uuid().substring(0,6)
|
||||
await createPasswordReset({ driver, code, email })
|
||||
return true
|
||||
},
|
||||
resetPassword: async (_, { email, token, newPassword }, { driver }) => {
|
||||
resetPassword: async (_, { email, code, newPassword }, { driver }) => {
|
||||
const session = driver.session()
|
||||
const now = new Date().getTime()
|
||||
const stillValid = new Date()
|
||||
stillValid.setDate(stillValid.getDate() - 1)
|
||||
const newHashedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||
const cypher = `
|
||||
MATCH (r:PasswordReset {token: $token})
|
||||
MATCH (r:PasswordReset {code: $code})
|
||||
MATCH (u:User {email: $email})-[:REQUESTED]->(r)
|
||||
WHERE r.validUntil > $now AND r.redeemedAt IS NULL
|
||||
SET r.redeemedAt = $now
|
||||
WHERE duration.between(r.issuedAt, datetime()).days <= 0 AND r.usedAt IS NULL
|
||||
SET r.usedAt = datetime()
|
||||
SET u.password = $newHashedPassword
|
||||
RETURN r
|
||||
`
|
||||
let transactionRes = await session.run(cypher, { now, email, token, newHashedPassword })
|
||||
let transactionRes = await session.run(cypher, { stillValid, email, code, newHashedPassword })
|
||||
const [reset] = transactionRes.records.map(record => record.get('r'))
|
||||
const result = !!(reset && reset.properties.redeemedAt)
|
||||
const result = !!(reset && reset.properties.usedAt)
|
||||
session.close()
|
||||
return result
|
||||
},
|
||||
|
||||
@ -64,23 +64,12 @@ describe('passwordReset', () => {
|
||||
expect(resets).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('creates a reset token', async () => {
|
||||
it('creates a reset code', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
const [reset] = resets
|
||||
const { token } = reset.properties
|
||||
expect(token).toMatch(/^........-....-....-....-............$/)
|
||||
})
|
||||
|
||||
it('created PasswordReset is valid for less than 4 minutes', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const resets = await getAllPasswordResets()
|
||||
const [reset] = resets
|
||||
let { validUntil } = reset.properties
|
||||
validUntil = Date.parse(validUntil)
|
||||
const now = new Date().getTime()
|
||||
expect(validUntil).toBeGreaterThan(now - 60 * 1000)
|
||||
expect(validUntil).toBeLessThan(now + 4 * 60 * 1000)
|
||||
const { code } = reset.properties
|
||||
expect(code).toHaveLength(6)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -89,50 +78,50 @@ describe('passwordReset', () => {
|
||||
const setup = async (options = {}) => {
|
||||
const {
|
||||
email = 'user@example.org',
|
||||
validUntil = new Date().getTime() + 3 * 60 * 1000,
|
||||
token = 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456',
|
||||
issuedAt = new Date(),
|
||||
code = 'abcdef',
|
||||
} = options
|
||||
|
||||
const session = driver.session()
|
||||
await createPasswordReset({ driver, email, validUntil, token })
|
||||
await createPasswordReset({ driver, email, issuedAt, code })
|
||||
session.close()
|
||||
}
|
||||
|
||||
const mutation = `mutation($token: String!, $email: String!, $newPassword: String!) { resetPassword(token: $token, email: $email, newPassword: $newPassword) }`
|
||||
const mutation = `mutation($code: String!, $email: String!, $newPassword: String!) { resetPassword(code: $code, email: $email, newPassword: $newPassword) }`
|
||||
let email = 'user@example.org'
|
||||
let token = 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456'
|
||||
let code = 'abcdef'
|
||||
let newPassword = 'supersecret'
|
||||
let variables
|
||||
|
||||
describe('invalid email', () => {
|
||||
it('resolves to false', async () => {
|
||||
await setup()
|
||||
variables = { newPassword, email: 'non-existent@example.org', token }
|
||||
variables = { newPassword, email: 'non-existent@example.org', code }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid email', () => {
|
||||
describe('but invalid token', () => {
|
||||
describe('but invalid code', () => {
|
||||
it('resolves to false', async () => {
|
||||
await setup()
|
||||
variables = { newPassword, email, token: 'slkdjfldsjflsdjfsjdfl' }
|
||||
variables = { newPassword, email, code: 'slkdjf' }
|
||||
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||
resetPassword: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('and valid token', () => {
|
||||
describe('and valid code', () => {
|
||||
beforeEach(() => {
|
||||
variables = {
|
||||
newPassword,
|
||||
email: 'user@example.org',
|
||||
token: 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456',
|
||||
code: 'abcdef',
|
||||
}
|
||||
})
|
||||
|
||||
describe('and token not expired', () => {
|
||||
describe('and code not expired', () => {
|
||||
beforeEach(async () => {
|
||||
await setup()
|
||||
})
|
||||
@ -143,12 +132,12 @@ describe('passwordReset', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('updates PasswordReset `redeemedAt` property', async () => {
|
||||
it('updates PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { redeemedAt } = request.properties
|
||||
expect(redeemedAt).not.toBeNull()
|
||||
const { usedAt } = request.properties
|
||||
expect(usedAt).not.toBeFalsy()
|
||||
})
|
||||
|
||||
it('updates password of the user', async () => {
|
||||
@ -168,10 +157,11 @@ describe('passwordReset', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('but expired token', () => {
|
||||
describe('but expired code', () => {
|
||||
beforeEach(async () => {
|
||||
const validUntil = new Date().getTime() - 1000
|
||||
await setup({ validUntil })
|
||||
const issuedAt = new Date()
|
||||
issuedAt.setDate(issuedAt.getDate() - 1)
|
||||
await setup({ issuedAt })
|
||||
})
|
||||
|
||||
it('resolves to false', async () => {
|
||||
@ -180,12 +170,12 @@ describe('passwordReset', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not update PasswordReset `redeemedAt` property', async () => {
|
||||
it('does not update PasswordReset `usedAt` property', async () => {
|
||||
await client.request(mutation, variables)
|
||||
const requests = await getAllPasswordResets()
|
||||
const [request] = requests
|
||||
const { redeemedAt } = request.properties
|
||||
expect(redeemedAt).toBeUndefined()
|
||||
const { usedAt } = request.properties
|
||||
expect(usedAt).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -26,7 +26,7 @@ type Mutation {
|
||||
signup(email: String!, password: String!): Boolean!
|
||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||
requestPasswordReset(email: String!): Boolean!
|
||||
resetPassword(email: String!, token: String!, newPassword: String!): Boolean!
|
||||
resetPassword(email: String!, code: String!, newPassword: String!): Boolean!
|
||||
report(id: ID!, description: String): Report
|
||||
disable(id: ID!): ID
|
||||
enable(id: ID!): ID
|
||||
|
||||
@ -150,18 +150,18 @@ export default {
|
||||
},
|
||||
async handleSubmitPassword() {
|
||||
const mutation = gql`
|
||||
mutation($token: String!, $email: String!, $newPassword: String!) {
|
||||
resetPassword(token: $token, email: $email, newPassword: $newPassword)
|
||||
mutation($code: String!, $email: String!, $newPassword: String!) {
|
||||
resetPassword(code: $code, email: $email, newPassword: $newPassword)
|
||||
}
|
||||
`
|
||||
const { newPassword } = this.password.formData
|
||||
const { email, code: token } = this.verification.formData
|
||||
const variables = { newPassword, email, token }
|
||||
const { email, code } = this.verification.formData
|
||||
const variables = { newPassword, email, code }
|
||||
try {
|
||||
const {
|
||||
data: { resetPassword },
|
||||
} = await this.$apollo.mutate({ mutation, variables })
|
||||
const changePasswordResult = resetPassword ? 'success' : 'failure'
|
||||
const changePasswordResult = resetPassword ? 'success' : 'error'
|
||||
this.changePasswordResult = changePasswordResult
|
||||
this.$emit('change-password-result', changePasswordResult)
|
||||
this.verification.formData = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user