mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge pull request #233 from Human-Connection/212-change-password
[User profile] Change password
This commit is contained in:
commit
ecbdfdfb55
@ -5,7 +5,7 @@ import { getLangByName } from '../../support/helpers'
|
||||
|
||||
let lastPost = {}
|
||||
|
||||
const loginCredentials = {
|
||||
let loginCredentials = {
|
||||
email: 'peterpan@example.org',
|
||||
password: '1234'
|
||||
}
|
||||
@ -244,3 +244,48 @@ Then(
|
||||
cy.get('.error').should('contain', message)
|
||||
}
|
||||
)
|
||||
|
||||
Given('my user account has the following login credentials:', table => {
|
||||
loginCredentials = table.hashes()[0]
|
||||
cy.debug()
|
||||
cy.factory().create('User', loginCredentials)
|
||||
})
|
||||
|
||||
When('I fill the password form with:', table => {
|
||||
table = table.rowsHash()
|
||||
cy.get('input[id=oldPassword]')
|
||||
.type(table['Your old password'])
|
||||
.get('input[id=newPassword]')
|
||||
.type(table['Your new passsword'])
|
||||
.get('input[id=confirmPassword]')
|
||||
.type(table['Confirm new password'])
|
||||
})
|
||||
|
||||
When('submit the form', () => {
|
||||
cy.get('form').submit()
|
||||
})
|
||||
|
||||
Then('I cannot login anymore with password {string}', password => {
|
||||
cy.reload()
|
||||
const { email } = loginCredentials
|
||||
cy.visit(`/login`)
|
||||
cy.get('input[name=email]')
|
||||
.trigger('focus')
|
||||
.type(email)
|
||||
cy.get('input[name=password]')
|
||||
.trigger('focus')
|
||||
.type(password)
|
||||
cy.get('button[name=submit]')
|
||||
.as('submitButton')
|
||||
.click()
|
||||
cy.get('.iziToast-wrapper').should('contain', 'Incorrect email address or password.')
|
||||
})
|
||||
|
||||
Then('I can login successfully with password {string}', password => {
|
||||
cy.reload()
|
||||
cy.login({
|
||||
...loginCredentials,
|
||||
...{password}
|
||||
})
|
||||
cy.get('.iziToast-wrapper').should('contain', "You are logged in!")
|
||||
})
|
||||
|
||||
31
cypress/integration/settings/ChangePassword.feature
Normal file
31
cypress/integration/settings/ChangePassword.feature
Normal file
@ -0,0 +1,31 @@
|
||||
Feature: Change password
|
||||
As a user
|
||||
I want to change my password in my settings
|
||||
For security, e.g. if I exposed my password by accident
|
||||
|
||||
Login via email and password is a well-known authentication procedure and you
|
||||
can assure to the server that you are who you claim to be. Either if you
|
||||
exposed your password by acccident and you want to invalidate the exposed
|
||||
password or just out of an good habit, you want to change your password.
|
||||
|
||||
Background:
|
||||
Given my user account has the following login credentials:
|
||||
| email | password |
|
||||
| user@example.org | exposed |
|
||||
And I am logged in
|
||||
|
||||
Scenario: Change my password
|
||||
Given I am on the "settings" page
|
||||
And I click on "Security"
|
||||
When I fill the password form with:
|
||||
| Your old password | exposed |
|
||||
| Your new passsword | secure |
|
||||
| Confirm new password | secure |
|
||||
And submit the form
|
||||
And I see a success message:
|
||||
"""
|
||||
Password successfully changed!
|
||||
"""
|
||||
And I log out through the menu in the top right corner
|
||||
Then I cannot login anymore with password "exposed"
|
||||
But I can login successfully with password "secure"
|
||||
154
webapp/components/ChangePassword.spec.js
Normal file
154
webapp/components/ChangePassword.spec.js
Normal file
@ -0,0 +1,154 @@
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import ChangePassword from './ChangePassword.vue'
|
||||
import Vue from 'vue'
|
||||
import Styleguide from '@human-connection/styleguide'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
|
||||
localVue.use(Styleguide)
|
||||
|
||||
describe('ChangePassword.vue', () => {
|
||||
let mocks
|
||||
let wrapper
|
||||
|
||||
beforeEach(() => {
|
||||
mocks = {
|
||||
validate: jest.fn(),
|
||||
$toast: {
|
||||
error: jest.fn(),
|
||||
success: jest.fn()
|
||||
},
|
||||
$t: jest.fn(),
|
||||
$store: {
|
||||
commit: jest.fn()
|
||||
},
|
||||
$apollo: {
|
||||
mutate: jest
|
||||
.fn()
|
||||
.mockRejectedValue({ message: 'Ouch!' })
|
||||
.mockResolvedValueOnce({ data: { changePassword: 'NEWTOKEN' } })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('mount', () => {
|
||||
let wrapper
|
||||
const Wrapper = () => {
|
||||
return mount(ChangePassword, { mocks, localVue })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('renders three input fields', () => {
|
||||
expect(wrapper.findAll('input')).toHaveLength(3)
|
||||
})
|
||||
|
||||
describe('validations', () => {
|
||||
it('invalid', () => {
|
||||
expect(wrapper.vm.disabled).toBe(true)
|
||||
})
|
||||
|
||||
describe('old password and new password', () => {
|
||||
describe('match', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input#oldPassword').setValue('some secret')
|
||||
wrapper.find('input#newPassword').setValue('some secret')
|
||||
})
|
||||
|
||||
it('invalid', () => {
|
||||
expect(wrapper.vm.disabled).toBe(true)
|
||||
})
|
||||
|
||||
it.skip('displays a warning', () => {
|
||||
const calls = mocks.validate.mock.calls
|
||||
const expected = [
|
||||
['change-password.validations.old-and-new-password-match']
|
||||
]
|
||||
expect(calls).toEqual(expect.arrayContaining(expected))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('new password and confirmation', () => {
|
||||
describe('mismatch', () => {
|
||||
it.todo('invalid')
|
||||
it.todo('displays a warning')
|
||||
})
|
||||
|
||||
describe('match', () => {
|
||||
describe('and old password mismatch', () => {
|
||||
it.todo('valid')
|
||||
})
|
||||
|
||||
describe('clicked', () => {
|
||||
it.todo('sets loading')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('given valid input', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('input#oldPassword').setValue('supersecret')
|
||||
wrapper.find('input#newPassword').setValue('superdupersecret')
|
||||
wrapper.find('input#confirmPassword').setValue('superdupersecret')
|
||||
})
|
||||
|
||||
describe('submit form', () => {
|
||||
beforeEach(() => {
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('calls changePassword mutation', () => {
|
||||
expect(mocks.$apollo.mutate).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('passes form data as variables', () => {
|
||||
expect(mocks.$apollo.mutate.mock.calls[0][0]).toEqual(
|
||||
expect.objectContaining({
|
||||
variables: {
|
||||
oldPassword: 'supersecret',
|
||||
newPassword: 'superdupersecret',
|
||||
confirmPassword: 'superdupersecret'
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe('mutation resolves', () => {
|
||||
beforeEach(() => {
|
||||
mocks.$apollo.mutate = jest.fn().mockResolvedValue()
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('calls auth/SET_TOKEN with response', () => {
|
||||
expect(mocks.$store.commit).toHaveBeenCalledWith(
|
||||
'auth/SET_TOKEN',
|
||||
'NEWTOKEN'
|
||||
)
|
||||
})
|
||||
|
||||
it('displays success message', () => {
|
||||
expect(mocks.$t).toHaveBeenCalledWith(
|
||||
'settings.security.change-password.success'
|
||||
)
|
||||
expect(mocks.$toast.success).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('mutation rejects', () => {
|
||||
beforeEach(() => {
|
||||
// second call will reject
|
||||
wrapper.find('form').trigger('submit')
|
||||
})
|
||||
|
||||
it('displays error message', () => {
|
||||
expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
83
webapp/components/ChangePassword.vue
Normal file
83
webapp/components/ChangePassword.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<ds-form
|
||||
v-model="formData"
|
||||
:schema="formSchema"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<template>
|
||||
<ds-input
|
||||
id="oldPassword"
|
||||
model="oldPassword"
|
||||
type="password"
|
||||
label="Your old password"
|
||||
/>
|
||||
<ds-input
|
||||
id="newPassword"
|
||||
model="newPassword"
|
||||
type="password"
|
||||
label="Your new password"
|
||||
/>
|
||||
<ds-input
|
||||
id="confirmPassword"
|
||||
model="confirmPassword"
|
||||
type="password"
|
||||
label="Confirm new password"
|
||||
/>
|
||||
<ds-space margin-top="base">
|
||||
<ds-button
|
||||
:loading="loading"
|
||||
primary
|
||||
>
|
||||
{{ $t('settings.security.change-password.button') }}
|
||||
</ds-button>
|
||||
</ds-space>
|
||||
</template>
|
||||
</ds-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
name: 'ChangePassword',
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
formSchema: {
|
||||
oldPassword: { required: true },
|
||||
newPassword: { required: true },
|
||||
confirmPassword: { required: true }
|
||||
},
|
||||
loading: false,
|
||||
disabled: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleSubmit(data) {
|
||||
this.loading = true
|
||||
const mutation = gql`
|
||||
mutation($oldPassword: String!, $newPassword: String!) {
|
||||
changePassword(oldPassword: $oldPassword, newPassword: $newPassword)
|
||||
}
|
||||
`
|
||||
const variables = this.formData
|
||||
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({ mutation, variables })
|
||||
this.$store.commit('auth/SET_TOKEN', data.changePassword)
|
||||
this.$toast.success(
|
||||
this.$t('settings.security.change-password.success')
|
||||
)
|
||||
} catch (err) {
|
||||
this.$toast.error(err.message)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -31,7 +31,11 @@
|
||||
"labelBio": "Über dich"
|
||||
},
|
||||
"security": {
|
||||
"name": "Sicherheit"
|
||||
"name": "Sicherheit",
|
||||
"change-password": {
|
||||
"button": "Passwort ändern",
|
||||
"success": "Passwort erfolgreich geändert!"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Einladungen"
|
||||
|
||||
@ -31,7 +31,11 @@
|
||||
"labelBio": "About You"
|
||||
},
|
||||
"security": {
|
||||
"name": "Security"
|
||||
"name": "Security",
|
||||
"change-password": {
|
||||
"button": "Change password",
|
||||
"success": "Password successfully changed!"
|
||||
}
|
||||
},
|
||||
"invites": {
|
||||
"name": "Invites"
|
||||
|
||||
@ -1,18 +1,16 @@
|
||||
<template>
|
||||
<ds-card :header="$t('settings.security.name')">
|
||||
<hc-empty
|
||||
icon="tasks"
|
||||
message="Coming Soon…"
|
||||
/>
|
||||
<change-password />
|
||||
</ds-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HcEmpty from '~/components/Empty.vue'
|
||||
import ChangePassword from '~/components/ChangePassword.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HcEmpty
|
||||
ChangePassword
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user