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 surveys.mutations import SurveysMutations
from rooms.mutations import RoomMutations from rooms.mutations import RoomMutations
from rooms.schema import RoomsQuery, ModuleRoomsQuery from rooms.schema import RoomsQuery, ModuleRoomsQuery
from users.schema_public import UsersQuery from users.schema import AllUsersQuery, UsersQuery
from users.schema import AllUsersQuery
from users.mutations import ProfileMutations from users.mutations import ProfileMutations

View File

@ -1,24 +1,14 @@
import graphene import graphene
from django.conf import settings from django.conf import settings
from graphene import relay
from graphene_django.debug import DjangoDebug from graphene_django.debug import DjangoDebug
from users.schema_public import UsersQuery
from users.mutations_public import UserMutations 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): class Mutation(UserMutations, graphene.ObjectType):
if settings.DEBUG: if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='__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; $white: #fff;
@ -99,12 +99,59 @@ input[type=text], input[type=password], input[type=email], select {
} }
.logo { .logo {
color: $brand; width: 250px;
font-size: 36px; height: 48px;
font-weight: 800;
font-family: Montserrat, Arial, sans-serif;
} }
.reset-heading { /* reset forms */
font-size: 2.4rem;
.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 %}"> <body class="{% block body_class %}{% endblock %}">
<div class="container"> <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 %} {% block body %}
{% endblock %} {% endblock %}
</div> </div>

View File

@ -4,10 +4,9 @@
{% block title %}{% trans 'Passwort zurücksetzen abgeschlossen' %}{% endblock %} {% block title %}{% trans 'Passwort zurücksetzen abgeschlossen' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Passwort zurücksetzen abgeschlossen' %}</h2> <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 class="reset__text">{% 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> <p class="reset__text"><a href="{% url "login" %}">{% trans 'Einloggen' %}</a></p>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -5,12 +5,13 @@
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %} {% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Setzen Sie Ihr neues Passwort' %}</h2> <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"> <form method="post" class="mt-1 reset__form">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button> <button class="btn mt-1" type="submit" name="action">{% trans 'Passwort zurücksetzen' %}</button>
</form> </form>
</div>
{% endblock %} {% endblock %}

View File

@ -5,8 +5,8 @@
{% block title %}{% trans 'Anweisungen versandt' %}{% endblock %} {% block title %}{% trans 'Anweisungen versandt' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2> <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> <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> </div>
{% endblock %} {% endblock %}

View File

@ -5,20 +5,17 @@
{% block title %}{% trans 'Passwort vergessen?' %}{% endblock %} {% block title %}{% trans 'Passwort vergessen?' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Passwort vergessen?' %}</h2> <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> <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">
<form method="post" class="mt-1">
{% csrf_token %} {% csrf_token %}
<div> <div>
{{ form.email.label_tag }} {{ form.email.label_tag }}
{{ form.email }} {{ form.email }}
</div> </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 }}"/> <input type="hidden" name="next" value="{{ next }}"/>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

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

View File

@ -5,13 +5,13 @@
{% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %} {% block title %}{% trans 'Setzen Sie Ihr Passwort' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Geben Sie ein persönliches Passwort ein:' %}</h2> <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"> <form method="post" class="mt-1 reset__form">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ form.as_p }}
<button type="submit" name="action">{% trans 'Passwort speichern' %}</button>
<button class="btn mt-1" type="submit" name="action">{% trans 'Passwort speichern' %}</button>
</form> </form>
</div>
{% endblock %} {% endblock %}

View File

@ -5,9 +5,9 @@
{% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %} {% block title %}{% trans 'Schauen Sie in Ihr Postfach' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Schauen Sie in Ihr Postfach' %}</h2> <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 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>{% 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> <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> </div>
{% endblock %} {% endblock %}

View File

@ -5,13 +5,11 @@
{% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %} {% block title %}{% trans 'Willkommen bei mySkillbox' %}{% endblock %}
{% block body %} {% block body %}
<h1 class="logo">myskillbox</h1> <div class="reset">
<h2 class="reset-heading">{% trans 'Willkommen bei Myskillbox' %}</h2> <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> <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">
<form method="post" class="mt-1">
{% csrf_token %} {% csrf_token %}
<div> <div>
<label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label> <label for="id_email">{% trans 'Geben Sie als erstes hier Ihre E-Mail-Adresse ein:' %}</label>
{{ form.email }} {{ form.email }}
@ -19,6 +17,5 @@
<button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button> <button class="btn mt-1" type="submit" name="action">{% trans 'E-Mail bestätigen' %}</button>
<input type="hidden" name="next" value="{{ next }}"/> <input type="hidden" name="next" value="{{ next }}"/>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -10,13 +10,9 @@
import re import re
import graphene import graphene
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.views import PasswordResetView, PasswordResetConfirmView, INTERNAL_RESET_URL_TOKEN
from graphene import relay from graphene import relay
from users.models import User
class FieldError(graphene.ObjectType): class FieldError(graphene.ObjectType):
code = graphene.String() code = graphene.String()
@ -46,114 +42,8 @@ class Login(relay.ClientIDMutation):
return cls(success=False, errors=['invalid_credentials']) 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: class UserMutations:
login = Login.Field() 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 import graphene
from graphene import relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField from graphene_django.filter import DjangoFilterConnectionField
from users.models import User from users.models import User, SchoolClass
from users.schema_public import UserNode
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): 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'))