mirror of
https://git.wownero.com/lza_menace/wowstash.git
synced 2024-08-15 00:33:15 +00:00
add reset password functionality
This commit is contained in:
parent
34d47ad39f
commit
a009450caa
6 changed files with 167 additions and 39 deletions
|
@ -3,8 +3,8 @@ from flask import request, render_template, session, redirect, url_for, flash
|
||||||
from flask_login import login_user, logout_user, current_user, login_required
|
from flask_login import login_user, logout_user, current_user, login_required
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from wowstash.blueprints.auth import auth_bp
|
from wowstash.blueprints.auth import auth_bp
|
||||||
from wowstash.forms import Register, Login, Delete
|
from wowstash.forms import Register, Login, Delete, ResetPassword
|
||||||
from wowstash.models import User
|
from wowstash.models import User, PasswordReset
|
||||||
from wowstash.factory import db, bcrypt
|
from wowstash.factory import db, bcrypt
|
||||||
from wowstash.library.docker import docker
|
from wowstash.library.docker import docker
|
||||||
from wowstash.library.helpers import capture_event
|
from wowstash.library.helpers import capture_event
|
||||||
|
@ -95,3 +95,29 @@ def delete():
|
||||||
else:
|
else:
|
||||||
flash('Please confirm deletion of the account')
|
flash('Please confirm deletion of the account')
|
||||||
return redirect(url_for('wallet.dashboard'))
|
return redirect(url_for('wallet.dashboard'))
|
||||||
|
|
||||||
|
@auth_bp.route("/reset/<string:hash>", methods=["GET", "POST"])
|
||||||
|
def reset(hash):
|
||||||
|
hash = PasswordReset.query.filter(PasswordReset.hash==hash).first()
|
||||||
|
if not hash:
|
||||||
|
flash('Invalid password reset hash')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
if hash.hours_elapsed() > hash.expiration_hours or hash.expired:
|
||||||
|
flash('Reset hash has expired')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
form = ResetPassword()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
try:
|
||||||
|
user = User.query.get(hash.user)
|
||||||
|
user.password = bcrypt.generate_password_hash(form.password.data).decode('utf8')
|
||||||
|
hash.expired = True
|
||||||
|
db.session.commit()
|
||||||
|
flash('Password reset successfully')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
except:
|
||||||
|
flash('Error resetting password')
|
||||||
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
|
return render_template('auth/reset.html', form=form)
|
||||||
|
|
|
@ -1,23 +1,43 @@
|
||||||
from wowstash.library.jsonrpc import wallet
|
import click
|
||||||
from wowstash.models import Transaction
|
from flask import Blueprint, url_for
|
||||||
from wowstash.factory import db
|
|
||||||
|
import wowstash.models
|
||||||
|
from wowstash.library.docker import docker
|
||||||
|
from wowstash.models import User, PasswordReset
|
||||||
|
from wowstash.factory import db, bcrypt
|
||||||
|
|
||||||
|
|
||||||
# @app.errorhandler(404)
|
bp = Blueprint("cli", "cli", cli_group=None)
|
||||||
def not_found(error):
|
|
||||||
return make_response(jsonify({
|
|
||||||
'error': 'Page not found'
|
|
||||||
}), 404)
|
|
||||||
|
|
||||||
# @app.cli.command('initdb')
|
|
||||||
def init_db():
|
@bp.cli.command('clean_containers')
|
||||||
|
def clean_containers():
|
||||||
|
docker.cleanup()
|
||||||
|
|
||||||
|
@bp.cli.command('reset_wallet')
|
||||||
|
@click.argument('user_id')
|
||||||
|
def reset_wallet(user_id):
|
||||||
|
user = User.query.get(user_id)
|
||||||
|
user.clear_wallet_data()
|
||||||
|
print(f'Wallet data cleared for user {user.id}')
|
||||||
|
|
||||||
|
@bp.cli.command('init')
|
||||||
|
def init():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
# @app.cli.command('send_transfers')
|
@bp.cli.command('reset_password')
|
||||||
def send_transfers():
|
@click.argument('user_email')
|
||||||
txes = Transaction.query.all()
|
@click.argument('duration')
|
||||||
for i in txes:
|
def reset_password(user_email, duration):
|
||||||
print(i)
|
user = User.query.filter(User.email==user_email).first()
|
||||||
# tx = wallet.transfer(
|
if not user:
|
||||||
# 0, current_user.subaddress_index, address, amount
|
click.echo('[!] Email address does not exist!')
|
||||||
# )
|
|
||||||
|
pwr = PasswordReset(
|
||||||
|
user=user.id,
|
||||||
|
hash=PasswordReset().generate_hash(),
|
||||||
|
expiration_hours=duration
|
||||||
|
)
|
||||||
|
db.session.add(pwr)
|
||||||
|
db.session.commit()
|
||||||
|
click.echo(f'[+] Password reset link #{pwr.id} for {user_email} expires in {duration} hours: {url_for("auth.reset", hash=pwr.hash)}')
|
||||||
|
|
|
@ -63,31 +63,14 @@ def create_app():
|
||||||
else:
|
else:
|
||||||
return float(atomic)
|
return float(atomic)
|
||||||
|
|
||||||
# CLI
|
|
||||||
@app.cli.command('clean_containers')
|
|
||||||
def clean_containers():
|
|
||||||
from wowstash.library.docker import docker
|
|
||||||
docker.cleanup()
|
|
||||||
|
|
||||||
@app.cli.command('reset_wallet')
|
|
||||||
@click.argument('user_id')
|
|
||||||
def reset_wallet(user_id):
|
|
||||||
from wowstash.models import User
|
|
||||||
user = User.query.get(user_id)
|
|
||||||
user.clear_wallet_data()
|
|
||||||
print(f'Wallet data cleared for user {user.id}')
|
|
||||||
|
|
||||||
@app.cli.command('init')
|
|
||||||
def init():
|
|
||||||
import wowstash.models
|
|
||||||
db.create_all()
|
|
||||||
|
|
||||||
# Routes/blueprints
|
# Routes/blueprints
|
||||||
from wowstash.blueprints.auth import auth_bp
|
from wowstash.blueprints.auth import auth_bp
|
||||||
from wowstash.blueprints.wallet import wallet_bp
|
from wowstash.blueprints.wallet import wallet_bp
|
||||||
from wowstash.blueprints.meta import meta_bp
|
from wowstash.blueprints.meta import meta_bp
|
||||||
|
from wowstash.cli import bp as cli_bp
|
||||||
app.register_blueprint(meta_bp)
|
app.register_blueprint(meta_bp)
|
||||||
app.register_blueprint(auth_bp)
|
app.register_blueprint(auth_bp)
|
||||||
app.register_blueprint(wallet_bp)
|
app.register_blueprint(wallet_bp)
|
||||||
|
app.register_blueprint(cli_bp)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -32,3 +32,11 @@ class Restore(FlaskForm):
|
||||||
raise ValidationError('Invalid seed provided; must be alphanumeric characters only')
|
raise ValidationError('Invalid seed provided; must be alphanumeric characters only')
|
||||||
if len(self.seed.data.split()) != 25:
|
if len(self.seed.data.split()) != 25:
|
||||||
raise ValidationError("Invalid seed provided; must be standard Wownero 25 word format")
|
raise ValidationError("Invalid seed provided; must be standard Wownero 25 word format")
|
||||||
|
|
||||||
|
class ResetPassword(FlaskForm):
|
||||||
|
password = StringField('Password', validators=[DataRequired()], render_kw={"placeholder": "Password", "type": "password"})
|
||||||
|
password_confirmed = StringField('Confirm Password', validators=[DataRequired()], render_kw={"placeholder": "Confirm Password", "type": "password"})
|
||||||
|
|
||||||
|
def validate_password(self, password):
|
||||||
|
if self.password.data != self.password_confirmed.data:
|
||||||
|
raise ValidationError('Passwords do not match')
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from os import kill
|
from os import kill
|
||||||
|
from datetime import datetime
|
||||||
|
from secrets import token_urlsafe
|
||||||
from sqlalchemy import Column, Integer, DateTime, String, ForeignKey
|
from sqlalchemy import Column, Integer, DateTime, String, ForeignKey
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
@ -65,3 +67,25 @@ class Event(db.Model):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordReset(db.Model):
|
||||||
|
__tablename__ = 'password_reset'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user = db.Column(db.Integer, db.ForeignKey(User.id))
|
||||||
|
date = db.Column(db.DateTime, server_default=func.now())
|
||||||
|
hash = db.Column(db.String(80))
|
||||||
|
expiration_hours = db.Column(db.Integer)
|
||||||
|
expired = db.Column(db.Boolean, default=False)
|
||||||
|
|
||||||
|
def generate_hash(self):
|
||||||
|
return token_urlsafe(16)
|
||||||
|
|
||||||
|
def hours_elapsed(self):
|
||||||
|
now = datetime.utcnow()
|
||||||
|
diff = now - self.date
|
||||||
|
return diff.total_seconds() / 60 / 60
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.id
|
||||||
|
|
67
wowstash/templates/auth/reset.html
Normal file
67
wowstash/templates/auth/reset.html
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
{% include 'head.html' %}
|
||||||
|
|
||||||
|
<body id="page-top">
|
||||||
|
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
|
<!-- <header class="masthead">
|
||||||
|
<div class="container h-100">
|
||||||
|
<div class="row h-100">
|
||||||
|
<div class="col-lg-12 my-auto">
|
||||||
|
<div class="header-content mx-auto">
|
||||||
|
<h1 class="mb-4">Reset your password</h1>
|
||||||
|
<p>Wownero is a privacy centric cryptocurrency and is most safely managed on your own personal devices, on your own network, and with your own copy of the blockchain. This is a publicly accessible website, and while strict security measures are implemented on our servers, your account's security <strong>cannot</strong> be guaranteed. </p>
|
||||||
|
<p>If you decide to use this site for managing your funds, you do so at your own risk and are bound by the terms and conditions of this site. Practice good operational security and do not use this site for large amounts of funds.</p>
|
||||||
|
<div>
|
||||||
|
<a href="{{ url_for('meta.faq') }}">FAQ</a> -
|
||||||
|
<a href="{{ url_for('meta.terms') }}">Terms</a> -
|
||||||
|
<a href="{{ url_for('meta.privacy') }}">Privacy</a>
|
||||||
|
</div><br>
|
||||||
|
<a href="#register" class="btn btn-outline btn-xl js-scroll-trigger">Proceed</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header> -->
|
||||||
|
|
||||||
|
<section class="section1" id="reset">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-heading text-center">
|
||||||
|
<form method="POST" action="">
|
||||||
|
{{ form.csrf_token }}
|
||||||
|
{% for f in form %}
|
||||||
|
{% if f.name != 'csrf_token' %}
|
||||||
|
{% if f.type == 'BooleanField' %}
|
||||||
|
<div class="form-group-span">
|
||||||
|
{{ f.label }}
|
||||||
|
{{ f }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ f.label }}
|
||||||
|
{{ f }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<ul>
|
||||||
|
{% for field, errors in form.errors.items() %}
|
||||||
|
<li>{{ form[field].label }}: {{ ', '.join(errors) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
<input type="submit" value="Reset" class="btn btn-link btn-outline-inverse btn-xl">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
|
{% include 'scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in a new issue