Browse Source

move qr helpers to utils.py, implement user hash login

testing
Burathar 4 years ago
parent
commit
2e5f1a2c2f
  1. 44
      app/auth/routes.py
  2. 49
      app/main/routes.py
  3. 14
      app/models/user.py
  4. 7
      app/templates/auth/user.html
  5. 14
      app/templates/auth/user_hash_login.html
  6. 4
      app/templates/game_player.html
  7. 21
      app/utils.py

44
app/auth/routes.py

@ -1,9 +1,10 @@ @@ -1,9 +1,10 @@
from datetime import datetime
from flask import render_template, flash, redirect, url_for, abort
from flask import render_template, flash, redirect, request, url_for, abort, Markup, escape
from flask_login import login_user, logout_user, current_user, login_required
from app import db
from app.auth import bp
from app.utils import generate_qr_code, serve_pil_image
from app.models import User
from app.auth.forms import LoginForm, RegistrationForm
@ -17,11 +18,14 @@ def login(): @@ -17,11 +18,14 @@ def login():
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('auth.login'))
login_user(user, remember=form.remember_me.data)
user.last_login = datetime.utcnow()
return redirect(url_for('main.index'))
return user_login(user, form.remember_me.data)
return render_template('auth/login.html', title='Sign In', form=form)
def user_login(user, remember):
login_user(user, remember)
user.last_login = datetime.utcnow()
return redirect(url_for('main.index'))
@bp.route('/logout')
@login_required
def logout():
@ -58,3 +62,35 @@ def generate_auth_hash(username): @@ -58,3 +62,35 @@ def generate_auth_hash(username):
user.set_auth_hash()
db.session.commit()
return 'nothing'
@bp.route('/user/<auth_hash>')
def user_hash_login(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if current_user.is_authenticated:
if current_user.owns_game_played_by(user):
safe_username = escape(user.name)
flash(Markup(f"The <a href='{request.url}'>login link</a> for {safe_username} works! "
"However, logged in users like you can't access it"))
return redirect(url_for('main.game_player', game_name=current_user.owned_game_played_by(user).name, username=user.name))
flash(f'You are aleady logged in as {current_user.name}!')
return redirect(url_for('main.index'))
if user.password_hash:
flash('Please login with your username and password!')
abort(404)
if user.last_login:
return user_login(user, True)
if request.args.get('login', default='false', type=str).lower() == 'true':
return user_login(user, True)
return render_template('auth/user_hash_login.html', title=f'User: {user.name}', user=user)
@bp.route('/user/<auth_hash>/qrcode.png')
@login_required
def user_qrcode(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if not current_user.owns_game_played_by(user):
abort(403)
if user.last_login:
flash('After a player has logged in, it is no longer possible to request their QR code.')
abort(403)
img = generate_qr_code(url_for('auth.user_hash_login', auth_hash=auth_hash, _external=True))
return serve_pil_image(img)

49
app/main/routes.py

@ -3,8 +3,7 @@ import json @@ -3,8 +3,7 @@ import json
from os import listdir
from pathlib import Path
from datetime import datetime
from io import BytesIO
import qrcode
from flask import render_template, flash, redirect, url_for, request, abort, send_file, current_app, send_from_directory
from flask_login import current_user, login_required
from werkzeug.utils import secure_filename
@ -13,12 +12,10 @@ from sqlalchemy import and_ @@ -13,12 +12,10 @@ from sqlalchemy import and_
from app import db
from app.main import bp
from app.utils import generate_qr_code, serve_pil_image
from app.models import User, Game, Role, GamePlayer, Objective, ObjectiveMinimalEncoder, LocationEncoder, PlayerCaughtPlayer
from app.main.forms import CreateGameForm, ObjectiveForm, PlayerAddForm, UserCreateForm, PlayerUpdateForm, CatchBunnyForm
import os
@bp.before_app_request
def before_request():
if current_user.is_authenticated:
@ -36,7 +33,6 @@ def index(): @@ -36,7 +33,6 @@ def index():
@bp.route('/create_game', methods=['GET', 'POST'])
@login_required
def create_game():
print(current_user.is_authenticated)
form = CreateGameForm()
if form.validate_on_submit():
game = Game(name=form.game_name.data, start_time=form.start_time.data, end_time=form.end_time.data)
@ -211,34 +207,13 @@ def game_player(game_name, username): @@ -211,34 +207,13 @@ def game_player(game_name, username):
form.role.choices = [(role.value, role.name) for role in Role]
form.role.default = player.role.value
form.process()
if form.validate_on_submit():
player.role = Role[form.role.data]
db.session.commit()
return redirect(url_for('main.game_dashboard', game_name=game.name))
return render_template('game_player.html', title=f'{user.name} in {game_name}', player=player, form=form, json=json, location_encoder=LocationEncoder)
@bp.route('/user/<auth_hash>/qrcode.png')
@login_required
def user_qrcode(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if not current_user.owns_game_played_by(user):
abort(403)
if user.last_login:
flash('After a player has logged in, it is no longer possible to request their QR code.')
abort(403)
img = generate_qr_code(url_for('main.user', auth_hash=auth_hash, _external=True))
return serve_pil_image(img)
@bp.route('/user/<auth_hash>')
@login_required
def user(auth_hash):
user = User.query.filter_by(auth_hash=auth_hash).first_or_404()
if user.password_hash:
flash('Please login with your username and password!')
abort(404)
return render_template('auth/user.html', title=f'User: {user.name}', user=user)
@bp.route('/game/<game_name>/add_objective', methods=['GET', 'POST'])
@login_required
def add_objective(game_name):
@ -267,24 +242,6 @@ def delete_objective(objective_hash): @@ -267,24 +242,6 @@ def delete_objective(objective_hash):
db.session.commit()
return redirect(url_for('main.game_dashboard', game_name=objective.game.name))
def generate_qr_code(url):
qr = qrcode.QRCode(
version=None,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=30,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
return qr.make_image(fill_color='black', back_color='white')
# Source: https://stackoverflow.com/questions/7877282/how-to-send-image-generated-by-pil-to-browser
def serve_pil_image(pil_img):
img_io = BytesIO()
pil_img.save(img_io, 'PNG', quality=70)
img_io.seek(0)
return send_file(img_io, mimetype='image/png')
@bp.route('/objective/<objective_hash>/qrcode.png')
@login_required
def objective_qrcode(objective_hash):

14
app/models/user.py

@ -67,12 +67,20 @@ class User(UserMixin, db.Model): @@ -67,12 +67,20 @@ class User(UserMixin, db.Model):
return None
return gameplayer.role
def owns_game_played_by(self, player):
'''Self is an owner of a game the player participates in'''
def owns_game_played_by(self, user):
'''Self is an owner of a game the user participates in'''
return self in [gameplayer.user for gameplayers in
[game.players for game in player.games]
[game.players for game in user.games]
for gameplayer in gameplayers if gameplayer.role == Role.owner]
def owned_game_played_by(self, user):
'''Return first game owned by self that the user participates in'''
return next(iter([gameplayer.game for gameplayers in
[game.players for game in user.games]
for gameplayer in gameplayers
if gameplayer.role == Role.owner and gameplayer.user == self]),
None)
def player_in(self, game):
# pylint: disable=not-an-iterable
'''Returns GamePlayer object for given game, or None if user does not participate in given game'''

7
app/templates/auth/user.html

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>User</h1>
This page is is progress, it should enable you to claim a player account using the authhash, as long as the player hasnt logged in yet.
{% endblock %}

14
app/templates/auth/user_hash_login.html

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h1>Welcome, {{ user.name }}!</h1>
<p>
If you found this page, it probably means someone who is organising a hunt invited you by
sending a link or QR-code to this page. You can start playing right away, if and if you get
logged out just visit this page again. However, if you want to be sure other people can't
steal this account, please set a password.
</p>
<a href="{{ url_for('main.index') }}"><button class="btn btn-primary">Set Password</button></a>
<a href="{{ url_for('auth.user_hash_login', auth_hash=user.auth_hash, login='true') }}"><button class="btn btn-primary">Start Playing!</button></a>
{% endblock %}

4
app/templates/game_player.html

@ -18,7 +18,9 @@ @@ -18,7 +18,9 @@
{% if player.user.auth_hash and not player.user.last_login %}
<div class="row">
<img src="{{ url_for('main.user_qrcode', auth_hash=player.user.auth_hash) }}" alt="qr_code_failed" , width="80%">
<a href="{{ url_for('auth.user_hash_login', auth_hash=player.user.auth_hash) }}">
<img src="{{ url_for('auth.user_qrcode', auth_hash=player.user.auth_hash) }}" alt="qr_code_failed" width="80%" title="login code for {{ player.user.name }}">
</a>
</div>
{% elif not player.user.last_login %}
<br>

21
app/utils.py

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
from io import BytesIO
import qrcode
from flask import send_file
def generate_qr_code(url):
qr = qrcode.QRCode(
version=None,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=30,
border=4,
)
qr.add_data(url)
qr.make(fit=True)
return qr.make_image(fill_color='black', back_color='white')
def serve_pil_image(pil_img):
# Source: https://stackoverflow.com/questions/7877282/how-to-send-image-generated-by-pil-to-browser
img_io = BytesIO()
pil_img.save(img_io, 'PNG', quality=70)
img_io.seek(0)
return send_file(img_io, mimetype='image/png')
Loading…
Cancel
Save