Compare commits

..

No commits in common. "29543026ed9f3392cff84f77c768ad3fb14d8de7" and "467f6a77ae37ba72599a51efb06432c5bc9e803c" have entirely different histories.

8 changed files with 96 additions and 118 deletions

View file

@ -27,5 +27,9 @@ dynamic = ["version"]
# setuptools to be a module without it.
packages = ["roc_fnb"]
[project.scripts]
# Put scripts here
bootstrap-first-admin = "roc_fnb.scripts.bootstrap_first_admin:bootstrap_first_admin"
[tool.yapf]
based_on_style = "facebook"

View file

@ -1,2 +0,0 @@
#!/usr/bin/env bash
python -m pytest --capture no --ignore mounts

View file

@ -58,3 +58,14 @@ class Database(MongoClient):
if user := self.db.users.find_one({'_id': id}):
return User(**user)
return None
def get_user_from_token(self, token: str) -> Optional[User]:
"""
Verify a user and retreive a their full profile from the database.
This is like User.verify_jwt except it also fetches fields from the
database which are not present in the client-visible token.
"""
if jwt_user := User.verify_jwt(token):
return self.get_user_by_id(jwt_user._id)
return None

81
roc_fnb/website/server.py Normal file
View file

@ -0,0 +1,81 @@
from functools import wraps
import json
from pathlib import Path
from random import randbytes
from sys import stderr
from flask import (Flask, redirect, url_for, request, send_file, make_response,
abort, render_template, session, g)
from roc_fnb.util.env_file import env_file
from roc_fnb.website.database import Database
from roc_fnb.website.models.user import JwtUser
db = Database.from_env()
app = Flask(
import_name=__name__.split('.')[0],
static_url_path='/',
template_folder=Path(__file__).absolute().parent / 'templates',
static_folder=Path(__file__).absolute().parent / 'static',
)
app.secret_key = env_file('FLASK_SECRET', default_file='./flask.secret', default_fn=lambda: randbytes(12))
@app.before_request
def decode_user():
if user := session.get('user'):
g.user = JwtUser.from_json(data=json.loads(user))
def require_user(admin = False, moderator = False):
"""
A decorator for any routes which require authentication.
https://stackoverflow.com/a/51820573
"""
def _require_user(handler):
@wraps(handler)
def __require_user():
if getattr(g, 'user', None) is None \
or (admin and not user.admin) \
or (moderator and not user.moderator):
abort(401)
return handler()
return __require_user
return _require_user
@app.route('/ig')
def ig_redir():
return redirect('https://instagram.com/RocFNB')
@app.route('/donate')
def donate_redir():
return redirect('https://venmo.com/RocFoodNotBombs')
@app.route('/')
def index():
return redirect('/index.html')
@app.post('/login')
def submit_login():
form = request.json
user = db.get_user_by_name(form['name'])
if not user.check_password(form['password']):
abort(401) # unauthorized
session['user'] = json.dumps(user.public_fields)
return redirect('/me')
@app.get('/login')
def render_login_page():
if getattr(g, 'user', None):
return redirect('/me')
return render_template('login.html')
@app.get('/me')
@require_user()
def get_profile():
return render_template('profile.html', user=g.user)

View file

@ -1 +0,0 @@
from roc_fnb.website.server.server import app

View file

@ -1,38 +0,0 @@
"""
Various decorators which may be applied to specific routes
See https://stackoverflow.com/a/51820573 for reference.
"""
from flask import g, abort, request
from functools import wraps
def require_user(admin = False, moderator = False):
"""A decorator for any routes which require authentication."""
def _require_user(handler):
@wraps(handler)
def __require_user():
if getattr(g, 'user', None) is None \
or (admin and not g.user.admin) \
or (moderator and not g.user.moderator):
abort(401)
return handler()
return __require_user
return _require_user
def logger_request_bindings(log):
"""Applied to a route which logs something to have request data bound to the logger."""
def _lrb(handler):
@wraps(handler)
def __lrb():
log.bind(
path=request.path,
method=request.method,
user_agent=request.user_agent,
remote_ip=request.remote_addr,
user=getattr(g, 'user', '(anonymous)'),
)
return handler(log)
return __lrb
return _lrb

View file

@ -1,46 +0,0 @@
from functools import wraps
import json
from pathlib import Path
from random import randbytes
from sys import stderr
from flask import Flask, redirect
from roc_fnb.util.env_file import env_file
from roc_fnb.util import log
from roc_fnb.website.database import Database
from roc_fnb.website.server.user import setup_user_routes
db = Database.from_env()
app = Flask(
import_name=__name__.split('.')[0],
static_url_path='/',
template_folder=Path(__file__).absolute().parent / 'templates',
static_folder=Path(__file__).absolute().parent / 'static',
)
app.secret_key = env_file('FLASK_SECRET', default_file='./flask.secret', default_fn=lambda: randbytes(12))
@app.before_request
def decode_user():
if user := session.get('user'):
g.user = JwtUser.from_json(data=json.loads(user))
@app.route('/ig')
def ig_redir():
return redirect('https://instagram.com/RocFNB')
@app.route('/donate')
def donate_redir():
return redirect('https://venmo.com/RocFoodNotBombs')
@app.route('/')
def index():
return redirect('/index.html')
setup_user_routes(app, db)

View file

@ -1,31 +0,0 @@
import json
from flask import request, redirect, render_template, g, abort
from roc_fnb.util import log
from roc_fnb.website.server.decorators import require_user, logger_request_bindings
def setup_user_routes(app, db):
@app.post('/login')
@logger_request_bindings(log)
def submit_login(log):
form = request.json
log.info('user attempting login', name=form.get('name'))
user = db.get_user_by_name(form['name'])
if not user.check_password(form['password']):
log.warn('incorrect password submitted', name=form['name'])
abort(401) # unauthorized
session['user'] = json.dumps(user.public_fields)
return redirect('/me')
@app.get('/login')
def render_login_page():
if getattr(g, 'user', None):
log.debug('user is already logged in', user=g.user)
return redirect('/me')
return render_template('login.html')
@app.get('/me')
@require_user()
def get_profile():
return render_template('profile.html', user=g.user)