05/06 Fix some notable and quality issues

This commit is contained in:
2026-05-06 11:47:20 -04:00
parent 830bb51f24
commit 02b3e1c45d
11 changed files with 125 additions and 46 deletions
+27 -1
View File
@@ -1,6 +1,32 @@
from functools import wraps
from flask import flash, redirect, url_for
from flask import flash, redirect, url_for, request
from flask_login import current_user
from urllib.parse import urlparse
# ── Open-redirect guard ───────────────────────────────────────────────────────
def safe_redirect_url(url: str | None, fallback: str | None = None) -> str:
"""Return *url* only if it is a safe relative URL on this host.
Rejects any URL that carries a network location (netloc) or an explicit
scheme, preventing open-redirect attacks where a crafted link contains
next=https://evil.com.
Parameters
----------
url : The candidate redirect target (may be None).
fallback : Returned when *url* is absent or unsafe.
Defaults to the dashboard index.
"""
if fallback is None:
fallback = url_for('dashboard.index')
if not url:
return fallback
parsed = urlparse(url)
if parsed.netloc or parsed.scheme:
return fallback
return url
def admin_required(f):
@wraps(f)
+2 -2
View File
@@ -1,5 +1,5 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from flask_wtf.file import FileField, FileAllowed, MultipleFileField
from wtforms import (StringField, PasswordField, SelectField, TextAreaField,
DecimalField, BooleanField, IntegerField, HiddenField,
RadioField)
@@ -174,7 +174,7 @@ class IssueUpdateForm(FlaskForm):
assigned_to = SelectField('Assign To', coerce=int, validators=[Optional()])
update_notes = TextAreaField('Update Notes', validators=[Optional(), Length(max=1000)])
result_notes = TextAreaField('Result Notes', validators=[Optional(), Length(max=2000)])
result_photos = FileField('Result Photos', validators=[
result_photos = MultipleFileField('Result Photos', validators=[
Optional(),
FileAllowed(['jpg','jpeg','png','gif'], 'Images only.')
])
+4 -1
View File
@@ -110,9 +110,12 @@ def send_sla_alerts():
logger = logging.getLogger(__name__)
# yield_per streams rows in batches of 100 rather than loading all open
# issues into memory at once. At current scale this is a no-op difference,
# but it prevents a memory spike if the issue count grows large.
open_issues = Issue.query.filter(
Issue.status.in_(['open', 'in_progress', 'pending_verification'])
).all()
).yield_per(100)
total_sent = 0