Reintegrate auth routes

This commit is contained in:
Tim Van Baak 2021-06-16 00:37:49 -07:00
parent 6b5463b702
commit e4e699fa1b
7 changed files with 114 additions and 75 deletions

View File

@ -2,6 +2,7 @@
User query interface User query interface
""" """
import datetime
import re import re
from typing import Optional, Sequence from typing import Optional, Sequence
@ -75,6 +76,15 @@ def get_all_users(db: DbContext) -> Sequence[User]:
return db(select(User)).scalars() return db(select(User)).scalars()
def get_user_by_id(db: DbContext, user_id: int) -> Optional[User]:
"""
Get a user by the user's id.
Returns None if no user was found.
"""
user: User = db(select(User).where(User.id == user_id)).scalar_one_or_none()
return user
def get_user_by_username(db: DbContext, username: str) -> Optional[User]: def get_user_by_username(db: DbContext, username: str) -> Optional[User]:
""" """
Get a user by the user's username. Get a user by the user's username.
@ -98,3 +108,12 @@ def password_check(db: DbContext, username: str, password: str) -> bool:
).scalar_one() ).scalar_one()
return check_password_hash(user_password_hash, password) return check_password_hash(user_password_hash, password)
def update_logged_in(db: DbContext, username: str) -> None:
"""Bump the value of the last_login column for a user."""
db(
update(User)
.where(User.username == username)
.values(last_login=datetime.datetime.utcnow())
)
db.session.commit()

View File

@ -100,6 +100,25 @@ class User(ModelBase):
articles = relationship("Article", back_populates="user") articles = relationship("Article", back_populates="user")
posts = relationship("Post", back_populates="user") posts = relationship("Post", back_populates="user")
#########################
# Flask-Login interface #
#########################
@property
def is_authenticated(self: "User") -> bool:
return True
@property
def is_active(self: "User") -> bool:
return True
@property
def is_anonymous(self: "User") -> bool:
return False
def get_id(self: "User") -> str:
return str(self.id)
class Lexicon(ModelBase): class Lexicon(ModelBase):
""" """

View File

@ -5,7 +5,8 @@ from flask import Flask, g
from amanuensis.config import AmanuensisConfig, CommandLineConfig from amanuensis.config import AmanuensisConfig, CommandLineConfig
from amanuensis.db import DbContext from amanuensis.db import DbContext
import amanuensis.server.home import amanuensis.server.auth as auth
import amanuensis.server.home as home
def get_app( def get_app(
@ -48,10 +49,11 @@ def get_app(
app.jinja_options.update(trim_blocks=True, lstrip_blocks=True) app.jinja_options.update(trim_blocks=True, lstrip_blocks=True)
# Set up Flask-Login # Set up Flask-Login
# TODO auth.get_login_manager().init_app(app)
# Register blueprints # Register blueprints
app.register_blueprint(amanuensis.server.home.bp) app.register_blueprint(auth.bp)
app.register_blueprint(home.bp)
def test(): def test():
return "Hello, world!" return "Hello, world!"

View File

@ -1,76 +1,79 @@
import logging import logging
import time from typing import Optional
from flask import ( from flask import (
Blueprint, Blueprint,
render_template, flash,
redirect, g,
url_for, redirect,
flash, render_template,
current_app) url_for,
)
from flask_login import ( from flask_login import (
login_user, AnonymousUserMixin,
logout_user, login_user,
login_required, logout_user,
LoginManager) login_required,
LoginManager,
)
from amanuensis.config import RootConfigDirectoryContext import amanuensis.backend.user as userq
from amanuensis.models import ModelFactory, AnonymousUserModel from amanuensis.db import User
from .forms import LoginForm from .forms import LoginForm
logger = logging.getLogger(__name__)
LOG = logging.getLogger(__name__)
bp = Blueprint("auth", __name__, url_prefix="/auth", template_folder=".")
def get_login_manager(root: RootConfigDirectoryContext) -> LoginManager: def get_login_manager() -> LoginManager:
""" """Login manager factory"""
Creates a login manager login_manager = LoginManager()
""" login_manager.login_view = "auth.login"
login_manager = LoginManager() login_manager.anonymous_user = AnonymousUserMixin
login_manager.login_view = 'auth.login'
login_manager.anonymous_user = AnonymousUserModel
@login_manager.user_loader def load_user(user_id_str: str) -> Optional[User]:
def load_user(uid): try:
return current_app.config['model_factory'].user(str(uid)) user_id = int(user_id_str)
except:
return None
return userq.get_user_by_id(g.db, user_id)
return login_manager login_manager.user_loader(load_user)
return login_manager
bp_auth = Blueprint('auth', __name__, @bp.route("/login/", methods=["GET", "POST"])
url_prefix='/auth',
template_folder='.')
@bp_auth.route('/login/', methods=['GET', 'POST'])
def login(): def login():
model_factory: ModelFactory = current_app.config['model_factory'] form = LoginForm()
form = LoginForm()
if not form.validate_on_submit(): if not form.validate_on_submit():
# Either the request was GET and we should render the form, # Either the request was GET and we should render the form,
# or the request was POST and validation failed. # or the request was POST and validation failed.
return render_template('auth.login.jinja', form=form) return render_template("auth.login.jinja", form=form)
# POST with valid data # POST with valid data
username = form.username.data username: str = form.username.data
user = model_factory.try_user(username) password: str = form.password.data
if not user or not user.check_password(form.password.data): user: User = userq.get_user_by_username(g.db, username)
# Bad creds if not user or not userq.password_check(g.db, username, password):
flash("Login not recognized") # Bad creds
return redirect(url_for('auth.login')) flash("Login not recognized")
return redirect(url_for("auth.login"))
# Login credentials were correct # Login credentials were correct
remember_me = form.remember.data remember_me: bool = form.remember.data
login_user(user, remember=remember_me) login_user(user, remember=remember_me)
with user.ctx.edit_config() as cfg: userq.update_logged_in(g.db, username)
cfg.last_login = int(time.time()) LOG.info("Logged in user {0.username} ({0.id})".format(user))
logger.info('Logged in user "{0.username}" ({0.uid})'.format(user.cfg)) return redirect(url_for("home.admin"))
return redirect(url_for('home.home'))
@bp_auth.route("/logout/", methods=['GET']) @bp.get("/logout/")
@login_required @login_required
def logout(): def logout():
logout_user() logout_user()
return redirect(url_for('home.home')) return redirect(url_for("home.admin"))

View File

@ -4,12 +4,9 @@ from wtforms.validators import DataRequired
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
"""/auth/login/""" """/auth/login/"""
username = StringField(
'Username', username = StringField("Username", validators=[DataRequired()])
validators=[DataRequired()]) password = PasswordField("Password", validators=[DataRequired()])
password = PasswordField( remember = BooleanField("Stay logged in")
'Password', submit = SubmitField("Log in")
validators=[DataRequired()])
remember = BooleanField('Stay logged in')
submit = SubmitField('Log in')

View File

@ -9,12 +9,12 @@
[{{ lexicon.status.capitalize() }}] [{{ lexicon.status.capitalize() }}]
</p> </p>
<p><i>{{ lexicon.prompt }}</i></p> <p><i>{{ lexicon.prompt }}</i></p>
{# {% if current_user.is_authenticated %} #} {% if current_user.is_authenticated %}
<p> <p>
{# TODO #} {# TODO #}
{# {% {# {%
if current_user.uid in lexicon.cfg.join.joined if current_user.uid in lexicon.cfg.join.joined
or current_user.cfg.is_admin or current_user.is_site_admin
%} #} %} #}
Editor: {#{ lexicon.cfg.editor|user_attr('username') }#} / Editor: {#{ lexicon.cfg.editor|user_attr('username') }#} /
Players: Players:
@ -31,7 +31,7 @@
{# {% endif %} #} {# {% endif %} #}
{# {% endif %} #} {# {% endif %} #}
</p> </p>
{# {% endif %} #} {% endif %}
</div> </div>
{% endmacro %} {% endmacro %}

View File

@ -11,13 +11,12 @@
<div id="wrapper"> <div id="wrapper">
<div id="header"> <div id="header">
<div id="login-status" {% block login_status_attr %}{% endblock %}> <div id="login-status" {% block login_status_attr %}{% endblock %}>
{# TODO #} {% if current_user.is_authenticated %}
{# {% if current_user.is_authenticated %} <b>{{ current_user.username -}}</b>
<b>{{ current_user.cfg.username -}}</b>
(<a href="{{ url_for('auth.logout') }}">Logout</a>) (<a href="{{ url_for('auth.logout') }}">Logout</a>)
{% else %} #} {% else %}
<a href="#{#{ url_for('auth.login') }#}">Login</a> <a href="{{ url_for('auth.login') }}">Login</a>
{# {% endif %} #} {% endif %}
</div> </div>
{% block header %}{% endblock %} {% block header %}{% endblock %}
</div> </div>