Remove graphql password reset, style django pages

This commit is contained in:
Christian Cueni 2019-10-03 16:58:05 +02:00
parent 57224d228a
commit 13e3192776
16 changed files with 173 additions and 375 deletions

View File

@ -19,8 +19,7 @@ from surveys.schema import SurveysQuery
from surveys.mutations import SurveysMutations
from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery, ModuleRoomsQuery
from users.schema_public import UsersQuery
from users.schema import AllUsersQuery
from users.schema import AllUsersQuery, UsersQuery
from users.mutations import ProfileMutations

View File

@ -1,24 +1,14 @@
import graphene
from django.conf import settings
from graphene import relay
from graphene_django.debug import DjangoDebug
from users.schema_public import UsersQuery
from users.mutations_public import UserMutations
class Query(UsersQuery, graphene.ObjectType):
node = relay.Node.Field()
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug')
class Mutation(UserMutations, graphene.ObjectType):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__debug')
schema = graphene.Schema(query=Query, mutation=Mutation)
schema = graphene.Schema(mutation=Mutation)

View File

@ -1,4 +1,4 @@
@import "materialize/materialize";
//@import "materialize/materialize";
$white: #fff;
@ -99,12 +99,59 @@ input[type=text], input[type=password], input[type=email], select {
}
.logo {
color: $brand;
font-size: 36px;
font-weight: 800;
font-family: Montserrat, Arial, sans-serif;
width: 250px;
height: 48px;
}
.reset-heading {
font-size: 2.4rem;
/* reset forms */
.reset__heading {
font-size: 2.125rem;
margin-bottom: 52px;
font-weight: 600;
}
.reset__text {
margin-bottom: 52px;
}
.reset__form label {
font-weight: 600;
}
.reset__form input {
display: flex;
padding: 16px;
box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.15);
border-radius: 4px;
box-sizing: border-box;
border: 1px solid #f0f0f0;
max-width: 100%;
background-color: #ffffff;
}
.reset__form button {
background: transparent;
border: 2px solid #17A887;
padding: 5px 15px;
border-radius: 3px;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 400;
display: inline-flex;
cursor: pointer;
font-size: 100%;
}
.reset__form label {
font-weight: 600;
}
.reset__form div {
margin-bottom: 20px;
}
.container {
max-width: 800px;
min-width: 320px;
margin: 0 auto;
}

View File

@ -28,6 +28,34 @@
<body class="{% block body_class %}{% endblock %}">
<div class="container">
<svg class="logo" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1350 250">
<path
d="M304.4,242.15a60,60,0,0,1-19.59-3.1,64.2,64.2,0,0,1-17.6-9.63l-2.94-2.22,21.17-34,3.58,3.21a21.91,21.91,0,0,0,6,4,15.21,15.21,0,0,0,5.81,1.09c4,0,6.51-1.44,8.08-4.68l1.15-2.19L263.73,85.72H313.8L334.27,143l17.38-57.3h48.8L353,208.39c-4.53,11.34-10.91,19.87-19,25.41h0C326,239.34,316,242.15,304.4,242.15Zm-29.33-17a53.63,53.63,0,0,0,12.38,6.3,51.94,51.94,0,0,0,17,2.66c10,0,18.42-2.33,25.12-6.94h0c6.71-4.62,12.1-11.92,16-21.71L388.67,93.79h-31l-22.74,75-26.79-75H275.94L319,195l-2.88,5.47c-2.87,5.92-8.18,9.11-15.29,9.11a23.28,23.28,0,0,1-8.88-1.69,24.83,24.83,0,0,1-4.58-2.53Z"
style="fill:#36c0a1"/>
<path
d="M458.66,113a12.63,12.63,0,0,0-6.43,1.39,4.55,4.55,0,0,0-2.36,4.18q0,3.22,4.4,5.25a93.59,93.59,0,0,0,14,4.61,178.08,178.08,0,0,1,21.33,7.29,40.28,40.28,0,0,1,14.79,11q6.32,7.39,6.33,19,0,17.8-14,28.19t-37.19,10.4A102.76,102.76,0,0,1,430,200.15a84.64,84.64,0,0,1-25.4-12.33l13.29-27.22a97.33,97.33,0,0,0,21.76,10.72A64.21,64.21,0,0,0,460.16,175a14.94,14.94,0,0,0,7.07-1.39,4.33,4.33,0,0,0,2.57-4q0-3.22-4.18-5.25a84.51,84.51,0,0,0-13.83-4.61A157.5,157.5,0,0,1,431,152.67a40,40,0,0,1-14.58-10.93q-6.22-7.29-6.22-18.86,0-18,13.72-28.51t36-10.5q26.79,0,51.23,14.15l-14.36,27.22Q473,113,458.66,113Z"
style="fill:#36c0a1"/>
<path d="M604.69,202.4l-21.22-40.51-8.79,9.22v31.3h-43.3V43.35h43.3v77.38l32.15-34.94h48.87l-42.66,45,42.87,71.6Z"
style="fill:#36c0a1"/>
<path
d="M712.25,36.49q6.22,6.22,6.22,16.08t-6.22,16.08q-6.22,6.22-16.08,6.22T680,68.64q-6.33-6.21-6.32-16.08T680,36.49q6.32-6.21,16.18-6.22T712.25,36.49Zm-37.51,49.3H718V202.4h-43.3Z"
style="fill:#36c0a1"/>
<path d="M748.47,43.35h43.3V202.4h-43.3Z" style="fill:#36c0a1"/>
<path d="M823.5,43.35h43.3V202.4H823.5Z" style="fill:#36c0a1"/>
<path
d="M1002.06,91.79A50.33,50.33,0,0,1,1021,113q6.75,13.72,6.75,31.73,0,17.8-6.54,31.19a48.35,48.35,0,0,1-18.54,20.69q-12,7.29-27.87,7.29A44,44,0,0,1,956.19,200a40.21,40.21,0,0,1-14.36-11.15v13.5h-43.3V43.35h43.3V99.29a38.85,38.85,0,0,1,13.93-11.15,41.53,41.53,0,0,1,18-3.86Q989.85,84.29,1002.06,91.79Zm-23.8,70.63q5.79-7.18,5.79-18.76t-5.79-18.76a18.82,18.82,0,0,0-15.43-7.18,18.59,18.59,0,0,0-15.22,7.18q-5.79,7.19-5.79,18.76t5.79,18.76a18.58,18.58,0,0,0,15.22,7.18A18.8,18.8,0,0,0,978.27,162.42Z"
style="fill:#36c0a1"/>
<path
d="M1142.8,91.69a54.24,54.24,0,0,1,22.62,20.9q8,13.5,8,31.51,0,17.8-8,31.4a54,54,0,0,1-22.62,21q-14.58,7.4-34.08,7.4t-34.19-7.4a53.86,53.86,0,0,1-22.73-21q-8-13.61-8-31.4,0-18,8-31.51a54.08,54.08,0,0,1,22.73-20.9q14.68-7.4,34.19-7.4T1142.8,91.69Zm-49.52,34.08q-5.79,7.18-5.79,18.76,0,11.79,5.79,18.86a18.92,18.92,0,0,0,15.43,7.07,18.7,18.7,0,0,0,15.22-7.07q5.79-7.07,5.79-18.86,0-11.58-5.79-18.76a18.6,18.6,0,0,0-15.22-7.18A18.81,18.81,0,0,0,1093.28,125.77Z"
style="fill:#36c0a1"/>
<path
d="M1176.45,85.79h49.73L1242.26,116l18-30.23h47.16L1271,142.6l39,59.81h-49.73l-18-33-20.58,33h-47.59l39-59.59Z"
style="fill:#36c0a1"/>
<path
d="M245,105.8A38.35,38.35,0,0,0,229.9,89.74h0a46.56,46.56,0,0,0-46.21,1.09A41.77,41.77,0,0,0,171.45,103a38.76,38.76,0,0,0-11.67-12,42.9,42.9,0,0,0-24.06-6.82,44.09,44.09,0,0,0-21.4,5.16,41.05,41.05,0,0,0-8.13,5.83v-9.4H58V201.83h48.19V144.37c0-5.32,1.23-9.42,3.77-12.55a11.7,11.7,0,0,1,9.27-4.46,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V144.37c0-5.26,1.24-9.49,3.69-12.59a11.44,11.44,0,0,1,9.15-4.43,9.48,9.48,0,0,1,7.75,3.35c2.09,2.45,3.11,5.75,3.11,10.09v61h48.19V129.28A51.17,51.17,0,0,0,245,105.8Zm-2.87,88h-32v-53c0-6.25-1.7-11.41-5-15.33a17.51,17.51,0,0,0-14-6.18h0a19.38,19.38,0,0,0-15.37,7.49c-3.61,4.55-5.44,10.48-5.44,17.6v49.39h-32v-53c0-6.25-1.7-11.41-5-15.33a17.53,17.53,0,0,0-14-6.18h0a19.66,19.66,0,0,0-15.44,7.45c-3.69,4.56-5.57,10.49-5.57,17.63v49.39h-32v-100h32v18.16h5.19l2.25-3.54a34.63,34.63,0,0,1,12.63-12,36.13,36.13,0,0,1,17.53-4.17,35,35,0,0,1,19.62,5.49,31.51,31.51,0,0,1,12.17,15.38l.46,1.18h6.52l.45-1a36.75,36.75,0,0,1,50.91-16.55,30,30,0,0,1,11.93,12.74,43.2,43.2,0,0,1,4.33,19.81Z"
style="fill:#36c0a1"/>
</svg>
{% block body %}
{% endblock %}
</div>

View File

@ -4,10 +4,9 @@
{% block title %}{% trans 'Passwort zurücksetzen abgeschlossen' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Passwort zurücksetzen abgeschlossen' %}</h2>
<p>{% trans 'Ihr Passwort wurde zurückgesetzt. Sie können sich nun auf der Loginseite anmelden.' %}</p>
<p><a href="{% url "login" %}">{% trans 'Einloggen' %}</a></p>
<div class="reset">
<h2 class="reset__heading">{% trans 'Passwort zurücksetzen abgeschlossen' %}</h2>
<p class="reset__text">{% trans 'Ihr Passwort wurde zurückgesetzt. Sie können sich nun auf der Loginseite anmelden.' %}</p>
<p class="reset__text"><a href="{% url "login" %}">{% trans 'Einloggen' %}</a></p>
</div>
{% endblock %}

View File

@ -5,12 +5,13 @@
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
<form method="post" class="mt-1">
<div class="reset">
<h2 class="reset__heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2>
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
<form method="post" class="mt-1 reset__form">
{% csrf_token %}
{{ form.as_p }}
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
</form>
</div>
{% endblock %}

View File

@ -5,8 +5,8 @@
{% block title %}{% trans 'Anweisungen versandt' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
<p>{% trans 'Wir haben die Anweisungen, um Ihr Passwort zurückzusetzen, an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
<div class="reset">
<h2 class="reset__heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
<p class="reset__text">{% trans 'Wir haben die Anweisungen, um Ihr Passwort zurückzusetzen, an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
</div>
{% endblock %}

View File

@ -5,20 +5,17 @@
{% block title %}{% trans 'Passwort vergessen?' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Passwort vergessen?' %}</h2>
<p>{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
<form method="post" class="mt-1">
<div class="reset">
<h2 class="reset__heading">{% trans 'Passwort vergessen?' %}</h2>
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
<form method="post" class="mt-1 reset__form">
{% csrf_token %}
<div>
{{ form.email.label_tag }}
{{ form.email }}
</div>
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
<button type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
</div>
{% endblock %}

View File

@ -4,10 +4,9 @@
{% block title %}{% trans 'Sie haben es geschafft' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Sie haben es geschafft' %}</h2>
<p>{% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
<p><a href="{% url "login" %}">{% trans 'Jetzt anmelden' %}</a></p>
<div class="reset">
<h2 class="reset__heading">{% trans 'Sie haben es geschafft' %}</h2>
<p class="reset__text">% trans 'Ihr Passwort wurde erfolgreich gespeichert. Sie können sich nun anmelden.' %}</p>
<p class="reset__text"><a href="{% url "login" %}">{% trans 'Jetzt anmelden' %}</a></p>
</div>
{% endblock %}

View File

@ -5,13 +5,13 @@
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
<form method="post" class="mt-1">
<div class="reset">
<h2 class="reset__heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2>
<p class="reset__text">{% trans 'Kein Problem! Geben Sie Ihre E-Mail-Adresse ein und erhalten Sie weitere Anweisungen.' %}</p>
<form method="post" class="mt-1 reset__form">
{% csrf_token %}
{{ form.as_p }}
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort speichern' %}</button>
<button type="submit" name="action">{% trans 'Passwort speichern' %}</button>
</form>
</div>
{% endblock %}

View File

@ -5,9 +5,9 @@
{% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
<p>{% trans 'Wir haben ein E-Mail mit allen weiteren Anweisungen an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
<p>{% trans 'Hinweis: Ihre persönlichen Angaben für Ihr Benutzerkonto wurden zuvor in mySkillbox importiert. Sie können ausschliesslich die importierte E-Mail-Adresse verwenden. Wenn Sie nicht wissen, welche E-Mail-Adresse für Sie importiert wurde, können Sie Ihre Lehrperson fragen.' %}</p>
</div>
<div class="reset">
<h2 class="reset__heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2>
<p class="reset__text">{% trans 'Wir haben ein E-Mail mit allen weiteren Anweisungen an Sie verschickt. Die E-Mail sollte in Kürze bei Ihnen ankommen.' %}</p>
<p class="reset__text">{% trans 'Hinweis: Ihre persönlichen Angaben für Ihr Benutzerkonto wurden zuvor in mySkillbox importiert. Sie können ausschliesslich die importierte E-Mail-Adresse verwenden. Wenn Sie nicht wissen, welche E-Mail-Adresse für Sie importiert wurde, können Sie Ihre Lehrperson fragen.' %}</p>
</div>
{% endblock %}

View File

@ -5,13 +5,11 @@
{% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %}
{% block body %}
<h1 class="logo">myskillbox</h1>
<h2 class="reset-heading">{% trans 'Willkommen bei Myskillbox' %}</h2>
<p>{% trans 'Bevor Sie mySkillbox verwenden können, müssen Sie Ihre E-Mail-Adresse bestätigen und ein persönliches Passwort festlegen.' %}</p>
<form method="post" class="mt-1">
<div class="reset">
<h2 class="reset__heading">{% trans 'Willkommen bei Myskillbox' %}</h2>
<p class="reset__text">{% trans 'Bevor Sie mySkillbox verwenden können, müssen Sie Ihre E-Mail-Adresse bestätigen und ein persönliches Passwort festlegen.' %}</p>
<form method="post" class="mt-1 reset__form">
{% csrf_token %}
<div>
<label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label>
{{ form.email }}
@ -19,6 +17,5 @@
<button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
</div>
{% endblock %}

View File

@ -10,13 +10,9 @@
import re
import graphene
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.views import PasswordResetView, PasswordResetConfirmView, INTERNAL_RESET_URL_TOKEN
from django.contrib.auth import authenticate, login
from graphene import relay
from users.models import User
class FieldError(graphene.ObjectType):
code = graphene.String()
@ -46,114 +42,8 @@ class Login(relay.ClientIDMutation):
return cls(success=False, errors=['invalid_credentials'])
class PasswordReset(relay.ClientIDMutation):
class Input:
email_input = graphene.String()
success = graphene.Boolean()
errors = graphene.List(MutationError)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
email = kwargs.get('email_input')
try:
user = User.objects.get(email=email) # todo: make lowercase
except User.DoesNotExist:
return cls(success=False, errors=['invalid_email'])
password_reset_view = PasswordResetView()
password_reset_view.request = info.context
form = password_reset_view.form_class({'email': user.email})
if not form.is_valid():
return cls(success=False, errors=form.errors)
try:
password_reset_view.form_valid(form)
except Exception:
return cls(success=False, errors=['email_error'])
return cls(success=True, errors=[])
class PasswordConfirm:
@classmethod
def verify_token_and_uidb64(cls, token, uidb64, request):
password_reset_confirm_view = PasswordResetConfirmView(request=request)
return password_reset_confirm_view.dispatch(uidb64=uidb64, token=token, request=request)
class PasswordResetVerify(relay.ClientIDMutation, PasswordConfirm):
class Input:
token_input = graphene.String()
uidb64_input = graphene.String()
success = graphene.Boolean()
errors = graphene.List(MutationError)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
uidb64 = kwargs.get('uidb64_input')
token = kwargs.get('token_input')
http_response = cls.verify_token_and_uidb64(token, uidb64, info.context)
# PasswordResetConfirmView returns a webpage if either token or uidb64 are invalid (HTTP 200)
# if token and uidb64 are correct a redirect is returned (HTTP302)
if http_response.status_code == 302:
return cls(success=True, errors=[])
else:
return cls(success=False, errors=['invalid_challenge'])
class PasswordResetSetPassword(relay.ClientIDMutation, PasswordConfirm):
class Input:
new_password_input = graphene.String()
confirm_new_password_input = graphene.String()
uidb64_input = graphene.String()
success = graphene.Boolean()
errors = graphene.List(MutationError)
@classmethod
def mutate_and_get_payload(cls, root, info, **kwargs):
uidb64 = kwargs.get('uidb64_input')
new_password = kwargs.get('new_password_input')
confirm_new_password_input = kwargs.get('confirm_new_password_input')
# fake ordinary POST for view
info.context.POST = {
'new_password1': new_password,
'new_password2': confirm_new_password_input
}
if not cls.verify_strong_password(new_password):
return cls(success=False, errors=['invalid_passwords'])
http_response = cls.verify_token_and_uidb64(INTERNAL_RESET_URL_TOKEN, uidb64, info.context)
if http_response.status_code == 302:
return cls(success=True, errors=[])
else:
return cls(success=False, errors=['invalid_passwords']) # todo: check error from form
@classmethod
def verify_strong_password(cls, 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)
return has_number and has_upper and has_lower and has_special
class UserMutations:
login = Login.Field()
password_reset = PasswordReset.Field()
password_reset_verify = PasswordResetVerify.Field()
password_reset_set_password = PasswordResetSetPassword.Field()

View File

@ -1,8 +1,57 @@
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from users.models import User
from users.schema_public import UserNode
from users.models import User, SchoolClass
class SchoolClassNode(DjangoObjectType):
pk = graphene.Int()
class Meta:
model = SchoolClass
filter_fields = ['name']
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
class UserNode(DjangoObjectType):
pk = graphene.Int()
permissions = graphene.List(graphene.String)
selected_class = graphene.Field(SchoolClassNode)
class Meta:
model = User
filter_fields = ['username', 'email']
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module', 'avatar_url',
'selected_class']
interfaces = (relay.Node,)
def resolve_pk(self, info, **kwargs):
return self.id
def resolve_permissions(self, info):
return self.get_all_permissions()
def resolve_selected_class(self, info):
return self.selected_class()
class UsersQuery(object):
me = graphene.Field(UserNode)
all_users = DjangoFilterConnectionField(UserNode)
def resolve_me(self, info, **kwargs):
return info.context.user
def resolve_all_users(self, info, **kwargs):
if not info.context.user.is_superuser:
return User.objects.none()
else:
return User.objects.all()
class AllUsersQuery(object):

View File

@ -1,63 +0,0 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-10-01
# @author: chrigu <christian.cueni@iterativ.ch>
import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from users.models import User, SchoolClass
class SchoolClassNode(DjangoObjectType):
pk = graphene.Int()
class Meta:
model = SchoolClass
filter_fields = ['name']
interfaces = (relay.Node,)
def resolve_pk(self, *args, **kwargs):
return self.id
class UserNode(DjangoObjectType):
pk = graphene.Int()
permissions = graphene.List(graphene.String)
selected_class = graphene.Field(SchoolClassNode)
class Meta:
model = User
filter_fields = ['username', 'email']
only_fields = ['username', 'email', 'first_name', 'last_name', 'school_classes', 'last_module', 'avatar_url',
'selected_class']
interfaces = (relay.Node,)
def resolve_pk(self, info, **kwargs):
return self.id
def resolve_permissions(self, info):
return self.get_all_permissions()
def resolve_selected_class(self, info):
return self.selected_class()
class UsersQuery(object):
me = graphene.Field(UserNode)
all_users = DjangoFilterConnectionField(UserNode)
def resolve_me(self, info, **kwargs):
return info.context.user
def resolve_all_users(self, info, **kwargs):
if not info.context.user.is_superuser:
return User.objects.none()
else:
return User.objects.all()

View File

@ -1,135 +0,0 @@
# -*- coding: utf-8 -*-
#
# ITerativ GmbH
# http://www.iterativ.ch/
#
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
#
# Created on 2019-10-02
# @author: chrigu <christian.cueni@iterativ.ch>
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.contrib.sessions.middleware import SessionMiddleware
from django.core import mail
from django.test import TestCase, RequestFactory
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
from graphene.test import Client
from api.schema_public import schema
from core.factories import UserFactory
class PasswordResetTests(TestCase):
def setUp(self):
self.user = UserFactory(username='aschi')
request = RequestFactory().post('/')
# adding session
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
self.client = Client(schema=schema, context_value=request)
def make_reset_mutation(self, email):
mutation = '''
mutation PasswordReset($input: PasswordResetInput!){
passwordReset(input: $input) {
success
errors {
field
}
}
}
'''
return self.client.execute(mutation, variables={
'input': {
'emailInput': email
}
})
def make_set_verify_mutation(self, uidb64, token):
mutation = '''
mutation PasswordResetVerify($input: PasswordResetVerifyInput!){
passwordResetVerify(input: $input) {
success
errors {
field
}
}
}
'''
return self.client.execute(mutation, variables={
'input': {
'uidb64Input': uidb64,
'tokenInput': token
}
})
def make_set_password_mutation(self, uidb64, new_password, new_password_confirm):
mutation = '''
mutation PasswordResetSetPassword($input: PasswordResetSetPasswordInput!){
passwordResetSetPassword(input: $input) {
success
errors {
field
}
}
}
'''
return self.client.execute(mutation, variables={
'input': {
'uidb64Input': uidb64,
'newPasswordInput': new_password,
'confirmNewPasswordInput': new_password_confirm,
}
})
def test_user_can_initiate_password(self):
result = self.make_reset_mutation(self.user.email)
self.assertEqual(len(mail.outbox), 1)
self.assertTrue(mail.outbox[0].subject.startswith('Passwort auf'))
self.assertTrue(result.get('data').get('passwordReset').get('success'))
def test_user_can_verify_and_set_password(self):
token_generator = PasswordResetTokenGenerator()
token = token_generator.make_token(self.user)
uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)).decode()
result = self.make_set_verify_mutation(uidb64, token)
self.assertTrue(result.get('data').get('passwordResetVerify').get('success'))
new_password = 'Abcd1234!'
set_result = self.make_set_password_mutation(uidb64, new_password, new_password)
self.assertTrue(set_result.get('data').get('passwordResetSetPassword').get('success'))
def test_user_cannot_use_unsafe_password(self):
token_generator = PasswordResetTokenGenerator()
token = token_generator.make_token(self.user)
uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)).decode()
result = self.make_set_verify_mutation(uidb64, token)
self.assertTrue(result.get('data').get('passwordResetVerify').get('success'))
new_password = 'test'
set_result = self.make_set_password_mutation(uidb64, new_password, new_password)
self.assertFalse(set_result.get('data').get('passwordResetSetPassword').get('success'),)
def test_new_passwords_must_match(self):
token_generator = PasswordResetTokenGenerator()
token = token_generator.make_token(self.user)
uidb64 = urlsafe_base64_encode(force_bytes(self.user.pk)).decode()
result = self.make_set_verify_mutation(uidb64, token)
self.assertTrue(result.get('data').get('passwordResetVerify').get('success'))
new_password = 'Abcd1234!'
new_password_confirm = 'Abcd1234!1'
set_result = self.make_set_password_mutation(uidb64, new_password, new_password_confirm)
self.assertFalse(set_result.get('data').get('passwordResetSetPassword').get('success'))