Remove graphql password reset, style django pages
This commit is contained in:
parent
57224d228a
commit
13e3192776
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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'))
|
||||
Loading…
Reference in New Issue