Add active user counter logic; add to admin tools.

Ports in lightly modified logic from the upstream which tracks active
sessions to provide counters and listings to understand site traffic
in the admin panel.
This commit is contained in:
TLSM 2022-09-05 00:41:17 -04:00 committed by Ben Rog-Wilhelm
parent ecbd8179b9
commit 129d644a3f
8 changed files with 125 additions and 31 deletions

View file

@ -88,25 +88,29 @@ mail = Mail(app)
@app.before_request
def before_request():
with open('site_settings.json', 'r') as f:
app.config['SETTINGS'] = json.load(f)
if request.host != app.config["SERVER_NAME"]: return {"error":"Unauthorized host provided."}, 401
if request.headers.get("CF-Worker"): return {"error":"Cloudflare workers are not allowed to access this website."}, 401
if request.host != app.config["SERVER_NAME"]:
return {"error": "Unauthorized host provided."}, 401
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(503)
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"):
abort(503)
g.db = db_session()
ua = request.headers.get("User-Agent","").lower()
if '; wv) ' in ua: g.webview = True
else: g.webview = False
if 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua: g.inferior_browser = True
else: g.inferior_browser = False
g.agent = request.headers.get("User-Agent")
if not g.agent:
return 'Please use a "User-Agent" header!', 403
ua = g.agent.lower()
g.webview = ('; wv) ' in ua)
g.inferior_browser = (
'iphone' in ua or
'ipad' in ua or
'ipod' in ua or
'mac os' in ua or
' firefox/' in ua)
g.timestamp = int(time.time())

View file

@ -72,6 +72,8 @@ PUSHER_KEY = environ.get("PUSHER_KEY", "").strip()
DEFAULT_COLOR = environ.get("DEFAULT_COLOR", "fff").strip()
COLORS = {'ff66ac','805ad5','62ca56','38a169','80ffff','2a96f3','eb4963','ff0000','f39731','30409f','3e98a7','e4432d','7b9ae4','ec72de','7f8fa6', 'f8db58','8cdbe6', DEFAULT_COLOR}
LOGGEDIN_ACTIVE_TIME = 15 * 60
AWARDS = {
"ghost": {
"kind": "ghost",

View file

@ -3,9 +3,15 @@ from .alerts import *
from files.helpers.const import *
from files.__main__ import db_session
from random import randint
import user_agents
import time
def get_logged_in_user():
if not (hasattr(g, 'db') and g.db): g.db = db_session()
if hasattr(g, 'v'):
return g.v
if not (hasattr(g, 'db') and g.db):
g.db = db_session()
v = None
@ -19,7 +25,7 @@ def get_logged_in_user():
lo_user = session.get("lo_user")
if lo_user:
id = int(lo_user)
v = g.db.query(User).filter_by(id=id).one_or_none()
v = g.db.query(User).get(id)
if v:
nonce = session.get("login_nonce", 0)
if nonce < v.login_nonce or v.id != id: abort(401)
@ -35,14 +41,40 @@ def get_logged_in_user():
v.client = None
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level):
if request.method.lower() != "get" \
and app.config['SETTINGS']['Read-only mode'] \
and not (v and v.admin_level):
abort(403)
if v and v.patron:
if request.content_length and request.content_length > 16 * 1024 * 1024: abort(413)
elif request.content_length and request.content_length > 8 * 1024 * 1024: abort(413)
if request.content_length and request.content_length > 8 * 1024 * 1024:
abort(413)
if not session.get("session_id"):
session.permanent = True
session["session_id"] = secrets.token_hex(49)
# Active User Counters
loggedin = cache.get(f'{SITE}_loggedin') or {}
loggedout = cache.get(f'{SITE}_loggedout') or {}
timestamp = int(time.time())
if v:
if session["session_id"] in loggedout:
del loggedout[session["session_id"]]
loggedin[v.id] = timestamp
else:
ua = str(user_agents.parse(g.agent))
if 'spider' not in ua.lower() and 'bot' not in ua.lower():
loggedout[session["session_id"]] = (timestamp, ua)
g.loggedin_counter = len([x for x in loggedin.values() \
if (timestamp - x) < LOGGEDIN_ACTIVE_TIME])
g.loggedout_counter = len([x for x in loggedout.values() \
if (timestamp - x[0]) < LOGGEDIN_ACTIVE_TIME])
cache.set(f'{SITE}_loggedin', loggedin)
cache.set(f'{SITE}_loggedout', loggedout)
g.v = v
return v
def check_ban_evade(v):
@ -53,12 +85,10 @@ def check_ban_evade(v):
def auth_desired(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
@ -68,13 +98,11 @@ def auth_desired(f):
def auth_required(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
@ -84,9 +112,7 @@ def auth_required(f):
def is_not_permabanned(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
@ -94,7 +120,6 @@ def is_not_permabanned(f):
if v.is_suspended_permanently:
return {"error": "Forbidden: you are permabanned."}, 403
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
@ -106,14 +131,11 @@ def admin_level_required(x):
def wrapper_maker(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
if v.admin_level < x: abort(403)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__

View file

@ -764,6 +764,25 @@ def users_list(v):
page=page,
)
@app.get('/admin/loggedin')
@admin_level_required(2)
def loggedin_list(v):
ids = [x for x, val in cache.get(f'{SITE}_loggedin').items() \
if (time.time() - val) < LOGGEDIN_ACTIVE_TIME]
users = g.db.query(User).filter(User.id.in_(ids)) \
.order_by(User.admin_level.desc(), User.truecoins.desc()).all()
return render_template("admin/loggedin.html", v=v, users=users)
@app.get('/admin/loggedout')
@admin_level_required(2)
def loggedout_list(v):
users = sorted([val[1] for x, val in cache.get(f'{SITE}_loggedout').items() \
if (time.time() - val[0]) < LOGGEDIN_ACTIVE_TIME])
return render_template("admin/loggedout.html", v=v, users=users)
@app.get("/admin/alt_votes")
@admin_level_required(2)
def alt_votes_get(v):

View file

@ -8,7 +8,13 @@
{% block content %}
<pre></pre>
<pre></pre>
<h3>&nbsp;Admin Tools</h3>
<h3>Admin Tools</h3>
<div class="mb-3">
<strong>Users Here Now:</strong> {{g.loggedin_counter + g.loggedout_counter}} &mdash;
<a href="/admin/loggedin">{{g.loggedin_counter}} Logged-In</a> |
<a href="/admin/loggedout">{{g.loggedout_counter}} Logged-Out</a>
</div>
<h4>Content</h4>
<ul>

View file

@ -0,0 +1,21 @@
{% extends "settings2.html" %}
{% block content %}
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Truescore</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.name_color}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.name_color}}"{% endif %}>{{user.username}}</span></a></td>
<td>{{user.truecoins}}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "settings2.html" %}
{% block content %}
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Details</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td>{{user}}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -21,6 +21,7 @@ qrcode
redis
requests
SQLAlchemy
user-agents
psycopg2-binary
pusher_push_notifications
pyenchant