From 5536e764e7be92a690b6235adc80234344fb0e93 Mon Sep 17 00:00:00 2001 From: Mattia Succurro Date: Thu, 24 Oct 2019 18:27:53 +0200 Subject: [PATCH] Added password change form --- CHANGELOG.md | 4 ++ app/templates/change_password.html | 17 ++++++++ app/templates/confirm_delete.html | 22 ++++++++++ .../{private_messages.html => feed.html} | 0 app/templates/includes/message.html | 2 +- app/templates/user_list.html | 0 app/utils.py | 34 +++++++++++++-- app/website.py | 42 ++++++++++++++++--- 8 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 app/templates/change_password.html create mode 100644 app/templates/confirm_delete.html rename app/templates/{private_messages.html => feed.html} (100%) mode change 100755 => 100644 app/templates/user_list.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 0398a37..82fcb82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * Now `/about/` shows Python and Flask versions. * Now the error 404 handler returns HTTP 404. * Added user followers and following lists, accessible via `/+/followers` and `/+/following` and from the profile info box, linked to the followers/following number. +* Added the page for permanent deletion of messages. Well, you cannot delete them yet. It's missing a function that checks the CSRF-Token. +* Renamed template `private_messages.html` to `feed.html`. +* Added the capability to change password. +* Corrected a bug into `pwdhash`: it accepted an argument, but pulled data from the form instead of processing it. Now it uses the argument. * Schema changes: added column `telegram` to `UserProfile` table. To update schema, execute the script `migrate_0_6_to_0_7.py` ## 0.6.0 diff --git a/app/templates/change_password.html b/app/templates/change_password.html new file mode 100644 index 0000000..afd4e28 --- /dev/null +++ b/app/templates/change_password.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block body %} +

Change Password

+ +
+
+
Old password:
+
+
New password:
+
+
New password, again:
+
+
+
+
+{% endblock %} diff --git a/app/templates/confirm_delete.html b/app/templates/confirm_delete.html new file mode 100644 index 0000000..b48919c --- /dev/null +++ b/app/templates/confirm_delete.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block body %} +

Confirm Deletion

+ +

Are you sure you want to permanently delete this post? + Neither you nor others will be able to see it; + you cannot recover a post after it's deleted.

+ +

If you only want to hide it from the public, + you can set its privacy to "Only me".

+ +

Here's the content of the message for reference:

+ +
    +
  • {% include "includes/message.html" %}
  • +
+ +
+ +
+{% endblock %} diff --git a/app/templates/private_messages.html b/app/templates/feed.html similarity index 100% rename from app/templates/private_messages.html rename to app/templates/feed.html diff --git a/app/templates/includes/message.html b/app/templates/includes/message.html index fc8a91f..59369c9 100644 --- a/app/templates/includes/message.html +++ b/app/templates/includes/message.html @@ -20,7 +20,7 @@
    {% if message.user == current_user %}
  • Edit or change privacy
  • - +
  • Delete permanently
  • {% else %} {% endif %} diff --git a/app/templates/user_list.html b/app/templates/user_list.html old mode 100755 new mode 100644 diff --git a/app/utils.py b/app/utils.py index f6370a2..8f88ac3 100644 --- a/app/utils.py +++ b/app/utils.py @@ -2,10 +2,9 @@ A list of utilities used across modules. ''' -import datetime, re, base64, hashlib, string +import datetime, re, base64, hashlib, string, sys, json from .models import User, Notification from flask import abort, render_template, request, session -import sys, json _forbidden_extensions = 'com net org txt'.split() _username_characters = frozenset(string.ascii_letters + string.digits + '_') @@ -43,7 +42,7 @@ def int_to_b64(n): return base64.b64encode(b).lstrip(b'A').decode() def pwdhash(s): - return hashlib.md5((request.form['password']).encode('utf-8')).hexdigest() + return hashlib.md5(s.encode('utf-8')).hexdigest() def get_object_or_404(model, *expressions): try: @@ -160,3 +159,32 @@ def tokenize(characters, table): break pos = mo.end(0) return tokens + +def get_secret_key(): + from . import app + secret_key = app.config['SECRET_KEY'] + if isinstance(secret_key, str): + secret_key = secret_key.encode('utf-8') + return secret_key + +def generate_access_token(user): + ''' + Generate access token for public API. + ''' + h = hashlib.sha256(get_secret_key()) + h.update(b':') + h.update(str(user.id).encode('utf-8')) + h.update(b':') + h.update(str(user.password).encode('utf-8')) + return str(user.id) + ':' + h.hexdigest()[:32] + +def check_access_token(user, token): + uid, hh = token.split(':') + if uid != user.get_id(): + return False + h = hashlib.sha256(get_secret_key()) + h.update(b':') + h.update(str(user.id).encode('utf-8')) + h.update(b':') + h.update(str(user.password).encode('utf-8')) + return h.hexdigest()[:32] == hh diff --git a/app/website.py b/app/website.py index e463d37..b5f8ee7 100644 --- a/app/website.py +++ b/app/website.py @@ -29,8 +29,7 @@ def private_timeline(): .where((Message.user << user.following()) | (Message.user == user)) .order_by(Message.pub_date.desc())) - # TODO change to "feed.html" - return object_list('private_messages.html', messages, 'message_list') + return object_list('feed.html', messages, 'message_list') @bp.route('/explore/') def public_timeline(): @@ -246,9 +245,15 @@ def edit(id): return redirect(url_for('website.user_detail', username=user.username)) return render_template('edit.html', message=message) -#@bp.route('/delete/', methods=['GET', 'POST']) -#def confirm_delete(id): -# return render_template('confirm_delete.html') +@bp.route('/delete/', methods=['GET', 'POST']) +def confirm_delete(id): + user = get_current_user() + message = get_object_or_404(Message, Message.id == id) + if message.user != user: + abort(404) + if request.method == 'POST': + abort(501, 'CSRF-Token missing.') + return render_template('confirm_delete.html', message=message) # Workaround for problems related to invalid data. # Without that, changes will be lost across requests. @@ -261,7 +266,8 @@ def profile_checkpoint(): year=int(request.form['year'] if request.form.get('has_year') else '0'), website=request.form['website'] or None, instagram=request.form['instagram'] or None, - facebook=request.form['facebook'] or None + facebook=request.form['facebook'] or None, + telegram=request.form['telegram'] or None ) @bp.route('/edit_profile/', methods=['GET', 'POST']) @@ -299,6 +305,30 @@ def edit_profile(): return redirect(url_for('website.user_detail', username=username)) return render_template('edit_profile.html') +@bp.route('/change_password/', methods=['GET', 'POST']) +def change_password(): + user = get_current_user() + if request.method == 'POST': + old_password = request.form['old_password'] + new_password = request.form['new_password'] + confirm_password = request.form['confirm_password'] + errors = False + if not new_password: + flash('Password cannot be empty') + errors = True + if new_password != confirm_password: + flash('Password mismatch') + errors = True + if pwdhash(old_password) != user.password: + flash('The old password is incorrect') + errors = True + if not errors: + user.update( + password=pwdhash(new_password) + ) + return redirect(url_for('website.edit_profile')) + return render_template('change_password.html') + @bp.route('/notifications/') @login_required def notifications():