diff --git a/docker/routlin-dash/app/action_accountlogout.py b/docker/routlin-dash/app/action_accountlogout.py index 2349017..829f296 100644 --- a/docker/routlin-dash/app/action_accountlogout.py +++ b/docker/routlin-dash/app/action_accountlogout.py @@ -7,9 +7,5 @@ bp = Blueprint('accountlogout', __name__) @bp.route('/action/accountlogout/logout', methods=['POST']) @auth.require_level('viewer') def logout(): - sid = session.get('session_id', '') - if sid: - import config_utils as _cu - _cu.record_session_logout(sid) session.clear() return redirect('/overview') diff --git a/docker/routlin-dash/app/config_utils.py b/docker/routlin-dash/app/config_utils.py index 3bddca6..2784bd4 100644 --- a/docker/routlin-dash/app/config_utils.py +++ b/docker/routlin-dash/app/config_utils.py @@ -610,72 +610,6 @@ def revert_group_chain(group_uuid): return errors, succeeded, failed -# Sessions DB ======================================================= - -def _open_sessions_db(): - import sqlite3 as _sq, time as _t - con = _sq.connect(SESSIONS_DB, timeout=5) - con.execute('PRAGMA journal_mode=WAL') - con.executescript(''' - CREATE TABLE IF NOT EXISTS sessions ( - session_id TEXT PRIMARY KEY, - email TEXT NOT NULL, - access_level TEXT NOT NULL DEFAULT '', - logged_in_at INTEGER NOT NULL, - last_seen INTEGER NOT NULL - ); - ''') - con.commit() - return con - -def record_session_login(session_id, email, access_level): - import time as _t - try: - con = _open_sessions_db() - now = int(_t.time()) - con.execute( - 'INSERT OR REPLACE INTO sessions(session_id, email, access_level, logged_in_at, last_seen) VALUES(?,?,?,?,?)', - (session_id, email, access_level, now, now) - ) - con.commit() - con.close() - except Exception: - pass - -def record_session_logout(session_id): - try: - con = _open_sessions_db() - con.execute('DELETE FROM sessions WHERE session_id=?', (session_id,)) - con.commit() - con.close() - except Exception: - pass - -def record_session_activity(session_id): - import time as _t - try: - con = _open_sessions_db() - con.execute('UPDATE sessions SET last_seen=? WHERE session_id=?', (int(_t.time()), session_id)) - con.commit() - con.close() - except Exception: - pass - -def get_active_sessions(): - import time as _t - cutoff = int(_t.time()) - 31 * 86400 - try: - con = _open_sessions_db() - rows = con.execute( - 'SELECT email, access_level, logged_in_at, last_seen FROM sessions WHERE last_seen > ? ORDER BY last_seen DESC', - (cutoff,) - ).fetchall() - con.close() - return rows - except Exception: - return [] - - # Misc ============================================================== def run_apply(): diff --git a/docker/routlin-dash/app/main.py b/docker/routlin-dash/app/main.py index ef7b1de..9d44208 100644 --- a/docker/routlin-dash/app/main.py +++ b/docker/routlin-dash/app/main.py @@ -29,8 +29,11 @@ from pages.captiveportal.action import bp as captiveportal_bp from action_accountlogout import bp as accountlogout_bp from api_apply_health import bp as api_apply_health_bp +from session_interface import SqliteSessionInterface + app = Flask(__name__) -app.secret_key = os.environ.get('SECRET_KEY', os.urandom(24)) +app.secret_key = os.environ.get('SECRET_KEY', os.urandom(24)) +app.session_interface = SqliteSessionInterface(config_utils.SESSIONS_DB) # Static www/ serving ================================================= @@ -77,9 +80,6 @@ def serve_view(page_name): view_req = view_def.get('client_requirement') level = factory.client_level() - sid = session.get('session_id', '') - if sid and level > 0: - config_utils.record_session_activity(sid) if not factory.passes(view_req, level): return redirect('/overview' if level > 0 else '/accountlogin') diff --git a/docker/routlin-dash/app/pages/accountlogin/action.py b/docker/routlin-dash/app/pages/accountlogin/action.py index 5dec1bd..313378a 100644 --- a/docker/routlin-dash/app/pages/accountlogin/action.py +++ b/docker/routlin-dash/app/pages/accountlogin/action.py @@ -56,10 +56,4 @@ def form_login(): session['apply_changes_immediately'] = False session.permanent = True - import uuid as _uuid - sid = str(_uuid.uuid4()) - session['session_id'] = sid - import config_utils as _cu - _cu.record_session_login(sid, account['email_address'], account.get('access_level', 'viewer')) - return redirect('/overview') diff --git a/docker/routlin-dash/app/pages/accountmanage/action.py b/docker/routlin-dash/app/pages/accountmanage/action.py index 0f44907..1b48bca 100644 --- a/docker/routlin-dash/app/pages/accountmanage/action.py +++ b/docker/routlin-dash/app/pages/accountmanage/action.py @@ -1,6 +1,6 @@ from pathlib import Path from flask import Blueprint, request, session, redirect, flash -import json, re +import json, os, re, sqlite3 from datetime import datetime, timezone import auth import config_utils @@ -100,6 +100,24 @@ def accounts_edit(): return redirect(f'/{_PAGE}') +@bp.route('/action/accountmanage/session_invalidate', methods=['POST']) +@auth.require_level('manager') +def session_invalidate(): + sid = request.form.get('session_id', '').strip() + if not sid: + flash('Invalid request.', 'error') + return redirect(f'/{_PAGE}') + try: + con = sqlite3.connect(config_utils.SESSIONS_DB, timeout=5) + con.execute('DELETE FROM sessions WHERE session_id=?', (sid,)) + con.commit() + con.close() + flash('Session invalidated.', 'success') + except Exception: + flash('Failed to invalidate session.', 'error') + return redirect(f'/{_PAGE}') + + @bp.route('/action/accountmanage/accounts_delete', methods=['POST']) @auth.require_level('manager') def accounts_delete(): @@ -118,7 +136,10 @@ def accounts_delete(): target = accounts[row_index] - if target.get('email_address', '').lower() == session.get('email_address', '').lower(): + target_email = target.get('email_address', '').lower() + current_email = session.get('email_address', '').lower() + initial_email = os.environ.get('INITIAL_MANAGER_EMAIL', '').strip().lower() + if target_email == current_email and target_email != initial_email: flash('You cannot remove your own account.', 'error') return redirect(f'/{_PAGE}') diff --git a/docker/routlin-dash/app/pages/accountmanage/view.py b/docker/routlin-dash/app/pages/accountmanage/view.py index dfb2d57..a48bc67 100644 --- a/docker/routlin-dash/app/pages/accountmanage/view.py +++ b/docker/routlin-dash/app/pages/accountmanage/view.py @@ -1,4 +1,5 @@ import json +import sqlite3 import time from datetime import datetime, timezone import config_utils @@ -14,28 +15,53 @@ def _fmt_ts(ts): def _active_sessions_table(): - rows = config_utils.get_active_sessions() - no_data = '
No active sessions.
' + try: + con = sqlite3.connect(config_utils.SESSIONS_DB, timeout=5) + rows = con.execute( + 'SELECT session_id, email, access_level, created_at, last_seen' + ' FROM sessions ORDER BY last_seen DESC' + ).fetchall() + con.close() + except Exception: + rows = [] + if not rows: - return no_data + return 'No active sessions.
' + now = int(time.time()) trs = '' - for email, access_level, logged_in_at, last_seen in rows: - ago = config_utils.relative_time(int(last_seen), now) + for sid, email, access_level, created_at, last_seen in rows: + online = (now - int(last_seen)) < 300 + badge = ( + 'Online' + if online else + 'Offline' + ) + btn = ( + f'' + ) trs += ( f'| Access Level | ' + 'Status | ' 'Logged In | ' 'Last Seen | ' + '' ' |
|---|