06/03 Optimize app
This commit is contained in:
@@ -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'))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user