-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathauth.py
More file actions
63 lines (50 loc) · 2.3 KB
/
Copy pathauth.py
File metadata and controls
63 lines (50 loc) · 2.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"""Flask-Login wiring for the /admin UI. dbstore.users is the source of truth."""
from datetime import timedelta
from flask import jsonify, redirect, request, url_for
from flask_login import LoginManager, UserMixin
import dbstore
login_manager = LoginManager()
login_manager.login_view = "admin.login_page"
@login_manager.unauthorized_handler
def _unauthorized():
"""JSON 401 for /admin/api/* fetch calls, HTML redirect for page routes.
Flask-Login's default behavior (redirect to login_view) makes sense for
the HTML shell routes (settings/users/audit/profile pages - a browser
navigation should land on the login page), but a `fetch()` call from the
SPA to /admin/api/* would silently follow that redirect and see a 200
HTML response instead of a 401, e.g. when a session expires, a user is
deleted, or logout happens in another tab - leaving admin pages stuck
instead of bouncing back to login.
"""
if request.path.startswith("/admin/api/"):
return jsonify({"error": "Unauthorized"}), 401
return redirect(url_for("admin.login_page"))
class User(UserMixin):
def __init__(self, user_id: int, username: str):
self.id = str(user_id)
self.username = username
@staticmethod
def from_row(row: dict):
if not row:
return None
return User(row["id"], row["username"])
@login_manager.user_loader
def load_user(user_id: str):
try:
row = dbstore.get_user_by_id(int(user_id))
except (TypeError, ValueError):
return None
return User.from_row(row)
def init_app(app):
# Assigned, not setdefault()'d: Flask pre-populates every SESSION_COOKIE_*
# key in app.config (SAMESITE=None, SECURE=False, and a 31-day lifetime),
# so setdefault() silently never applied any of these - the SameSite=Lax
# this app relies on to blunt cross-site POSTs was not actually in effect.
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
# Read once at startup, hence listed in admin.RESTART_REQUIRED_SETTINGS.
app.config["SESSION_COOKIE_SECURE"] = dbstore.get_setting_typed("session_cookie_secure")
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(
minutes=max(1, dbstore.get_setting_typed("session_lifetime_minutes"))
)
login_manager.init_app(app)