From 59ea26b8f114d0f733b889a44cb543f266a0cbfd Mon Sep 17 00:00:00 2001 From: lza_menace Date: Wed, 26 Aug 2020 22:50:08 -0700 Subject: [PATCH] implement registration, login, starting wallet dashboard --- requirements.txt | 1 + wowstash/blueprints/__init__.py | 2 +- wowstash/blueprints/account/routes.py | 28 ------- wowstash/blueprints/authentication/routes.py | 76 ++++++++++++++++--- .../{account => wallet}/__init__.py | 2 +- wowstash/blueprints/wallet/routes.py | 21 +++++ wowstash/config.example.py | 2 +- wowstash/factory.py | 16 +++- wowstash/forms.py | 9 ++- wowstash/library/jsonrpc.py | 39 +++++++++- wowstash/models.py | 25 ++++-- .../{account.html => account/dashboard.html} | 57 ++++++++++---- wowstash/templates/authentication/login.html | 38 +++++----- wowstash/templates/index.html | 4 + wowstash/templates/navbar.html | 8 +- 15 files changed, 241 insertions(+), 87 deletions(-) delete mode 100644 wowstash/blueprints/account/routes.py rename wowstash/blueprints/{account => wallet}/__init__.py (53%) create mode 100644 wowstash/blueprints/wallet/routes.py rename wowstash/templates/{account.html => account/dashboard.html} (56%) diff --git a/requirements.txt b/requirements.txt index bcce1c1..eee54ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ requests Flask-WTF flask_sqlalchemy flask-bcrypt +flask-login diff --git a/wowstash/blueprints/__init__.py b/wowstash/blueprints/__init__.py index ea7c7f6..b99d6a4 100644 --- a/wowstash/blueprints/__init__.py +++ b/wowstash/blueprints/__init__.py @@ -1,3 +1,3 @@ -from .account import account_bp +from .wallet import wallet_bp from .authentication import authentication_bp from .meta import meta_bp diff --git a/wowstash/blueprints/account/routes.py b/wowstash/blueprints/account/routes.py deleted file mode 100644 index 458f0cf..0000000 --- a/wowstash/blueprints/account/routes.py +++ /dev/null @@ -1,28 +0,0 @@ -from flask import request, render_template, session -from flask import redirect, url_for, current_app -from wowstash.blueprints.account import account_bp - - -@account_bp.route("/account") -def overview(): - if session.get("public_address"): - return render_template("account.html", - session_data=session, - h=daemon.get_height(), - wallet=wallet) - else: - return redirect(url_for("index")) - -@account_bp.route("/account/wallet") -def connect_wallet(): - if session.get("public_address"): - wallet.init(host=current_app.config['DAEMON_HOST'], - port=current_app.config['DAEMON_PORT'], - public_view_key=session['public_view_key'], - wallet_password=session['wallet_password'], - mnemonic_seed=session['seed'], - restore_height=daemon.get_height(), - path=current_app.config['BINARY_PATH']) - return redirect(url_for("account.overview")) - else: - return redirect(url_for("index")) diff --git a/wowstash/blueprints/authentication/routes.py b/wowstash/blueprints/authentication/routes.py index 5614cd6..b51e5d3 100644 --- a/wowstash/blueprints/authentication/routes.py +++ b/wowstash/blueprints/authentication/routes.py @@ -1,27 +1,79 @@ -from flask import request, render_template, session, redirect, url_for +from flask import request, render_template, session, redirect, url_for, flash +from flask_login import login_user, logout_user, current_user from wowstash.blueprints.authentication import authentication_bp -from wowstash.forms import Register +from wowstash.forms import Register, Login from wowstash.models import User +from wowstash.library.jsonrpc import wallet +from wowstash.factory import db, bcrypt @authentication_bp.route("/register", methods=["GET", "POST"]) def register(): form = Register() + if current_user.is_authenticated: + flash('Already registered and authenticated.') + return redirect(url_for('wallet.dashboard')) + if form.validate_on_submit(): - print(dir(User)) - # user = User.query - user = User.objects.filter(email=form.email.data) - print(user) - return "ok" - else: - print(form) + # Check if Wownero wallet is available + if wallet.connected is False: + flash('Wallet RPC interface is unavailable at this time. Try again later.') + return redirect(url_for('authentication.register')) + + # Check if email already exists + user = User.query.filter_by(email=form.email.data).first() + if user: + flash('This email is already registered.') + return redirect(url_for('authentication.login')) + + # Create new subaddress + subaddress = wallet.new_address(label=form.email.data) + + # Save new user + user = User( + email=form.email.data, + password=bcrypt.generate_password_hash(form.password.data).decode('utf8'), + subaddress_index=subaddress[0] + ) + db.session.add(user) + db.session.commit() + + # Login user and redirect to wallet page + login_user(user) + return redirect(url_for('wallet.dashboard')) + return render_template("authentication/register.html", form=form) @authentication_bp.route("/login", methods=["GET", "POST"]) def login(): - return render_template("authentication/login.html") + form = Login() + if current_user.is_authenticated: + flash('Already registered and authenticated.') + return redirect(url_for('wallet.dashboard')) + + if form.validate_on_submit(): + # Check if user doesn't exist + user = User.query.filter_by(email=form.email.data).first() + if not user: + flash('Invalid username or password.') + return redirect(url_for('authentication.login')) + + # Check if password is correct + password_matches = bcrypt.check_password_hash( + user.password, + form.password.data + ) + if not password_matches: + flash('Invalid username or password.') + return redirect(url_for('authentication.login')) + + # Login user and redirect to wallet page + login_user(user) + return redirect(url_for('wallet.dashboard')) + + return render_template("authentication/login.html", form=form) @authentication_bp.route("/logout") def logout(): - session.clear() - return redirect(url_for('index')) + logout_user() + return redirect(url_for('meta.index')) diff --git a/wowstash/blueprints/account/__init__.py b/wowstash/blueprints/wallet/__init__.py similarity index 53% rename from wowstash/blueprints/account/__init__.py rename to wowstash/blueprints/wallet/__init__.py index cbf19d4..da1dde6 100644 --- a/wowstash/blueprints/account/__init__.py +++ b/wowstash/blueprints/wallet/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -account_bp = Blueprint("account", __name__) +wallet_bp = Blueprint("wallet", __name__) from . import routes diff --git a/wowstash/blueprints/wallet/routes.py b/wowstash/blueprints/wallet/routes.py new file mode 100644 index 0000000..793eaa4 --- /dev/null +++ b/wowstash/blueprints/wallet/routes.py @@ -0,0 +1,21 @@ +from flask import request, render_template, session, redirect, url_for, current_app +from flask_login import login_required, current_user +from wowstash.blueprints.wallet import wallet_bp +from wowstash.library.jsonrpc import wallet, daemon +from wowstash.factory import login_manager +from wowstash.models import User + + +@wallet_bp.route("/wallet/dashboard") +@login_required +def dashboard(): + user = User.query.get(current_user.id) + wallet_height = wallet.height()['height'] + daemon_height = daemon.height()['height'] + subaddress = wallet.get_address(0, user.subaddress_index)['addresses'][0]['address'] + return render_template( + "account/dashboard.html", + wallet_height=wallet_height, + daemon=daemon_height, + subaddress=subaddress + ) diff --git a/wowstash/config.example.py b/wowstash/config.example.py index 34e2381..63385b4 100644 --- a/wowstash/config.example.py +++ b/wowstash/config.example.py @@ -11,7 +11,7 @@ DAEMON_PASS = '' # Wallet WALLET_PROTO = 'http' WALLET_HOST = 'localhost' -WALLET_PORT = 8888 +WALLET_PORT = 9999 WALLET_USER = 'yyyyy' WALLET_PASS = 'xxxxx' diff --git a/wowstash/factory.py b/wowstash/factory.py index 8528b35..b9598f4 100644 --- a/wowstash/factory.py +++ b/wowstash/factory.py @@ -2,6 +2,7 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_session import Session from flask_bcrypt import Bcrypt +from flask_login import LoginManager from redis import Redis from wowstash import config @@ -40,6 +41,8 @@ def _setup_bcrypt(app: Flask): def create_app(): global app global db + global bcrypt + global login_manager app = Flask(__name__) app.config.from_envvar('FLASK_SECRETS') app.secret_key = app.config['SECRET_KEY'] @@ -49,13 +52,22 @@ def create_app(): _setup_session(app) _setup_bcrypt(app) + login_manager = LoginManager() + login_manager.init_app(app) + login_manager.login_view = 'authentication.login' + + @login_manager.user_loader + def load_user(user_id): + from wowstash.models import User + return User.query.get(user_id) + # Routes from wowstash.blueprints.authentication import authentication_bp - from wowstash.blueprints.account import account_bp + from wowstash.blueprints.wallet import wallet_bp from wowstash.blueprints.meta import meta_bp app.register_blueprint(meta_bp) app.register_blueprint(authentication_bp) - app.register_blueprint(account_bp) + app.register_blueprint(wallet_bp) app.app_context().push() return app diff --git a/wowstash/forms.py b/wowstash/forms.py index f8e4a70..5591855 100644 --- a/wowstash/forms.py +++ b/wowstash/forms.py @@ -1,8 +1,15 @@ from flask_wtf import FlaskForm -from wtforms import StringField +from wtforms import StringField, BooleanField from wtforms.validators import DataRequired class Register(FlaskForm): email = StringField('Email Address:', validators=[DataRequired()], render_kw={"placeholder": "Email", "class": "form-control", "type": "email"}) password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"}) + faq_reviewed = BooleanField('FAQ Reviewed:', validators=[DataRequired()], render_kw={"class": "form-control-span"}) + terms_reviewed = BooleanField('Terms Reviewed:', validators=[DataRequired()], render_kw={"class": "form-control-span"}) + privacy_reviewed = BooleanField('Privacy Policy Reviewed:', validators=[DataRequired()], render_kw={"class": "form-control-span"}) + +class Login(FlaskForm): + email = StringField('Email Address:', validators=[DataRequired()], render_kw={"placeholder": "Email", "class": "form-control", "type": "email"}) + password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"}) diff --git a/wowstash/library/jsonrpc.py b/wowstash/library/jsonrpc.py index 3e00bdf..d2b1a17 100644 --- a/wowstash/library/jsonrpc.py +++ b/wowstash/library/jsonrpc.py @@ -1,7 +1,9 @@ import json import requests +import operator from wowstash import config + class JSONRPC(object): def __init__(self, proto, host, port, username='', password=''): self.endpoint = '{}://{}:{}/'.format( @@ -32,11 +34,32 @@ class JSONRPC(object): except: return {} + class Wallet(JSONRPC): + def __init__(self, **kwargs): + super().__init__(**kwargs) + if 'height' in self.height(): + self.connected = True + else: + self.connected = False + def height(self): return self.make_rpc('get_height', {}) + def new_address(self, account_index=0, label=None): + data = {'account_index': account_index, 'label': label} + _address = self.make_rpc('create_address', data) + return (_address['address_index'], _address['address']) + + def get_address(self, account_index, subaddress_index): + data = {'account_index': account_index, 'address_index': [subaddress_index]} + return self.make_rpc('get_address', data) + + class Daemon(JSONRPC): + def __init__(self, **kwargs): + super().__init__(**kwargs) + def info(self): return self.make_rpc('get_info', {}, json_rpc=False) @@ -44,4 +67,18 @@ class Daemon(JSONRPC): return self.make_rpc('get_height', {}, json_rpc=False) -daemon = Daemon(proto=config.DAEMON_PROTO, host=config.DAEMON_HOST, port=config.DAEMON_PORT) +daemon = Daemon( + proto=config.DAEMON_PROTO, + host=config.DAEMON_HOST, + port=config.DAEMON_PORT, + username=config.DAEMON_USER, + password=config.DAEMON_PASS +) + +wallet = Wallet( + proto=config.WALLET_PROTO, + host=config.WALLET_HOST, + port=config.WALLET_PORT, + username=config.WALLET_USER, + password=config.WALLET_PASS +) diff --git a/wowstash/models.py b/wowstash/models.py index 04ea861..9d16c03 100644 --- a/wowstash/models.py +++ b/wowstash/models.py @@ -1,7 +1,6 @@ from sqlalchemy import Column, Integer, DateTime, String from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.sql import func -from flask_bcrypt import generate_password_hash, check_password_hash from wowstash.factory import db @@ -16,8 +15,24 @@ class User(db.Model): subaddress_index = db.Column(db.Integer) registered_on = db.Column(db.DateTime, server_default=func.now()) - def hash_password(self): - self.password = generate_password_hash(self.password).decode('utf8') + @property + def is_authenticated(self): + return True - def check_password(self, password): - return check_password_hash(self.password, password) + @property + def is_active(self): + return True + + @property + def is_anonymous(self): + return False + + @property + def is_admin(self): + return self.admin + + def get_id(self): + return self.id + + def __repr__(self): + return self.username diff --git a/wowstash/templates/account.html b/wowstash/templates/account/dashboard.html similarity index 56% rename from wowstash/templates/account.html rename to wowstash/templates/account/dashboard.html index c863bc1..b74bc01 100644 --- a/wowstash/templates/account.html +++ b/wowstash/templates/account/dashboard.html @@ -7,21 +7,30 @@ {% include 'navbar.html' %} -
+ + + + -
+ --> + +
+
+ +
+ +
+ Send +
+
+ Receive +
+
+
+

Your address: {{ subaddress }}

+
+
+