mirror of
https://github.com/simon987/nyaa.git
synced 2025-12-20 10:05:58 +00:00
[Config change] Password reset by email (#381)
* Password reset by email Adds endpoint, templates, email templates, forms * Timeout password reset request in six hours
This commit is contained in:
committed by
GitHub
parent
6d09920abd
commit
9e87e810af
@@ -1,3 +1,5 @@
|
||||
import binascii
|
||||
import time
|
||||
from datetime import datetime
|
||||
from ipaddress import ip_address
|
||||
|
||||
@@ -5,7 +7,8 @@ import flask
|
||||
|
||||
from nyaa import email, forms, models
|
||||
from nyaa.extensions import db
|
||||
from nyaa.views.users import get_activation_link
|
||||
from nyaa.utils import sha1_hash
|
||||
from nyaa.views.users import get_activation_link, get_password_reset_link, get_serializer
|
||||
|
||||
app = flask.current_app
|
||||
bp = flask.Blueprint('account', __name__)
|
||||
@@ -95,6 +98,61 @@ def register():
|
||||
return flask.render_template('register.html', form=form)
|
||||
|
||||
|
||||
@bp.route('/password-reset/<payload>', methods=['GET', 'POST'])
|
||||
@bp.route('/password-reset', methods=['GET', 'POST'])
|
||||
def password_reset(payload=None):
|
||||
if not app.config['ALLOW_PASSWORD_RESET']:
|
||||
return flask.abort(404)
|
||||
|
||||
if flask.g.user:
|
||||
return flask.redirect(redirect_url())
|
||||
|
||||
if payload is None:
|
||||
form = forms.PasswordResetRequestForm(flask.request.form)
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
user = models.User.by_email(form.email.data.strip())
|
||||
if user:
|
||||
send_password_reset_request_email(user)
|
||||
|
||||
flask.flash(flask.Markup(
|
||||
'A password reset request was sent to the provided email, '
|
||||
'if a matching account was found.'), 'info')
|
||||
return flask.redirect(flask.url_for('main.home'))
|
||||
return flask.render_template('password_reset_request.html', form=form)
|
||||
|
||||
else:
|
||||
s = get_serializer()
|
||||
try:
|
||||
request_timestamp, pw_hash, user_id = s.loads(payload)
|
||||
except:
|
||||
return flask.abort(404)
|
||||
|
||||
user = models.User.by_id(user_id)
|
||||
if not user:
|
||||
return flask.abort(404)
|
||||
|
||||
# Timeout after six hours
|
||||
if (time.time() - request_timestamp) > 6 * 3600:
|
||||
return flask.abort(404)
|
||||
|
||||
sha1_password_hash_hash = binascii.hexlify(sha1_hash(user.password_hash.hash)).decode()
|
||||
if pw_hash != sha1_password_hash_hash:
|
||||
return flask.abort(404)
|
||||
|
||||
form = forms.PasswordResetForm(flask.request.form)
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
user.password_hash = form.password.data
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
send_password_reset_email(user)
|
||||
|
||||
flask.flash(flask.Markup('Your password was reset. Log in now.'), 'info')
|
||||
return flask.redirect(flask.url_for('account.login'))
|
||||
return flask.render_template('password_reset.html', form=form)
|
||||
|
||||
|
||||
@bp.route('/profile', methods=['GET', 'POST'])
|
||||
def profile():
|
||||
if not flask.g.user:
|
||||
@@ -162,3 +220,35 @@ def send_verification_email(user):
|
||||
)
|
||||
|
||||
email.send_email(email_msg)
|
||||
|
||||
|
||||
def send_password_reset_email(user):
|
||||
''' Alert user that their password has been successfully reset '''
|
||||
|
||||
email_msg = email.EmailHolder(
|
||||
subject='Your {} password has been reset'.format(app.config['GLOBAL_SITE_NAME']),
|
||||
recipient=user,
|
||||
text=flask.render_template('email/reset.txt', user=user),
|
||||
html=flask.render_template('email/reset.html', user=user),
|
||||
)
|
||||
|
||||
email.send_email(email_msg)
|
||||
|
||||
|
||||
def send_password_reset_request_email(user):
|
||||
''' Send user a password reset link '''
|
||||
reset_link = get_password_reset_link(user)
|
||||
|
||||
tmpl_context = {
|
||||
'reset_link': reset_link,
|
||||
'user': user
|
||||
}
|
||||
|
||||
email_msg = email.EmailHolder(
|
||||
subject='{} password reset request'.format(app.config['GLOBAL_SITE_NAME']),
|
||||
recipient=user,
|
||||
text=flask.render_template('email/reset-request.txt', **tmpl_context),
|
||||
html=flask.render_template('email/reset-request.html', **tmpl_context),
|
||||
)
|
||||
|
||||
email.send_email(email_msg)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import binascii
|
||||
import math
|
||||
import time
|
||||
from ipaddress import ip_address
|
||||
|
||||
import flask
|
||||
@@ -10,7 +12,7 @@ from nyaa import forms, models
|
||||
from nyaa.extensions import db
|
||||
from nyaa.search import (DEFAULT_MAX_SEARCH_RESULT, DEFAULT_PER_PAGE, SERACH_PAGINATE_DISPLAY_MSG,
|
||||
_generate_query_string, search_db, search_elastic)
|
||||
from nyaa.utils import chain_get
|
||||
from nyaa.utils import chain_get, sha1_hash
|
||||
|
||||
app = flask.current_app
|
||||
bp = flask.Blueprint('users', __name__)
|
||||
@@ -251,3 +253,13 @@ def get_activation_link(user):
|
||||
s = get_serializer()
|
||||
payload = s.dumps(user.id)
|
||||
return flask.url_for('users.activate_user', payload=payload, _external=True)
|
||||
|
||||
|
||||
def get_password_reset_link(user):
|
||||
# This mess to not to have static password reset links
|
||||
# Maybe not the best idea? But this should not be a security risk, and it works.
|
||||
password_hash_hash = binascii.hexlify(sha1_hash(user.password_hash.hash)).decode()
|
||||
|
||||
s = get_serializer()
|
||||
payload = s.dumps((time.time(), password_hash_hash, user.id))
|
||||
return flask.url_for('account.password_reset', payload=payload, _external=True)
|
||||
|
||||
Reference in New Issue
Block a user