mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Test+Implement resetPassword
This commit is contained in:
parent
145a8d8bf6
commit
c9ea956f85
@ -148,6 +148,7 @@ const permissions = shield(
|
|||||||
DeleteComment: isAuthor,
|
DeleteComment: isAuthor,
|
||||||
DeleteUser: isDeletingOwnAccount,
|
DeleteUser: isDeletingOwnAccount,
|
||||||
requestPasswordReset: allow,
|
requestPasswordReset: allow,
|
||||||
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
email: isMyOwn,
|
email: isMyOwn,
|
||||||
|
|||||||
@ -1,21 +1,46 @@
|
|||||||
export default {
|
import uuid from 'uuid/v4'
|
||||||
Mutation: {
|
import bcrypt from 'bcryptjs'
|
||||||
requestPasswordReset: async (_, { email }, { driver }) => {
|
|
||||||
|
export async function createPasswordReset({ driver, token, email, validUntil }) {
|
||||||
const session = driver.session()
|
const session = driver.session()
|
||||||
let validUntil = new Date()
|
|
||||||
validUntil += 3*60*1000
|
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH(u:User) WHERE u.email = $email
|
MATCH (u:User) WHERE u.email = $email
|
||||||
CREATE(pr:PasswordReset {id: apoc.create.uuid(), validUntil: $validUntil, redeemedAt: NULL})
|
CREATE(pr:PasswordReset {token: $token, validUntil: $validUntil, redeemedAt: NULL})
|
||||||
MERGE (u)-[:REQUESTED]->(pr)
|
MERGE (u)-[:REQUESTED]->(pr)
|
||||||
RETURN u,pr
|
RETURN u,pr
|
||||||
`
|
`
|
||||||
await session.run(cypher, { email, validUntil })
|
const transactionRes = await session.run(cypher, { token, email, validUntil })
|
||||||
|
const resets = transactionRes.records.map(record => record.get('pr'))
|
||||||
session.close()
|
session.close()
|
||||||
|
return resets
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Mutation: {
|
||||||
|
requestPasswordReset: async (_, { email }, { driver }) => {
|
||||||
|
let validUntil = new Date()
|
||||||
|
validUntil += 3 * 60 * 1000
|
||||||
|
const token = uuid()
|
||||||
|
await createPasswordReset({ driver, token, email, validUntil })
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
resetPassword: async (_, { email, token, newPassword }, { driver }) => {
|
resetPassword: async (_, { email, token, newPassword }, { driver }) => {
|
||||||
throw Error('Not Implemented')
|
const session = driver.session()
|
||||||
}
|
const now = new Date().getTime()
|
||||||
}
|
const newHashedPassword = await bcrypt.hashSync(newPassword, 10)
|
||||||
|
const cypher = `
|
||||||
|
MATCH (r:PasswordReset {token: $token})
|
||||||
|
MATCH (u:User {email: $email})-[:REQUESTED]->(r)
|
||||||
|
WHERE r.validUntil > $now AND r.redeemedAt IS NULL
|
||||||
|
SET r.redeemedAt = $now
|
||||||
|
SET u.password = $newHashedPassword
|
||||||
|
RETURN r
|
||||||
|
`
|
||||||
|
let transactionRes = await session.run(cypher, { now, email, token, newHashedPassword })
|
||||||
|
const [reset] = transactionRes.records.map(record => record.get('r'))
|
||||||
|
const result = !!(reset && reset.properties.redeemedAt)
|
||||||
|
session.close()
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { GraphQLClient } from 'graphql-request'
|
import { GraphQLClient } from 'graphql-request'
|
||||||
import Factory from '../../seed/factories'
|
import Factory from '../../seed/factories'
|
||||||
import { host, login } from '../../jest/helpers'
|
import { host } from '../../jest/helpers'
|
||||||
import { getDriver } from '../../bootstrap/neo4j'
|
import { getDriver } from '../../bootstrap/neo4j'
|
||||||
|
import { createPasswordReset } from './passwordReset'
|
||||||
|
|
||||||
const factory = Factory()
|
const factory = Factory()
|
||||||
let client
|
let client
|
||||||
@ -36,7 +37,9 @@ describe('passwordReset', () => {
|
|||||||
const variables = { email: 'non-existent@example.org' }
|
const variables = { email: 'non-existent@example.org' }
|
||||||
|
|
||||||
it('resolves anyways', async () => {
|
it('resolves anyways', async () => {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({"requestPasswordReset": true})
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
requestPasswordReset: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates no node', async () => {
|
it('creates no node', async () => {
|
||||||
@ -50,7 +53,9 @@ describe('passwordReset', () => {
|
|||||||
const variables = { email: 'user@example.org' }
|
const variables = { email: 'user@example.org' }
|
||||||
|
|
||||||
it('resolves', async () => {
|
it('resolves', async () => {
|
||||||
await expect(client.request(mutation, variables)).resolves.toEqual({"requestPasswordReset": true})
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
requestPasswordReset: true,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates node with label `PasswordReset`', async () => {
|
it('creates node with label `PasswordReset`', async () => {
|
||||||
@ -59,21 +64,139 @@ describe('passwordReset', () => {
|
|||||||
expect(resets).toHaveLength(1)
|
expect(resets).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates an id used as a reset token', async () => {
|
it('creates a reset token', async () => {
|
||||||
await client.request(mutation, variables)
|
await client.request(mutation, variables)
|
||||||
const [reset] = await getAllPasswordResets()
|
const resets = await getAllPasswordResets()
|
||||||
const { id: token } = reset.properties
|
const [reset] = resets
|
||||||
|
const { token } = reset.properties
|
||||||
expect(token).toMatch(/^........-....-....-....-............$/)
|
expect(token).toMatch(/^........-....-....-....-............$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('created PasswordReset is valid for less than 4 minutes', async () => {
|
it('created PasswordReset is valid for less than 4 minutes', async () => {
|
||||||
await client.request(mutation, variables)
|
await client.request(mutation, variables)
|
||||||
const [reset] = await getAllPasswordResets()
|
const resets = await getAllPasswordResets()
|
||||||
|
const [reset] = resets
|
||||||
let { validUntil } = reset.properties
|
let { validUntil } = reset.properties
|
||||||
validUntil = Date.parse(validUntil)
|
validUntil = Date.parse(validUntil)
|
||||||
const now = (new Date()).getTime()
|
const now = new Date().getTime()
|
||||||
expect(validUntil).toBeGreaterThan(now - 60*1000)
|
expect(validUntil).toBeGreaterThan(now - 60 * 1000)
|
||||||
expect(validUntil).toBeLessThan(now + 4*60*1000)
|
expect(validUntil).toBeLessThan(now + 4 * 60 * 1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('resetPassword', () => {
|
||||||
|
const setup = async (options = {}) => {
|
||||||
|
const {
|
||||||
|
email = 'user@example.org',
|
||||||
|
validUntil = new Date().getTime() + 3 * 60 * 1000,
|
||||||
|
token = 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456',
|
||||||
|
} = options
|
||||||
|
|
||||||
|
const session = driver.session()
|
||||||
|
await createPasswordReset({ driver, email, validUntil, token })
|
||||||
|
session.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutation = `mutation($token: String!, $email: String!, $newPassword: String!) { resetPassword(token: $token, email: $email, newPassword: $newPassword) }`
|
||||||
|
let email = 'user@example.org'
|
||||||
|
let token = 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456'
|
||||||
|
let newPassword = 'supersecret'
|
||||||
|
let variables
|
||||||
|
|
||||||
|
describe('invalid email', () => {
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await setup()
|
||||||
|
variables = { newPassword, email: 'non-existent@example.org', token }
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({ resetPassword: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid email', () => {
|
||||||
|
describe('but invalid token', () => {
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await setup()
|
||||||
|
variables = { newPassword, email, token: 'slkdjfldsjflsdjfsjdfl' }
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('but invalid token', () => {
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
variables = { newPassword, email: 'user@example.org', token: 'lksjdflksjdflksjdlkfjsf' }
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and valid token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
variables = {
|
||||||
|
newPassword,
|
||||||
|
email: 'user@example.org',
|
||||||
|
token: 'abcdefgh-ijkl-mnop-qrst-uvwxyz123456',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('and token not expired', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await setup()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves to true', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates PasswordReset `redeemedAt` property', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const requests = await getAllPasswordResets()
|
||||||
|
const [request] = requests
|
||||||
|
const { redeemedAt } = request.properties
|
||||||
|
expect(redeemedAt).not.toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates password of the user', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const checkLoginMutation = `
|
||||||
|
mutation($email: String!, $password: String!) {
|
||||||
|
login(email: $email, password: $password)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const expected = expect.objectContaining({ login: expect.any(String) })
|
||||||
|
await expect(
|
||||||
|
client.request(checkLoginMutation, {
|
||||||
|
email: 'user@example.org',
|
||||||
|
password: 'supersecret',
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('but expired token', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const validUntil = new Date().getTime() - 1000
|
||||||
|
await setup({ validUntil })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolves to false', async () => {
|
||||||
|
await expect(client.request(mutation, variables)).resolves.toEqual({
|
||||||
|
resetPassword: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not update PasswordReset `redeemedAt` property', async () => {
|
||||||
|
await client.request(mutation, variables)
|
||||||
|
const requests = await getAllPasswordResets()
|
||||||
|
const [request] = requests
|
||||||
|
const { redeemedAt } = request.properties
|
||||||
|
expect(redeemedAt).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -26,7 +26,7 @@ type Mutation {
|
|||||||
signup(email: String!, password: String!): Boolean!
|
signup(email: String!, password: String!): Boolean!
|
||||||
changePassword(oldPassword: String!, newPassword: String!): String!
|
changePassword(oldPassword: String!, newPassword: String!): String!
|
||||||
requestPasswordReset(email: String!): Boolean!
|
requestPasswordReset(email: String!): Boolean!
|
||||||
resetPassword(email: String!, resetToken: String!, newPassword: String!): String!
|
resetPassword(email: String!, token: String!, newPassword: String!): Boolean!
|
||||||
report(id: ID!, description: String): Report
|
report(id: ID!, description: String): Report
|
||||||
disable(id: ID!): ID
|
disable(id: ID!): ID
|
||||||
enable(id: ID!): ID
|
enable(id: ID!): ID
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user