diff --git a/docker/config.ini b/docker/config.ini index 577b585b..c5c7008c 100644 --- a/docker/config.ini +++ b/docker/config.ini @@ -32,6 +32,8 @@ rpc_port=8994 enabled=true host=0.0.0.0 port=12994 +username=devuser +password=devpass use_ssl=false [postgresql] diff --git a/requirements.txt b/requirements.txt index 6d90706f..0eb72fd2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,5 @@ requests SQLAlchemy Flask protobuf +flask-login +Flask-WTF diff --git a/squeakserver/admin/forms.py b/squeakserver/admin/forms.py new file mode 100644 index 00000000..e58edf88 --- /dev/null +++ b/squeakserver/admin/forms.py @@ -0,0 +1,12 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, SubmitField +from wtforms.validators import ValidationError, DataRequired, EqualTo + +from squeakserver.admin.squeak_admin_web_user import User + + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + password = PasswordField('Password', validators=[DataRequired()]) + remember_me = BooleanField('Remember Me') + submit = SubmitField('Sign In') diff --git a/squeakserver/admin/squeak_admin_web_service.py b/squeakserver/admin/squeak_admin_web_service.py index 8aa485db..9ed2430f 100644 --- a/squeakserver/admin/squeak_admin_web_service.py +++ b/squeakserver/admin/squeak_admin_web_service.py @@ -3,6 +3,14 @@ import logging from flask import Flask from flask import request +from flask import redirect, url_for +from flask import render_template +from flask import flash + +from flask_login import LoginManager +from flask_login import current_user, login_user +from flask_login import login_required +from flask_login import logout_user from google.protobuf import json_format from google.protobuf import message @@ -10,17 +18,38 @@ from google.protobuf import message from proto import squeak_admin_pb2, squeak_admin_pb2_grpc from proto import lnd_pb2, lnd_pb2_grpc +from squeakserver.admin.squeak_admin_web_user import User +from squeakserver.admin.forms import LoginForm logger = logging.getLogger(__name__) -def create_app(handler): +def create_app(handler, username, password): # create and configure the app logger.info("Starting flask app from directory: {}".format(os.getcwd())) - app = Flask(__name__, static_folder='/app/static/build', static_url_path='/') + app = Flask( + __name__, + static_folder='/app/static/build', + static_url_path='/', + template_folder='/app/squeakserver/admin/templates', + ) app.config.from_mapping( SECRET_KEY='dev', ) + login = LoginManager(app) + valid_user = User( + username, + password, + ) + + + @login.user_loader + def load_user(id): + return valid_user.get_user_by_username(id) + + @login.unauthorized_handler + def unauthorized_callback(): + return redirect('/login') def handle_request(request_message, handle_rpc_request): data = request.get_data() @@ -28,11 +57,30 @@ def create_app(handler): reply = handle_rpc_request(request_message) return reply.SerializeToString(reply) + @app.route('/login', methods=['GET', 'POST']) + def login(): + logger.info("Trying to login") + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + if form.validate_on_submit(): + user = valid_user.get_user_by_username(form.username.data) + if user is None or not user.check_password(form.password.data): + flash('Invalid username or password') + return redirect(url_for('login')) + login_user(user, remember=form.remember_me.data) + return redirect(url_for('index')) + return render_template('login.html', title='Sign In', form=form) + + @app.route('/logout') + def logout(): + logout_user() + return redirect(url_for('index')) + @app.route('/') + @login_required def index(): logger.info("Getting index route.") - logger.info("os.getcwd(): {}".format(os.getcwd())) - logger.info("os.listdir(os.getcwd()): {}".format(os.listdir(os.getcwd()))) return app.send_static_file('index.html') @app.route('/hello') @@ -297,12 +345,11 @@ def create_app(handler): class SqueakAdminWebServer(): - def __init__(self, host, port, use_ssl, handler): + def __init__(self, host, port, username, password, use_ssl, handler): self.host = host self.port = port self.use_ssl = use_ssl - self.handler = handler - self.app = create_app(handler) + self.app = create_app(handler, username, password) def serve(self): self.app.run( diff --git a/squeakserver/admin/squeak_admin_web_user.py b/squeakserver/admin/squeak_admin_web_user.py new file mode 100644 index 00000000..9e8cbc63 --- /dev/null +++ b/squeakserver/admin/squeak_admin_web_user.py @@ -0,0 +1,34 @@ +import logging + +from flask_login import UserMixin + +from werkzeug.security import generate_password_hash, check_password_hash + + +logger = logging.getLogger(__name__) + + +class User(UserMixin): + + def __init__(self, username, password): + self.username = username + self.password_hash = generate_password_hash(password) + + def is_authenticated(self): + return True + + def is_active(self): + return True + + def is_anonymous(self): + return False + + def get_id(self): + return self.username + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + + def get_user_by_username(self, username): + if self.username == username: + return self diff --git a/squeakserver/admin/templates/base.html b/squeakserver/admin/templates/base.html new file mode 100644 index 00000000..775a3cef --- /dev/null +++ b/squeakserver/admin/templates/base.html @@ -0,0 +1,31 @@ + +
+ {% if title %} +