Compare commits
1 Commits
c2b5fa7b2e
...
01925fd3f0
| Author | SHA1 | Date | |
|---|---|---|---|
| 01925fd3f0 |
@ -163,21 +163,10 @@ def _groups_from_claims(claims: dict) -> set[str]:
|
||||
return set()
|
||||
|
||||
|
||||
def _captcha_enabled() -> bool:
|
||||
"""Return True when the login captcha is enabled in SystemSettings."""
|
||||
try:
|
||||
s = SystemSettings.query.first()
|
||||
if s is None:
|
||||
return True # default on for new installs
|
||||
return bool(getattr(s, "login_captcha_enabled", True))
|
||||
except Exception:
|
||||
return True # fail-safe: keep captcha on if DB unreachable
|
||||
|
||||
|
||||
def captcha_required(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if request.method == "POST" and _captcha_enabled():
|
||||
if request.method == "POST":
|
||||
expected = session.get("captcha_answer")
|
||||
provided = (request.form.get("captcha") or "").strip()
|
||||
if not expected or provided != expected:
|
||||
@ -195,7 +184,6 @@ def captcha_required(func):
|
||||
return render_template(
|
||||
"auth/login.html",
|
||||
captcha_question=question,
|
||||
captcha_enabled=True,
|
||||
username=request.form.get("username", ""),
|
||||
entra_sso_enabled=entra_ready,
|
||||
)
|
||||
@ -207,8 +195,6 @@ def captcha_required(func):
|
||||
@auth_bp.route("/login", methods=["GET", "POST"])
|
||||
@captcha_required
|
||||
def login():
|
||||
captcha_on = _captcha_enabled()
|
||||
|
||||
if request.method == "GET":
|
||||
if not users_exist():
|
||||
return redirect(url_for("auth.initial_setup"))
|
||||
@ -225,7 +211,6 @@ def login():
|
||||
return render_template(
|
||||
"auth/login.html",
|
||||
captcha_question=question,
|
||||
captcha_enabled=captcha_on,
|
||||
entra_sso_enabled=entra_ready,
|
||||
)
|
||||
|
||||
@ -248,7 +233,6 @@ def login():
|
||||
return render_template(
|
||||
"auth/login.html",
|
||||
captcha_question=question,
|
||||
captcha_enabled=captcha_on,
|
||||
username=username,
|
||||
entra_sso_enabled=entra_ready,
|
||||
)
|
||||
|
||||
@ -741,7 +741,6 @@ def settings():
|
||||
old_ui_timezone = settings.ui_timezone
|
||||
old_require_daily_dashboard_visit = settings.require_daily_dashboard_visit
|
||||
old_is_sandbox_environment = settings.is_sandbox_environment
|
||||
old_login_captcha_enabled = getattr(settings, "login_captcha_enabled", True)
|
||||
old_graph_tenant_id = settings.graph_tenant_id
|
||||
old_graph_client_id = settings.graph_client_id
|
||||
old_graph_mailbox = settings.graph_mailbox
|
||||
@ -788,9 +787,6 @@ def settings():
|
||||
# Checkbox: present in form = checked, absent = unchecked.
|
||||
settings.is_sandbox_environment = bool(request.form.get("is_sandbox_environment"))
|
||||
|
||||
# Login captcha toggle — same form (General tab).
|
||||
settings.login_captcha_enabled = bool(request.form.get("login_captcha_enabled"))
|
||||
|
||||
# Autotask integration
|
||||
if "autotask_enabled" in request.form:
|
||||
settings.autotask_enabled = bool(request.form.get("autotask_enabled"))
|
||||
@ -985,8 +981,6 @@ def settings():
|
||||
changes_general["require_daily_dashboard_visit"] = {"old": old_require_daily_dashboard_visit, "new": settings.require_daily_dashboard_visit}
|
||||
if old_is_sandbox_environment != settings.is_sandbox_environment:
|
||||
changes_general["is_sandbox_environment"] = {"old": old_is_sandbox_environment, "new": settings.is_sandbox_environment}
|
||||
if old_login_captcha_enabled != settings.login_captcha_enabled:
|
||||
changes_general["login_captcha_enabled"] = {"old": old_login_captcha_enabled, "new": settings.login_captcha_enabled}
|
||||
|
||||
if changes_general:
|
||||
_log_admin_event(
|
||||
|
||||
@ -1447,7 +1447,6 @@ def run_migrations() -> None:
|
||||
migrate_cc_accounts_repo_unique_key()
|
||||
migrate_cc_remove_synthetic_missed_runs()
|
||||
migrate_entra_sso_settings()
|
||||
migrate_system_settings_login_captcha()
|
||||
print("[migrations] All migrations completed.")
|
||||
|
||||
|
||||
@ -1555,37 +1554,6 @@ def migrate_system_settings_sandbox_environment() -> None:
|
||||
print(f"[migrations] Failed to migrate system_settings.is_sandbox_environment: {exc}")
|
||||
|
||||
|
||||
def migrate_system_settings_login_captcha() -> None:
|
||||
"""Add login_captcha_enabled column to system_settings if missing.
|
||||
|
||||
Default TRUE so existing installs keep captcha enabled after the upgrade.
|
||||
"""
|
||||
table = "system_settings"
|
||||
column = "login_captcha_enabled"
|
||||
|
||||
try:
|
||||
engine = db.get_engine()
|
||||
except Exception as exc:
|
||||
print(f"[migrations] Could not get engine for login_captcha_enabled migration: {exc}")
|
||||
return
|
||||
|
||||
try:
|
||||
if _column_exists(table, column):
|
||||
print("[migrations] system_settings.login_captcha_enabled already exists.")
|
||||
return
|
||||
|
||||
with engine.begin() as conn:
|
||||
conn.execute(
|
||||
text(
|
||||
f'ALTER TABLE "{table}" ADD COLUMN {column} BOOLEAN NOT NULL DEFAULT TRUE'
|
||||
)
|
||||
)
|
||||
|
||||
print("[migrations] migrate_system_settings_login_captcha completed.")
|
||||
except Exception as exc:
|
||||
print(f"[migrations] Failed to migrate system_settings.login_captcha_enabled: {exc}")
|
||||
|
||||
|
||||
def migrate_performance_indexes() -> None:
|
||||
"""Add performance indexes for frequently queried foreign key columns.
|
||||
|
||||
|
||||
@ -123,9 +123,6 @@ class SystemSettings(db.Model):
|
||||
# this is not a production environment.
|
||||
is_sandbox_environment = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
# Login page captcha (simple math question). Default True for new installs.
|
||||
login_captcha_enabled = db.Column(db.Boolean, nullable=False, default=True)
|
||||
|
||||
# Cove Data Protection integration settings
|
||||
cove_enabled = db.Column(db.Boolean, nullable=False, default=False)
|
||||
cove_api_url = db.Column(db.String(255), nullable=True) # default: https://api.backup.management/jsonapi
|
||||
|
||||
@ -348,13 +348,10 @@ body.bc-body {
|
||||
============================================================ */
|
||||
.bc-main-auth .bc-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
padding: 32px 16px;
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@ -8,13 +8,13 @@
|
||||
top: 30px;
|
||||
left: -60px;
|
||||
width: 250px;
|
||||
background-color: rgba(220, 53, 69, 0.45);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
text-align: center;
|
||||
transform: rotate(-45deg);
|
||||
z-index: 9999;
|
||||
pointer-events: none; /* Banner itself is not clickable, elements behind it are */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.sandbox-banner-text {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
{% extends "layout/base.html" %}
|
||||
{% block content %}
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-4">
|
||||
<h2 class="mb-3">Login</h2>
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
@ -24,7 +25,6 @@
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{% if captcha_enabled %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Captcha: {{ captcha_question }}</label>
|
||||
<input
|
||||
@ -34,7 +34,6 @@
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
<div class="mt-3 text-center">
|
||||
<a class="btn btn-link" href="{{ url_for('auth.password_reset_request') }}">Forgot password?</a>
|
||||
@ -47,4 +46,5 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@ -161,17 +161,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Security</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="login_captcha_enabled" name="login_captcha_enabled" {% if settings.login_captcha_enabled %}checked{% endif %} />
|
||||
<label class="form-check-label" for="login_captcha_enabled">Enable login captcha</label>
|
||||
</div>
|
||||
<div class="form-text">When enabled, users must solve a simple math question before logging in. Enabled by default.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button type="submit" class="btn btn-primary">Save settings</button>
|
||||
</div>
|
||||
|
||||
@ -2,23 +2,6 @@
|
||||
|
||||
This file documents all changes made to this project via Claude Code.
|
||||
|
||||
## [2026-03-20] (4)
|
||||
|
||||
### Added
|
||||
- Settings → General: "Security" card with login captcha toggle (`login_captcha_enabled`):
|
||||
- When disabled, the math captcha is hidden on the login page and not validated
|
||||
- Default `TRUE` for new installs and existing installs after migration
|
||||
- Migration `migrate_system_settings_login_captcha()` adds the column with `DEFAULT TRUE`
|
||||
- Audit-logged when changed (same pattern as other General settings)
|
||||
|
||||
### Fixed
|
||||
- Login page layout broken when flash messages were present (e.g. "You have been logged out"):
|
||||
- `bc-main-auth .bc-content` now uses `max-width: 480px; margin: 0 auto` as a centered column instead of `align-items: center` on a flex container (which caused Bootstrap `col-md-4` percentage widths to collapse)
|
||||
- `login.html`: replaced `row justify-content-center / col-md-4` with a plain `<div>` — the parent CSS column handles centering
|
||||
|
||||
### Changed
|
||||
- Sandbox banner: semi-transparent (`rgba(220,53,69,0.45)`) instead of solid red
|
||||
|
||||
## [2026-03-20] (3)
|
||||
|
||||
### Added
|
||||
|
||||
Loading…
Reference in New Issue
Block a user