password reset via encrypted token by email

This commit is contained in:
2025-01-22 23:03:01 +00:00
parent a5341c4eb2
commit 173ee2348f
5 changed files with 53 additions and 22 deletions

View File

@@ -121,9 +121,10 @@ def controlpanel(request):
jobs_completed = [] jobs_completed = []
def process_imports(): def process_imports():
"""databaseReset.py """databaseReset.py - CHECK THIS IS ALL CORRECT& CURRENT IF YOU RE-ENABLE IT
jq.enq("reinit",reinit_db) jq.enq("reinit",reinit_db)
jq.enq("caves",import_caves) jq.enq("caves",import_caves)
jq.enq("users",import_users)
jq.enq("people",import_people) jq.enq("people",import_people)
jq.enq("scans",import_surveyscans) jq.enq("scans",import_surveyscans)
jq.enq("logbooks",import_logbooks) jq.enq("logbooks",import_logbooks)
@@ -136,6 +137,7 @@ def controlpanel(request):
jobs_completed.append("Caves") jobs_completed.append("Caves")
if request.POST.get("import_people", False): if request.POST.get("import_people", False):
import_people() import_people()
jobs_completed.append("People") jobs_completed.append("People")
if request.POST.get("import_surveyscans", False): if request.POST.get("import_surveyscans", False):
import_surveyscans() import_surveyscans()
@@ -187,7 +189,7 @@ def exportlogbook(request, year=None):
There are no images stored in the database. However links to images work in the HTML text of a logbook entry. There are no images stored in the database. However links to images work in the HTML text of a logbook entry.
This function is the recipient of the POST action as the export form in the control panel This function is the recipient of the POST action as the export form in the control panel (now disabled).
""" """
def lbeKey(lbe): def lbeKey(lbe):

View File

@@ -23,7 +23,23 @@ todo = """
- login automatically, and redirect to control panel ? - login automatically, and redirect to control panel ?
""" """
def reset_done(request):
"""This page is called when a password reset has successively occured
Unfortunately by this point, we do not know the name of the user who initiated the
password reset, so when we do the git commit of the encrypted users file
we do not have a name to put to the responsible person. To do that,
we would have to intercept at the previous step, the url:
"reset/<uidb64>/<token>/",
views.PasswordResetConfirmView.as_view(),
and this class-based view is a lot more complicated to replace or sub-class.
Currently we are doing the git commit anonymously.. though I guess we could attempt to
read the cookie... if it is set.
"""
current_user = request.user
save_users(request, current_user)
return HttpResponseRedirect("/accounts/login/")
def register(request, username=None): def register(request, username=None):
"""To register a new user on the troggle system, similar to the "expo" user """To register a new user on the troggle system, similar to the "expo" user
(with cavey:beery password) but specific to an individual (with cavey:beery password) but specific to an individual
@@ -46,7 +62,7 @@ def register(request, username=None):
# return render(request, "login/register.html", {"form": form, "unauthorized": True}) # 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: # 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) updated_user = register_user(un, email, password=pw, pwhash=None)
save_users(request, updated_user, email) save_users(request, updated_user)
# to do, login automatically, and redirect to control panel ? # to do, login automatically, and redirect to control panel ?
return HttpResponseRedirect("/accounts/login/") return HttpResponseRedirect("/accounts/login/")
else: else:
@@ -63,9 +79,12 @@ def register(request, username=None):
return render(request, "login/register.html", {"form": form, "warning": warning}) return render(request, "login/register.html", {"form": form, "warning": warning})
def save_users(request, updated_user, email):
def save_users(request, updated_user):
f = get_encryptor() f = get_encryptor()
ru = [] ru = []
print(f"\n + Saving users, encrypted emails, and password hashes") print(f"\n + Saving users, encrypted emails, and password hashes")
for u in User.objects.all(): for u in User.objects.all():
if u.username in ["expo", "expoadmin"]: if u.username in ["expo", "expoadmin"]:
@@ -76,29 +95,32 @@ def save_users(request, updated_user, email):
original = f.decrypt(e_email).decode() original = f.decrypt(e_email).decode()
print(f" - {u.username} - {original}") print(f" - {u.username} - {original}")
if updated_user.is_anonymous:
git_string = f"troggle <troggle@exposerver.expo>"
else:
git_string = f"{updated_user.username} <{email}>"
encryptedfile = settings.EXPOWEB / ENCRYPTED_DIR / USERS_FILE encryptedfile = settings.EXPOWEB / ENCRYPTED_DIR / USERS_FILE
try: try:
print(f"- Rewriting the entire encrypted set of registered users to disc ") print(f"- Rewriting the entire encrypted set of registered users to disc ")
write_users(ru, encryptedfile, updated_user, email) write_users(ru, encryptedfile, git_string)
except: except:
message = f'! - Users encrypted data saving failed - \n!! Permissions failure ?! on attempting to save file "{encryptedfile}"' message = f'! - Users encrypted data saving failed - \n!! Permissions failure ?! on attempting to save file "{encryptedfile}"'
print(message) print(message)
raise
return render(request, "errors/generic.html", {"message": message}) return render(request, "errors/generic.html", {"message": message})
def write_users(registered_users, encryptedfile, updated_user, email): def write_users(registered_users, encryptedfile, git_string):
jsondict = { "registered_users": registered_users } jsondict = { "registered_users": registered_users }
try: try:
if settings.DEVSERVER: with open(encryptedfile, 'w', encoding='utf-8') as json_f:
with open(encryptedfile, 'w', encoding='utf-8') as json_f: json.dump(jsondict, json_f, indent=1)
json.dump(jsondict, json_f, indent=1)
except Exception as e: except Exception as e:
print(f" ! Exception dumping json <{e}>") print(f" ! Exception dumping json <{e}>")
raise raise
commit_msg = f"Online (re-)registration of a troggle User" commit_msg = f"Online (re-)registration of a troggle User"
editor = f"{updated_user.username} <{email}>"
try: try:
add_commit(encryptedfile, commit_msg, editor) add_commit(encryptedfile, commit_msg, git_string)
except Exception as e: except Exception as e:
print(f" ! Exception doing git add/commit <{e}>") print(f" ! Exception doing git add/commit <{e}>")
raise raise

View File

@@ -86,6 +86,7 @@ SMART_APPEND_SLASH = True # not eorking as middleware different after Dj2.0
ROOT_URLCONF = "troggle.urls" ROOT_URLCONF = "troggle.urls"
LOGOUT_REDIRECT_URL = "/statistics" # see troggle/core/views/auth.py LOGOUT_REDIRECT_URL = "/statistics" # see troggle/core/views/auth.py
LOGIN_REDIRECT_URL = "/controlpanel" # see troggle/core/views/auth.py LOGIN_REDIRECT_URL = "/controlpanel" # see troggle/core/views/auth.py
PASSWORD_RESET_TIMEOUT = 3*60*60 # password reset sends an email. The response is valid for 3 hours
SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True SECURE_BROWSER_XSS_FILTER = True
@@ -97,7 +98,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # from Django 3.2
INSTALLED_APPS = ( INSTALLED_APPS = (
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", # includes the url redirections for login, logout "django.contrib.auth", # includes the url redirections for login, logout, password_reset etc.
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",

View File

@@ -64,10 +64,15 @@ So type in the same email address that you use there.
<div class='align-right'> <div class='align-right'>
<input type="checkbox" checked name="visible" onclick="myFunction()">Make Passwords visible (on this form only) <input type="checkbox" checked name="visible" onclick="myFunction()">Make Passwords visible (on this form only)
<br /><br /> <br /><br />
<button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" type = "submit" value = "Go to" > <button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;"
Register &rarr; onclick="window.location.href='/accounts/password_reset/'" value = "Go to" >
</button> Reset password
</button>
&nbsp;&nbsp;&nbsp;
<button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" type = "submit" >
Register &rarr;
</button>
</div> </div>
</form> </form>
</div> </div>

11
urls.py
View File

@@ -53,7 +53,7 @@ from troggle.core.views.logbooks import (
) )
from troggle.core.views.other import controlpanel, exportlogbook, frontpage, todos from troggle.core.views.other import controlpanel, exportlogbook, frontpage, todos
from troggle.core.views.prospect import prospecting from troggle.core.views.prospect import prospecting
from troggle.core.views.user_registration import register from troggle.core.views.user_registration import register, reset_done
from troggle.core.views.scans import allscans, cavewallets, scansingle, walletslistperson, walletslistyear from troggle.core.views.scans import allscans, cavewallets, scansingle, walletslistperson, walletslistyear
from troggle.core.views.signup import signup from troggle.core.views.signup import signup
from troggle.core.views.uploads import dwgupload, expofilerename, gpxupload, photoupload from troggle.core.views.uploads import dwgupload, expofilerename, gpxupload, photoupload
@@ -93,7 +93,7 @@ todo = '''
# WHen running on the server, apache intercepts all the /expofiles/ files so troggle never sees them, # WHen running on the server, apache intercepts all the /expofiles/ files so troggle never sees them,
# so the "content type" is set by whatever apache thinks it should be. Which means .gpx files # so the "content type" is set by whatever apache thinks it should be. Which means .gpx files
# get treated as XML and the web browser fails to do anything usefull # get treated as XML and the web browser fails to do anything useful
if settings.EXPOFILESREMOTE: if settings.EXPOFILESREMOTE:
expofilesurls = [ expofilesurls = [
@@ -168,9 +168,10 @@ 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 # 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/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/login/', expologin, name='expologin'), # same as in django.contrib.auth.urls
path("accounts/register/<slug:username>", register, name="re_register"), path("accounts/register/<slug:username>", register, name="re_register"), # overriding django.contrib.auth.urls
path("accounts/register/", register, name="register"), path("accounts/register/", register, name="register"), # overriding django.contrib.auth.urls
path('accounts/', include('django.contrib.auth.urls')), # see line 109 in this file path("accounts/reset/done/", reset_done, name="password_reset_done"), # overriding django.contrib.auth.urls
path('accounts/', include('django.contrib.auth.urls')), # see line 109 in this file NB initial "/accounts/" in URL
path('person/<slug:slug>', person, name="person"), path('person/<slug:slug>', person, name="person"),
path('personexpedition/<slug:slug>/<int:year>', personexpedition, name="personexpedition"), path('personexpedition/<slug:slug>/<int:year>', personexpedition, name="personexpedition"),