06/03 Optimize app

This commit is contained in:
2026-06-03 16:22:02 -04:00
parent ec1c0fc7c1
commit eb85e70793
16 changed files with 406 additions and 5 deletions
+6
View File
@@ -12,6 +12,7 @@ from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length
from app.models.user import User
from app.extensions import db, limiter
from app.utils.audit import audit
from datetime import datetime
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@@ -48,12 +49,14 @@ def login():
login_user(user, remember=form.remember_me.data)
user.last_login = datetime.utcnow()
db.session.commit()
audit('login_success', f'user={user.username}')
log.info('[auth] user %s logged in', user.username)
next_page = request.args.get('next', '')
if not next_page.startswith('/'):
next_page = url_for('dashboard.index')
return redirect(next_page)
audit('login_failed', f'username={form.username.data!r}')
log.warning('[auth] failed login attempt for username=%r ip=%s',
form.username.data, request.remote_addr)
flash('Invalid username or password.', 'danger')
@@ -96,6 +99,7 @@ def totp_verify():
login_user(user, remember=remember)
user.last_login = datetime.utcnow()
db.session.commit()
audit('login_success_2fa', f'user={user.username}')
log.info('[auth] TOTP verified for user %s', user.username)
return redirect(next_url)
log.warning('[auth] invalid TOTP code for user %s ip=%s',
@@ -142,6 +146,7 @@ def totp_setup():
user.totp_enabled = True
db.session.commit()
session.pop('_totp_setup_secret', None)
audit('totp_enabled', f'user={user.username}')
log.info('[auth] TOTP enabled for user %s', user.username)
flash('Two-factor authentication enabled successfully.', 'success')
return redirect(url_for('settings.index'))
@@ -165,6 +170,7 @@ def totp_disable():
user.totp_enabled = False
user.totp_secret = None
db.session.commit()
audit('totp_disabled', f'user={user.username}')
log.info('[auth] TOTP disabled for user %s', user.username)
flash('Two-factor authentication disabled.', 'info')
return redirect(url_for('settings.index'))
+2
View File
@@ -73,6 +73,7 @@ def index():
).scalar()
net_cash_flow = float(total_income) - float(total_expense)
savings_rate = round(net_cash_flow / float(total_income) * 100, 1) if total_income else 0
# ── Accounts ─────────────────────────────────────
accounts = Account.query.filter_by(is_active=True).order_by(Account.name).all()
@@ -152,6 +153,7 @@ def index():
total_income=float(total_income),
total_expense=float(total_expense),
net_cash_flow=net_cash_flow,
savings_rate=savings_rate,
accounts=accounts,
total_assets=total_assets,
total_liabilities=total_liabilities,
+3
View File
@@ -17,6 +17,7 @@ from app.services.schwab_service import (
schwab_bp = Blueprint('schwab', __name__, url_prefix='/schwab')
log = logging.getLogger(__name__)
from app.utils.audit import audit
def _active_connection():
@@ -113,6 +114,7 @@ def callback():
))
db.session.commit()
audit('schwab_connected')
flash('Schwab connected successfully. Map your accounts to get started.', 'success')
return redirect(url_for('schwab.map_accounts'))
@@ -314,5 +316,6 @@ def disconnect():
for sa in connection.accounts:
sa.is_active = False
db.session.commit()
audit('schwab_disconnected')
flash('Disconnected from Schwab. Your imported transactions are kept.', 'info')
return redirect(url_for('schwab.index'))
+18
View File
@@ -450,3 +450,21 @@ def view_receipt(filename):
upload_dir = current_app.config.get('UPLOAD_FOLDER', '/home/pfm/app/uploads')
# Strip any path components to prevent directory traversal
return send_from_directory(upload_dir, os.path.basename(filename))
@settings_bp.route('/audit')
@login_required
def audit_log():
from app.models.audit_log import AuditLog
page = request.args.get('page', 1, type=int)
action = request.args.get('action', '')
query = AuditLog.query.order_by(AuditLog.timestamp.desc())
if action:
query = query.filter(AuditLog.action == action)
pagination = query.paginate(page=page, per_page=50, error_out=False)
actions = [r[0] for r in db.session.query(AuditLog.action).distinct().all()]
return render_template('settings/audit.html',
pagination=pagination,
logs=pagination.items,
actions=actions,
action=action)