from pathlib import Path from flask import Blueprint, request, session, redirect, flash import json, os, re, sqlite3 from datetime import datetime, timezone import auth import config_utils import sanitize _PAGE = Path(__file__).parent.name bp = Blueprint(_PAGE, __name__) VALID_LEVELS = {'viewer', 'administrator', 'manager'} def _load_accounts(): try: with open(config_utils.ACCOUNTS_FILE) as f: return json.load(f) except Exception: return {'accounts': []} def _save_accounts(data): with open(config_utils.ACCOUNTS_FILE, 'w') as f: json.dump(data, f, indent=2) @bp.route('/action/accountmanage/accounts_add', methods=['POST']) @auth.require_level('manager') def accounts_add(): email = sanitize.email(request.form.get('email_address', '')) access_level = request.form.get('access_level', '').strip() if not email: flash('Email address is required.', 'error') return redirect(f'/{_PAGE}') if not re.match(r'^[^@\s]+@[^@\s]+\.[^@\s]+$', email): flash('Email address does not appear to be valid.', 'error') return redirect(f'/{_PAGE}') if access_level not in VALID_LEVELS: flash('Invalid access level.', 'error') return redirect(f'/{_PAGE}') data = _load_accounts() accounts = data.get('accounts', []) if any(a.get('email_address', '').lower() == email for a in accounts): flash('An account with that email address already exists.', 'error') return redirect(f'/{_PAGE}') now = datetime.now(tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') accounts.append({ 'email_address': email, 'access_level': access_level, 'account_created_utc': now, 'account_created_by': session.get('email_address', ''), 'hashed_password': '', 'timezone': '', }) data['accounts'] = accounts _save_accounts(data) flash(f'Authorization added for {email}. User must complete account setup via the Create Account page.', 'success') return redirect(f'/{_PAGE}') @bp.route('/action/accountmanage/accounts_edit', methods=['POST']) @auth.require_level('manager') def accounts_edit(): try: row_index = int(request.form.get('row_index', '')) except (ValueError, TypeError): flash('Invalid request.', 'error') return redirect(f'/{_PAGE}') access_level = request.form.get('access_level', '').strip() if access_level not in VALID_LEVELS: flash('Invalid access level.', 'error') return redirect(f'/{_PAGE}') data = _load_accounts() accounts = data.get('accounts', []) if row_index < 0 or row_index >= len(accounts): flash('Account not found.', 'error') return redirect(f'/{_PAGE}') target = accounts[row_index] if target.get('email_address', '').lower() == session.get('email_address', '').lower(): flash('You cannot change your own access level.', 'error') return redirect(f'/{_PAGE}') accounts[row_index]['access_level'] = access_level data['accounts'] = accounts _save_accounts(data) flash('Account updated.', 'success') 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(): try: row_index = int(request.form.get('row_index', '')) except (ValueError, TypeError): flash('Invalid request.', 'error') return redirect(f'/{_PAGE}') data = _load_accounts() accounts = data.get('accounts', []) if row_index < 0 or row_index >= len(accounts): flash('Account not found.', 'error') return redirect(f'/{_PAGE}') target = accounts[row_index] 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}') removed_email = target.get('email_address', '') accounts.pop(row_index) data['accounts'] = accounts _save_accounts(data) flash(f'Account for {removed_email} has been removed.', 'success') return redirect(f'/{_PAGE}')