Add pw change endpoint in API
This commit is contained in:
parent
d3edbae71a
commit
d64c641661
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div class="pw-reset">
|
||||
<form class="pw-reset__form reset-form">
|
||||
<label for="current-pw" class="reset-form__current-label">Aktuells Passwort</label>
|
||||
<input id="current-pw" type="text" class="reset-form__current">
|
||||
<label for="new-pw" class="reset-form__new-label">Neues Passwort</label>
|
||||
<input id="new-pw" type="text" class="reset-form__new">
|
||||
<button class="reset-form__submit">Zurücksetzen</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
me: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div class="password-reset">
|
||||
<h1 class="password-reset__header">Passwort Zurücksetzen</h1>
|
||||
<password-reset />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import PasswordReset from '@/components/PasswordReset'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PasswordReset
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
me: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div class="profile">
|
||||
<h1 class="profile__heading">Profile</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
me: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
|
||||
</style>
|
||||
|
|
@ -18,6 +18,8 @@ import start from '@/pages/start'
|
|||
import submission from '@/pages/studentSubmission'
|
||||
import portfolio from '@/pages/portfolio'
|
||||
import project from '@/pages/project'
|
||||
import profilePage from '@/pages/profile'
|
||||
import passwordReset from '@/pages/passwordReset'
|
||||
|
||||
const routes = [
|
||||
{path: '/', component: start, meta: {layout: 'blank'}},
|
||||
|
|
@ -62,6 +64,8 @@ const routes = [
|
|||
{path: 'topic/:topicSlug', component: topic, meta: {subnavigation: true}}
|
||||
]
|
||||
},
|
||||
{path: '/me', name: 'profile', component: profilePage},
|
||||
{path: '/password-reset', name: 'pw-reset', component: passwordReset},
|
||||
{path: '*', component: p404}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from objectives.schema import ObjectivesQuery
|
|||
from rooms.mutations import RoomMutations
|
||||
from rooms.schema import RoomsQuery
|
||||
from users.schema import UsersQuery
|
||||
from users.mutations import ProfileMutations
|
||||
|
||||
|
||||
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
||||
|
|
@ -27,7 +28,7 @@ class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery
|
|||
|
||||
|
||||
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations,
|
||||
graphene.ObjectType):
|
||||
ProfileMutations, graphene.ObjectType):
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
from api.schema import schema
|
||||
from core.factories import UserFactory
|
||||
from django.contrib.auth import authenticate
|
||||
|
||||
|
||||
class PasswordUpdate(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(username='aschi')
|
||||
|
||||
request = RequestFactory().get('/')
|
||||
request.user = self.user
|
||||
self.client = Client(schema=schema, context_value=request)
|
||||
|
||||
def make_request(self, new_password, old_password='test'):
|
||||
|
||||
mutation = '''
|
||||
mutation UpdatePassword($input: UpdatePasswordInput!) {
|
||||
updatePassword(input: $input) {
|
||||
success
|
||||
errors {
|
||||
field
|
||||
errors {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
return self.client.execute(mutation, variables={
|
||||
'input': {
|
||||
'passwordInput': {
|
||||
'oldPassword': old_password,
|
||||
'newPassword': new_password
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def test_update_password(self):
|
||||
|
||||
new_password = 'Abcd123!'
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertTrue(result.get('data').get('updatePassword').get('success'))
|
||||
|
||||
user = authenticate(username=self.user.username, password=new_password)
|
||||
self.assertIsNotNone(user)
|
||||
|
||||
def test_update_fails_with_short_password(self):
|
||||
|
||||
new_password = 'Ab!d123'
|
||||
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'new_password',
|
||||
'errors': [
|
||||
{'code': 'min_length'}
|
||||
]
|
||||
})
|
||||
|
||||
def test_update_fails_with_no_special_character(self):
|
||||
|
||||
new_password = 'Abcd1239'
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'new_password',
|
||||
'errors': [
|
||||
{'code': 'invalid'}
|
||||
]
|
||||
})
|
||||
|
||||
def test_update_fails_with_no_digit(self):
|
||||
|
||||
new_password = 'Abcd!asddfg'
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'new_password',
|
||||
'errors': [
|
||||
{'code': 'invalid'}
|
||||
]
|
||||
})
|
||||
|
||||
def test_update_fails_with_no_lowercase_char(self):
|
||||
|
||||
new_password = '45ABDC!AWSWS'
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'new_password',
|
||||
'errors': [
|
||||
{'code': 'invalid'}
|
||||
]
|
||||
})
|
||||
|
||||
def test_update_fails_with_no_uppercase_char(self):
|
||||
|
||||
new_password = '45aswed!aswdef'
|
||||
result = self.make_request(new_password)
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'new_password',
|
||||
'errors': [
|
||||
{'code': 'invalid'}
|
||||
]
|
||||
})
|
||||
|
||||
def test_update_fails_with_wrong_old_password(self):
|
||||
|
||||
new_password = 'Abcd123!'
|
||||
result = self.make_request(new_password, 'testttt')
|
||||
|
||||
self.assertFalse(result.get('data').get('updatePassword').get('success'))
|
||||
self.assertEquals(result.get('data').get('updatePassword').get('errors')[0], {
|
||||
'field': 'old_password',
|
||||
'errors': [
|
||||
{'code': 'invalid'}
|
||||
]
|
||||
})
|
||||
|
|
@ -6,3 +6,8 @@ class SchoolClassInput(InputObjectType):
|
|||
id = graphene.ID()
|
||||
name = graphene.String()
|
||||
year = graphene.Int()
|
||||
|
||||
|
||||
class PasswordUpdateInput(InputObjectType):
|
||||
old_password = graphene.String()
|
||||
new_password = graphene.String()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
from users.inputs import PasswordUpdateInput
|
||||
from users.serializers import PasswordSerialzer
|
||||
|
||||
|
||||
class FieldError(graphene.ObjectType):
|
||||
code = graphene.String()
|
||||
|
||||
|
||||
class UpdateError(graphene.ObjectType):
|
||||
field = graphene.String()
|
||||
errors = graphene.List(FieldError)
|
||||
|
||||
|
||||
class UpdatePassword(relay.ClientIDMutation):
|
||||
class Input:
|
||||
password_input = graphene.Argument(PasswordUpdateInput)
|
||||
|
||||
success = graphene.Boolean()
|
||||
errors = graphene.List(UpdateError)
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
user = info.context.user
|
||||
password_data = kwargs.get('password_input')
|
||||
|
||||
serializer = PasswordSerialzer(data=password_data, context=user)
|
||||
|
||||
if serializer.is_valid():
|
||||
user.set_password(password_data['new_password'])
|
||||
user.save()
|
||||
return cls(success=True)
|
||||
|
||||
errors = []
|
||||
for key, value in serializer.errors.items():
|
||||
error = UpdateError(field=key, errors=[])
|
||||
for field_error in serializer.errors[key]:
|
||||
error.errors.append(FieldError(code=field_error.code))
|
||||
|
||||
errors.append(error)
|
||||
|
||||
return cls(success=False, errors=errors)
|
||||
|
||||
|
||||
class ProfileMutations:
|
||||
update_password = UpdatePassword.Field()
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-04-02
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import re
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import CharField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
MIN_PASSWORD_LENGTH = 8
|
||||
|
||||
# For future versions https://docs.djangoproject.com/en/2.1/topics/auth/passwords/#integrating-validation
|
||||
|
||||
|
||||
def validate_old_password(old_password, username):
|
||||
user = get_user_model().objects.get(username=username)
|
||||
if user.check_password(old_password):
|
||||
return old_password
|
||||
else:
|
||||
raise serializers.ValidationError(_(u'Das eingegebene Passwort ist falsch'))
|
||||
|
||||
|
||||
def validate_old_new_password(value):
|
||||
if value.get('old_password') == '' and value.get('new_password') == '':
|
||||
return value
|
||||
elif value.get('old_password') == '' and value.get('new_password') != '':
|
||||
raise serializers.ValidationError(_(u'Das neue Passwort muss gesetzt werden'))
|
||||
elif value.get('old_password') != '' and value.get('new_password') == '':
|
||||
raise serializers.ValidationError(_(u'Das alte Passwort muss angegeben werden'))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def validate_strong_email(password):
|
||||
|
||||
if len(password) == 0:
|
||||
return password
|
||||
|
||||
has_number = re.search('\d', password)
|
||||
has_upper = re.search('[A-Z]', password)
|
||||
has_lower = re.search('[a-z]', password)
|
||||
has_special = re.search('[!@#$%^&*(),.?":{}|<>\+]', password)
|
||||
|
||||
if has_number and has_upper and has_lower and has_special:
|
||||
return password
|
||||
else:
|
||||
raise serializers.ValidationError(_(u'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten'))
|
||||
|
||||
|
||||
class PasswordSerialzer(serializers.Serializer):
|
||||
old_password = CharField(allow_blank=True)
|
||||
new_password = CharField(allow_blank=True, min_length=MIN_PASSWORD_LENGTH)
|
||||
|
||||
def validate_new_password(self, value):
|
||||
return validate_strong_email(value)
|
||||
|
||||
def validate_old_password(self, value):
|
||||
return validate_old_password(value, self.context.username)
|
||||
|
||||
def validate(self, obj):
|
||||
return validate_old_new_password(obj)
|
||||
Loading…
Reference in New Issue