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 submission from '@/pages/studentSubmission'
|
||||||
import portfolio from '@/pages/portfolio'
|
import portfolio from '@/pages/portfolio'
|
||||||
import project from '@/pages/project'
|
import project from '@/pages/project'
|
||||||
|
import profilePage from '@/pages/profile'
|
||||||
|
import passwordReset from '@/pages/passwordReset'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', component: start, meta: {layout: 'blank'}},
|
{path: '/', component: start, meta: {layout: 'blank'}},
|
||||||
|
|
@ -62,6 +64,8 @@ const routes = [
|
||||||
{path: 'topic/:topicSlug', component: topic, meta: {subnavigation: true}}
|
{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}
|
{path: '*', component: p404}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from objectives.schema import ObjectivesQuery
|
||||||
from rooms.mutations import RoomMutations
|
from rooms.mutations import RoomMutations
|
||||||
from rooms.schema import RoomsQuery
|
from rooms.schema import RoomsQuery
|
||||||
from users.schema import UsersQuery
|
from users.schema import UsersQuery
|
||||||
|
from users.mutations import ProfileMutations
|
||||||
|
|
||||||
|
|
||||||
class Query(UsersQuery, RoomsQuery, ObjectivesQuery, BookQuery, AssignmentsQuery, StudentSubmissionQuery,
|
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,
|
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations,
|
||||||
graphene.ObjectType):
|
ProfileMutations, graphene.ObjectType):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
debug = graphene.Field(DjangoDebug, name='__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()
|
id = graphene.ID()
|
||||||
name = graphene.String()
|
name = graphene.String()
|
||||||
year = graphene.Int()
|
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