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
"""
import datetime
import re
from typing import Optional, Sequence
@ -75,6 +76,15 @@ def get_all_users(db: DbContext) -> Sequence[User]:
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]:
"""
Get a user by the user's username.
@ -98,3 +108,12 @@ def password_check(db: DbContext, username: str, password: str) -> bool:
).scalar_one()
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")
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):
"""

View File

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

View File

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

View File

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

View File

@ -9,12 +9,12 @@
[{{ lexicon.status.capitalize() }}]
</p>
<p><i>{{ lexicon.prompt }}</i></p>
{# {% if current_user.is_authenticated %} #}
{% if current_user.is_authenticated %}
<p>
{# TODO #}
{# {%
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') }#} /
Players:
@ -31,7 +31,7 @@
{# {% endif %} #}
{# {% endif %} #}
</p>
{# {% endif %} #}
{% endif %}
</div>
{% endmacro %}

View File

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