summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Sargent <philip.sargent@gmail.com>2025-01-27 15:15:54 +0000
committerPhilip Sargent <philip.sargent@gmail.com>2025-01-27 15:15:54 +0000
commit1825ed55fcb3b8522aa4a8139c49a02f6f992e73 (patch)
tree8b06aeb0b38459b8be0f31b31860a439ce6d41ed
parent6d25f70491e3c034e6a5a2cb165c330cdc578522 (diff)
downloadtroggle-1825ed55fcb3b8522aa4a8139c49a02f6f992e73.tar.gz
troggle-1825ed55fcb3b8522aa4a8139c49a02f6f992e73.tar.bz2
troggle-1825ed55fcb3b8522aa4a8139c49a02f6f992e73.zip
password reset for logged-on users now working
-rw-r--r--core/views/user_registration.py109
-rw-r--r--templates/login/register.html95
-rw-r--r--templates/login/register_email.html83
-rw-r--r--templates/login/register_text.html48
-rw-r--r--urls.py2
5 files changed, 263 insertions, 74 deletions
diff --git a/core/views/user_registration.py b/core/views/user_registration.py
index 27f6046..fc2aace 100644
--- a/core/views/user_registration.py
+++ b/core/views/user_registration.py
@@ -13,7 +13,8 @@ from troggle.parsers.users import register_user, get_encryptor, ENCRYPTED_DIR, U
from troggle.parsers.people import troggle_slugify
from troggle.core.utils import (
add_commit,
-)
+ is_identified_user
+ )
"""
This is the new individual user login registration, instead of everyone signing
in as "expo". This will be useful for the kanban expo organisation tool.
@@ -27,7 +28,7 @@ todo = """
class ExpoPasswordResetForm(PasswordResetForm):
"""Because we are using the Django-standard django.contrib.auth mechanisms, the way Django wants us to
- modify them is to subclass their stuff and insert our extras in teh subclasses. This is completely
+ modify them is to subclass their stuff and insert our extras in the subclasses. This is completely
unlike how the rest of troggle works because we avoid Class-based views.
We avoid them because they are very hard to debug for newcomer programmers who don't know Django:
the logic is spread out up a tree of preceding ancestor classes.
@@ -89,21 +90,61 @@ def newregister(request, username=None):
save_users(request, updated_user, email)
return HttpResponseRedirect("/accounts/password_reset/")
else: # GET
- form = newregister_form(initial={"visible": "True"})
+ form = newregister_form(initial={"visible-passwords": "True"})
return render(request, "login/register.html", {"form": form, "warning": warning, "newuser": True})
-def register(request, username=None):
- """To register a new user on the troggle system, similar to the "expo" user
- (with cavey:beery password) but specific to an individual
+def re_register_email(request):
+ """For a logged-on user:
+ - change the email address
+ - trigger reset password ( by email token)
+
+ and we ignore any username specified in the URL of the request.
"""
- current_user = request.user # if not logged in, this is 'AnonymousUser'
- warning = ""
+ logged_in = (identified_login := is_identified_user(request.user))
+ if not logged_in:
+ return HttpResponseRedirect("/accounts/login/")
+
+ u = request.user
+ initial_values = {}
+ initial_values.update({"username": u.username})
+ initial_values.update({"email": u.email})
+
+ if request.method == "POST":
+ form = register_email_form(request.POST) # only username and password
+ if form.is_valid():
+ print("POST VALID")
+ email = form.cleaned_data["email"]
+ u.email = email
+ u.save()
+ save_users(request, u, email)
+ return render(request, "login/register_email.html", {"form": form, "confirmed": True})
+ else:
+ print("POST INVALID")
+ return render(request, "login/register_email.html", {"form": form})
+ else: # GET
+ form = register_email_form(initial=initial_values)
+ return render(request, "login/register_email.html", {"form": form})
+
+def register(request, url_username=None):
+ """To register an old expoer as a new user on the troggle system,
+ for someone who has previously attended expo,
+ similar to the "expo" user
+ (with cavey:beery password) but specific to an individual.
+ """
+ warning = ""
+ initial_values={"visible-passwords": "True"}
+
+ logged_in = (identified_login := is_identified_user(request.user))
+ if logged_in:
+ return re_register_email(request)
+
if request.method == "POST":
form = register_form(request.POST)
if form.is_valid():
+ print("POST VALID")
un = form.cleaned_data["username"]
pw= form.cleaned_data["password1"]
email = form.cleaned_data["email"]
@@ -112,7 +153,7 @@ def register(request, username=None):
# this is a password re-set, not a new registration. So we need to check it is the same person.
form_user = expoers[0]
if current_user != form_user:
- print(f"## UNAUTHORIZED Password reset ## {current_user} {form_user}")
+ print(f"## UNAUTHORIZED Password reset ## {request.user} {form_user}")
# return render(request, "login/register.html", {"form": form, "unauthorized": True})
# create User in the system and refresh stored encrypted user list and git commit it:
updated_user = register_user(un, email, password=pw, pwhash=None)
@@ -120,19 +161,16 @@ def register(request, username=None):
# to do, login automatically, and redirect to control panel ?
return HttpResponseRedirect("/accounts/login/")
else: # GET
- if username: # if provided in URL
- if not current_user.is_anonymous:
- warning = f"WARNING - you are logged-in as someone else '{current_user}'. You must logout and login again as '{username}' "
+ if url_username: # if provided in URL
+ if not request.user.is_anonymous:
+ warning = f"WARNING - you are logged-in as someone else '{request.user}'. You must logout and login again as '{url_username}' "
print(f"REGISTER: {warning}")
- form = register_form(initial={"visible": "True", "username": username} )
-
- elif current_user:
- form = register_form(initial={"visible": "True", "username": current_user.username})
- else:
- form = register_form(initial={"visible": "True"})
-
- return render(request, "login/register.html", {"form": form, "warning": warning})
-
+ initial_values.update({"username": url_username})
+ elif request.user:
+ initial_values.update({"username": request.user.username})
+
+ form = register_form(initial=initial_values)
+ return render(request, "login/register.html", {"form": form, "warning": warning, "logged_in": logged_in})
def save_users(request, updated_user, email="troggle@exposerver.expo"):
@@ -215,8 +253,37 @@ class newregister_form(forms.Form): # not a model-form, just a form-form
"If you have been on expo before, you need to use the other form at expo.survex.com/accounts/register/ ."
)
+class register_email_form(forms.Form): # not a model-form, just a form-form
+ """The widgets are being used EVEN THOUGH we are not using form.as_p() to create the
+ HTML form"""
+ username = forms.CharField(strip=True, required=True,
+ label="Username",
+ widget=forms.TextInput(
+ attrs={"size": 35, "placeholder": "e.g. anathema-device",
+ "style": "vertical-align: text-top;", "readonly": "readonly"} # READONLY for when changing email
+ ))
+ email = forms.CharField(strip=True, required=True,
+ label="email",
+ widget=forms.TextInput(
+ attrs={"size": 35, "placeholder": "e.g. anathema@potatohut.expo",
+ "style": "vertical-align: text-top;"}
+ ))
+
+ def clean(self):
+ cleaned_data = super().clean()
+
+ email = cleaned_data.get("email")
+ users = User.objects.filter(email=email)
+ print(f"{len(users)=}")
+ if len(users) > 1: # allow 0 (change) or 1 (confirm)
+ print("ValidationError")
+ raise ValidationError(
+ "Duplicate email address. Another registered user is already using this email address. Email addresses must be unique as that is how we reset forgotten passwords."
+ )
class register_form(forms.Form): # not a model-form, just a form-form
+ """The widgets are being used EVEN THOUGH we are not using form.as_p() to create the
+ HTML form"""
username = forms.CharField(strip=True, required=True,
label="Username",
widget=forms.TextInput(
diff --git a/templates/login/register.html b/templates/login/register.html
index 8f6409c..e35f793 100644
--- a/templates/login/register.html
+++ b/templates/login/register.html
@@ -44,7 +44,11 @@ User Registration - for a personal login to Troggle
{%endif %}</h2>
<!--using template login/register.html -->
</div>
+<!-- This is really TWO forms, depending on whether the 'new_user' is set or not.
+ALSO it behaves differently if a usernaem is specified int he URL
+ALSO it behaves differently if there is an expo-valid logged-on User
+-->
<h3>Register your email address</h3>
{% if unauthorized %}
<span style="color:red">
@@ -63,17 +67,39 @@ You are not logged in as the user you are attempting to re-register.
So type in the same email address that you use there if you have already signed up to that.
<p>
<span style="color:red">
-{{ warning }}
+{{ warning }} {{ form.errors }}
</span>
<div style='width: 700px; font-family: monospace; font-weight: bold; font-size: 150%; text-align: right; '>
<form method="post" accept-charset="utf-8">{% csrf_token %}
- {{form.as_p}}
+<p>
+ <label for="id_username">Username:</label>
+ {{form.username}}
+</p>
+<p>
+ <label for="id_email">email:</label>
+ {{form.email}}
+</p>
+{% if logged_in %}<!-- one we have initially logged in,
+all later password chnages are done ONLY via email token password re-set-->
+{% else %}
+<p>
+ <label for="id_password1">Troggle password:</label>
+ {{form.password1}}
+</p>
+<p>
+ <label for="id_password2">Re-type your troggle password:</label>
+ {{form.password2}}
+</p>
+{%endif %}
<div class='align-right'>
{% if newuser %}
{% else %}
- <input type="checkbox" checked name="visible" onclick="myFunction()">Make Passwords visible (on this form only)
+ {% if logged_in %}
+ {% else %}
+ <input type="checkbox" checked name="visible-passwords" onclick="myFunction()">Make Passwords visible (on this form only)
+ {%endif %}
{%endif %}
<br /><br />
{% if newuser %}
@@ -83,67 +109,32 @@ So type in the same email address that you use there if you have already signed
Get login token by email &rarr;
</button>
{% else %}
- <button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;"
+ <button class="fancybutton"
+ {% if logged_in %}
+ style="padding: 0.5em 25px; font-size: 100%;"
+ {% else %}
+ style="padding: 0.5em 25px; font-size: 100%; background: silver;"
+ disabled
+ {% endif %}
onclick="window.location.href='/accounts/password_reset/'" value = "Go to" >
Reset password
</button>
&nbsp;&nbsp;&nbsp;
+
<button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" type = "submit" >
- Register &rarr;
+ {% if logged_in %}
+ Change or confirm email &rarr;
+ {% else %}
+ Register &rarr;
+ {% endif %}
</button>
{%endif %}
</div>
</form>
</div>
<div style='width: 50em' align="left">
-<h3>Your name</h3>
-{% if newuser %}
-<p>Use the name you are usually known by "officially": this is usually the name on your passport.
-But if you habitually use your second forename, not the first forename, use that.
-Yes, you can put in any 'de', 'van' or 'von' or whatever 'nobiliary particle' you have, or a hyphenated surname; but this must be your real name.
-Nicknames and fun names are done later.
-{% else %}
-<p>For previous expoers, your username must be your 'troggle id' as listed on the <a href='/people_ids'>past expoers list</a>
-{%endif %}
-<p>Unfortunately cavers tend to use weird and playful names when signing up for things,
-so we can't automatically connect the troggle names and ids with the email addresses
-on the email list. And we don't believe in signing people up for things without their
-direct permission anyway.
-Having said that, when you register here we <em>will</em> sign you up automatically to
-the expo email list as that is how expo manages everything and it is a condition of
-coming on expo. (You can unsubscribe from the email list after expo.)
-
-<p>But the automatic sign-up to the email list is not working yet, and may not be before April 2025.
-So if you don't want to miss out on anything important, make sure you sign up to the
-<a href="https://lists.wookware.org/cgi-bin/mailman/roster/expo">email list</a>
-right now.
-
-<h3>Students !</h3>
-Please do not use an email address which will expire when you leave your current institution.
-This will happen much sooner than you can possibly believe. If you realise that you have done this on the email list,
-you can change it at the bottom of <a href="https://lists.wookware.org/cgi-bin/mailman/listinfo/expo">this page</a>.
+{% include 'login/register_text.html' %}
-{% if newuser %}
-<h3>What happens next</h3>
-<p>Clicking the big blue button will send you an email which will contain a login token.
-Click on the link in the email and you will be able to set your own login password.
-Use this to login to troggle and go to the Expo Signup form.
-{% else %}
-{%endif %}
-
-<h3>Security note</h3>
-We never store passwords at all, we only store a cryptographic hash.
-We do store your email address but only 'in clear' inside the live database online
-where it is accessible only to the database administrators. There is no troggle report
-which publishes your email address.
-For permanent storage all email addresses are encrypted. Your real name and troggle
-username is public however, and we do not have anonymous people attending expo.
-<p>
-The password we {% if newuser %}will be{% else %}are{%endif %} asking for is solely for logging into troggle.
-The troggle login is used to track who is editing the current and past expo data, website content, and historic survey data, as well as for accessing the expo Kanban software. It is not the same as the password you use to access your email
-with your email provider
-and it is not the same as the password you use to interact with the expo
-<a href="https://lists.wookware.org/cgi-bin/mailman/roster/expo">email list</a>.
<span style="color:red">
{{ form.non_field_errors }} <!-- form validation errors appear here, and also at the top of the form-->
diff --git a/templates/login/register_email.html b/templates/login/register_email.html
new file mode 100644
index 0000000..49e9791
--- /dev/null
+++ b/templates/login/register_email.html
@@ -0,0 +1,83 @@
+{% extends 'base.html' %}
+
+{% block content %}
+<!-- this overrides the django.contrib.auth default form
+and it must be placed in
+troggle/templates/login/register.html
+because magic
+
+This is because Django is Opinionated and does lots of Invisible Defaults
+see
+https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project
+-->
+
+<style>
+input, textarea {font-family: monospace; font-weight: bold; text-align:right; font-size: 100%; padding: 0.5em; }
+textarea {text-align:left }
+li {color:red}
+</style>
+
+<div class='middle'>
+<h2>
+Email change - for a personal login to Troggle
+</h2>
+<!--using template login/register_email.html -->
+</div>
+<!--ONLY for an expo-valid logged-on User
+-->
+<h3>Register your email address</h3>
+
+<p>[For new people wanting to come to expo for the first time,
+please use the <a href="/accounts/newregister/">New User</a> registration form]
+
+<p>This will eventually sign you up automatically to the
+<a href="https://lists.wookware.org/cgi-bin/mailman/roster/expo">expo email list</a>.
+So type in the same email address that you use there if you have already signed up to that.
+<p>
+<span style="color:red">
+{{ form.non_field_errors }}
+{{ form.errors }}
+</span>
+
+<div style='width: 700px; font-family: monospace; font-weight: bold; font-size: 150%; text-align: right; '>
+ <form method="post" accept-charset="utf-8">{% csrf_token %}
+<p>
+ <label for="id_username">Username:</label>
+ {{form.username}}
+</p>
+<p>
+ <label for="id_email">email:</label>
+ {{form.email}}
+</p>
+
+<div class='align-right'>
+
+ <button class="fancybutton" type="button"
+ {% if confirmed %}
+ style="padding: 0.5em 25px; font-size: 100%;"
+ {% else %}
+ style="padding: 0.5em 25px; font-size: 100%; background: silver;"
+ disabled
+ {% endif %}
+ onclick="window.location.href='/accounts/password_reset/'" value = "Go to" >
+ Reset password
+ </button>
+ &nbsp;&nbsp;&nbsp;
+
+ <button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" type = "submit" >
+ Change or confirm email &rarr;
+ </button>
+
+</div>
+</form>
+</div>
+<div style='width: 50em' align="left">
+{% include 'login/register_text.html' %}
+
+
+<span style="color:red">
+{{ form.non_field_errors }} <!-- form validation errors appear here, and also at the top of the form-->
+</span>
+</div>
+
+{% endblock %}
diff --git a/templates/login/register_text.html b/templates/login/register_text.html
new file mode 100644
index 0000000..ac5c3ff
--- /dev/null
+++ b/templates/login/register_text.html
@@ -0,0 +1,48 @@
+<h3>Your name</h3>
+{% if newuser %}
+<p>Use the name you are usually known by "officially": this is usually the name on your passport.
+But if you habitually use your second forename, not the first forename, use that.
+Yes, you can put in any 'de', 'van' or 'von' or whatever 'nobiliary particle' you have, or a hyphenated surname; but this must be your real name.
+Nicknames and fun names are done later.
+{% else %}
+<p>For previous expoers, your username must be your 'troggle id' as listed on the <a href='/people_ids'>past expoers list</a>
+{%endif %}
+<p>Unfortunately cavers tend to use weird and playful names when signing up for things,
+so we can't automatically connect the troggle names and ids with the email addresses
+on the email list. And we don't believe in signing people up for things without their
+direct permission anyway.
+Having said that, when you register here we <em>will</em> sign you up automatically to
+the expo email list as that is how expo manages everything and it is a condition of
+coming on expo. (You can unsubscribe from the email list after expo.)
+
+<p>But the automatic sign-up to the email list is not working yet, and may not be before April 2025.
+So if you don't want to miss out on anything important, make sure you sign up to the
+<a href="https://lists.wookware.org/cgi-bin/mailman/roster/expo">email list</a>
+right now.
+
+<h3>Students !</h3>
+Please do not use an email address which will expire when you leave your current institution.
+This will happen much sooner than you can possibly believe. If you realise that you have done this on the email list,
+you can change it at the bottom of <a href="https://lists.wookware.org/cgi-bin/mailman/listinfo/expo">this page</a>.
+
+{% if newuser %}
+<h3>What happens next</h3>
+<p>Clicking the big blue button will send you an email which will contain a login token.
+Click on the link in the email and you will be able to set your own login password.
+Use this to login to troggle and go to the Expo Signup form.
+{% else %}
+{%endif %}
+
+<h3>Security note</h3>
+We never store passwords at all, we only store a cryptographic hash.
+We do store your email address but only 'in clear' inside the live database online
+where it is accessible only to the database administrators. There is no troggle report
+which publishes your email address.
+For permanent storage all email addresses are encrypted. Your real name and troggle
+username is public however, and we do not have anonymous people attending expo.
+<p>
+The password we {% if newuser %}will be{% else %}are{%endif %} asking for is solely for logging into troggle.
+The troggle login is used to track who is editing the current and past expo data, website content, and historic survey data, as well as for accessing the expo Kanban software. It is not the same as the password you use to access your email
+with your email provider
+and it is not the same as the password you use to interact with the expo
+<a href="https://lists.wookware.org/cgi-bin/mailman/roster/expo">email list</a>. \ No newline at end of file
diff --git a/urls.py b/urls.py
index 414557c..d873368 100644
--- a/urls.py
+++ b/urls.py
@@ -172,7 +172,7 @@ trogglepatterns = [
# NB setting url pattern name to 'login' instea dof 'expologin' with override Django, see https://docs.djangoproject.com/en/dev/topics/http/urls/#naming-url-patterns
path('accounts/logout/', expologout, name='expologout'), # same as in django.contrib.auth.urls
path('accounts/login/', expologin, name='expologin'), # same as in django.contrib.auth.urls
- path("accounts/register/<slug:username>", register, name="re_register"), # overriding django.contrib.auth.urls
+ path("accounts/register/<slug:url_username>", register, name="re_register"), # overriding django.contrib.auth.urls
path("accounts/register/", register, name="register"), # overriding django.contrib.auth.urls
path("accounts/newregister/", newregister, name="newregister"),
path("accounts/reset/done/", reset_done, name="password_reset_done"), # overriding django.contrib.auth.urls