diff --git a/amanuensis/cli.py b/amanuensis/cli.py index 362acc3..4d436df 100644 --- a/amanuensis/cli.py +++ b/amanuensis/cli.py @@ -179,7 +179,7 @@ def command_user_passwd(args): if not args.username: args.username = input("Username: ") - uid = get_user_by_username(args.username) + uid = uid_from_username(args.username) if uid is None: print("No user with username '{}'".format(args.username)) return -1 diff --git a/amanuensis/server/__init__.py b/amanuensis/server/__init__.py index 093024c..54f8ee2 100644 --- a/amanuensis/server/__init__.py +++ b/amanuensis/server/__init__.py @@ -1,10 +1,12 @@ from flask import Flask, render_template +from flask_login import LoginManager import config +from server.auth import get_bp as get_auth_bp app = Flask(__name__, template_folder="../templates") app.secret_key = bytes.fromhex(config.get('secret_key')) - -from server.auth import bp -app.register_blueprint(auth.bp) +login = LoginManager(app) +auth_bp = get_auth_bp(login) +app.register_blueprint(auth_bp) diff --git a/amanuensis/server/auth.py b/amanuensis/server/auth.py new file mode 100644 index 0000000..da3e414 --- /dev/null +++ b/amanuensis/server/auth.py @@ -0,0 +1,40 @@ +import flask +from flask_wtf import FlaskForm +from wtforms import StringField, PasswordField, BooleanField, SubmitField +from wtforms.validators import DataRequired +from flask_login import current_user, login_user + +import config +import user + +class LoginForm(FlaskForm): + username = StringField('Username', validators=[DataRequired()]) + #password = PasswordField('Password', validators=[DataRequired()]) + #remember = BooleanField('Remember Me') + submit = SubmitField('Log in') + +def get_bp(login_manager): + """Create a blueprint for the auth functions""" + bp = flask.Blueprint('auth', __name__, url_prefix='/auth') + + @login_manager.user_loader + def load_user(uid): + return user.user_from_uid(str(uid)) + + @bp.route('/login/', methods=['GET', 'POST']) + def login(): + form = LoginForm() + if form.validate_on_submit(): + username = form.username.data + uid = user.uid_from_username(username) + if uid is None: + pass + u = user.user_from_uid(uid) + login_user(u) + config.logger.info("Logged in user '{}' ({})".format(u.get('username'), u.uid)) + name = u.get('username') + else: + name = "guest" + return flask.render_template('auth/login.html', form=form, username=name) + + return bp diff --git a/amanuensis/templates/admin.html b/amanuensis/templates/admin.html deleted file mode 100644 index 1db858a..0000000 --- a/amanuensis/templates/admin.html +++ /dev/null @@ -1,6 +0,0 @@ - - - -

Hello, {{username}}

- - diff --git a/amanuensis/templates/auth/login.html b/amanuensis/templates/auth/login.html new file mode 100644 index 0000000..30bc909 --- /dev/null +++ b/amanuensis/templates/auth/login.html @@ -0,0 +1,12 @@ + + + +

Hello, {{username}}

+{% if current_user.is_authenticated %}{{ current_user.get('displayname') }}{% endif %} +
+ {{ form.hidden_tag() }} +

{{ form.username.label }}
{{ form.username(size=32) }}

+

{{ form.submit() }}

+
+ + diff --git a/amanuensis/user.py b/amanuensis/user.py index f1935e5..e04f3ba 100644 --- a/amanuensis/user.py +++ b/amanuensis/user.py @@ -3,24 +3,33 @@ import re import time import uuid +from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash import config -class User(): +class User(UserMixin): def __init__(self, uid): if not os.path.isdir(config.prepend('user', uid)): raise ValueError("No user with uid {}".format(uid)) - self.uid = uid - self.config = os.path.join('user', uid, 'config.json') + self.uid = str(uid) + self.config_path = os.path.join('user', uid, 'config.json') + with config.json_ro(self.config_path) as j: + self.config = j + + def get_id(self): + return self.uid + + def get(self, key): + return self.config.get(key) def set_password(self, pw): h = generate_password_hash(pw) - with config.json_rw(self.config) as j: + with config.json_rw(self.config_path) as j: j['password'] = h def check_password(self, pw): - with config.json_ro(self.config) as j: + with config.json_ro(self.config_path) as j: return check_password_hash(j['password'], pw) def valid_username(username): @@ -34,6 +43,10 @@ def valid_email(email): return re.match(addrspec, email) def create_user(username, displayname, email): + if not valid_username(username): + raise ValueError("Invalid username: '{}'".format(username)) + if not valid_email(email): + raise ValueError("Invalid email: '{}'".format(email)) uid = uuid.uuid4().hex now = int(time.time()) temp_pw = os.urandom(32).hex() @@ -51,6 +64,20 @@ def create_user(username, displayname, email): u.set_password(temp_pw) return u, temp_pw -def get_user_by_username(username): +def uid_from_username(username): + """Gets the internal uid of a user given a username""" + if username is None: + raise ValueError("username must not be None") + if not username: + raise ValueError("username must not be empty") with config.json_ro('user', 'index.json') as index: - return index.get(username) + uid = index.get(username) + if uid is None: + config.logger.debug("uid_from_username('{}') returned None".format(username)) + return uid + +def user_from_uid(uid): + if not os.path.isdir(config.prepend('user', uid)): + config.logger.debug("No user with uid '{}'".format(uid)) + return None + return User(uid)