diff --git a/reconscript/templates/layout.html b/reconscript/templates/layout.html
index a5adbba..2335cf5 100644
--- a/reconscript/templates/layout.html
+++ b/reconscript/templates/layout.html
@@ -47,12 +47,7 @@
ReconScript
diff --git a/reconscript/ui.py b/reconscript/ui.py
index 10bc9e8..0aa2c6e 100644
--- a/reconscript/ui.py
+++ b/reconscript/ui.py
@@ -1,4 +1,4 @@
-"""Flask UI for ReconScript with consent enforcement and RBAC."""
+"""Flask UI for ReconScript without authentication or RBAC."""
from __future__ import annotations
@@ -6,7 +6,6 @@
import logging
import os
import uuid
-from functools import wraps
from pathlib import Path
from typing import Optional
@@ -14,7 +13,6 @@
Flask,
abort,
flash,
- current_app,
redirect,
render_template,
Response,
@@ -22,14 +20,6 @@
send_from_directory,
url_for,
)
-from flask_login import (
- LoginManager,
- UserMixin,
- current_user,
- login_required,
- login_user,
- logout_user,
-)
from .consent import ConsentError, load_manifest, validate_manifest
from .core import ReconError, run_recon
@@ -42,13 +32,6 @@
DEV_KEYS_DIR = Path(__file__).resolve().parents[1] / "keys"
-class StaticUser(UserMixin):
- def __init__(self, username: str, role: str) -> None:
- self.id = username
- self.role = role
-
-
-<<<<<<< HEAD
def _allow_dev_secrets() -> bool:
return os.environ.get("ALLOW_DEV_SECRETS", "false").lower() == "true"
@@ -56,22 +39,14 @@ def _allow_dev_secrets() -> bool:
def _enforce_secret_path(path: Path, *, env_var: str) -> Path:
resolved = path.expanduser().resolve()
if not resolved.exists():
- raise RuntimeError(f"{env_var} must point to an existing file (got {resolved}).")
-=======
-def _load_secret_key() -> bytes:
- secret_path = Path(
- os.environ.get("FLASK_SECRET_KEY_FILE", "keys/dev_flask_secret.key")
- )
->>>>>>> 74be2f0 (style: fix reporters.py syntax and apply Black formatting)
- try:
- if DEV_KEYS_DIR in resolved.parents and not _allow_dev_secrets():
- raise RuntimeError(
- f"{env_var} refers to a developer sample key. Provide deployment-specific secrets or set ALLOW_DEV_SECRETS=true for local testing."
- )
- except RuntimeError:
- raise
- except Exception:
- pass
+ raise RuntimeError(
+ f"{env_var} must point to an existing file (got {resolved})."
+ )
+ if DEV_KEYS_DIR in resolved.parents and not _allow_dev_secrets():
+ raise RuntimeError(
+ f"{env_var} refers to a developer sample key. Provide deployment-specific "
+ "secrets or set ALLOW_DEV_SECRETS=true for local testing."
+ )
return resolved
@@ -85,46 +60,12 @@ def _load_secret_key() -> bytes:
try:
secret = secret_path.read_bytes().strip()
except OSError as exc:
-<<<<<<< HEAD
- raise RuntimeError(f"Unable to read Flask secret key from {secret_path}: {exc}") from exc
- if not secret:
- raise RuntimeError(f"Secret key file {secret_path} is empty.")
- return secret
-=======
raise RuntimeError(
f"Unable to read Flask secret key from {secret_path}: {exc}"
) from exc
->>>>>>> 74be2f0 (style: fix reporters.py syntax and apply Black formatting)
-
-
-def _rbac_required(role: str):
- def decorator(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- if not current_user.is_authenticated:
- return redirect(url_for("login"))
- if (
- current_app.config.get("RBAC_ENABLED")
- and getattr(current_user, "role", None) != role
- ):
- abort(403)
- return func(*args, **kwargs)
-
- return wrapper
-
- return decorator
-
-
-def _load_user_credentials() -> tuple[str, str]:
- username = os.environ.get("ADMIN_USER", "").strip()
- password = os.environ.get("ADMIN_PASSWORD", "").strip()
- if not username or not password:
- raise RuntimeError("ADMIN_USER and ADMIN_PASSWORD must be set for the ReconScript UI.")
- if not _allow_dev_secrets() and (username == "admin" or password == "changeme"):
- raise RuntimeError("Default credentials are not permitted. Set strong ADMIN_USER and ADMIN_PASSWORD values.")
- if len(password) < 12 and not _allow_dev_secrets():
- raise RuntimeError("ADMIN_PASSWORD must be at least 12 characters long.")
- return username, password
+ if not secret:
+ raise RuntimeError(f"Secret key file {secret_path} is empty.")
+ return secret
def _store_upload(file_storage) -> Path:
@@ -139,38 +80,15 @@ def _store_upload(file_storage) -> Path:
def create_app() -> Flask:
configure_logging()
- public_ui = os.environ.get("ENABLE_PUBLIC_UI", "false").lower() == "true"
- rbac_enabled = os.environ.get("ENABLE_RBAC", "false").lower() == "true"
- if public_ui and not rbac_enabled:
- raise RuntimeError(
- "ENABLE_PUBLIC_UI=true requires ENABLE_RBAC=true to protect access."
- )
-
app = Flask(__name__)
- app.config["RBAC_ENABLED"] = rbac_enabled
- app.config["PUBLIC_UI"] = public_ui
+ app.config["PUBLIC_UI"] = os.environ.get("ENABLE_PUBLIC_UI", "false").lower() == "true"
app.config["UPLOAD_FOLDER"] = str(ensure_results_dir() / "uploads")
app.secret_key = _load_secret_key()
- login_manager = LoginManager(app)
- login_manager.login_view = "login"
-
- username, password = _load_user_credentials()
- user = StaticUser(username, "admin")
-
- @login_manager.user_loader
- def load_user(
- user_id: str,
- ) -> Optional[StaticUser]: # pragma: no cover - simple lookup
- if user_id == user.id:
- return user
- return None
-
@app.context_processor
def inject_globals():
return {
"public_ui": app.config["PUBLIC_UI"],
- "rbac_enabled": app.config["RBAC_ENABLED"],
}
@app.route("/healthz")
@@ -184,26 +102,6 @@ def metrics() -> Response:
return Response("", status=204)
return Response(payload, mimetype=content_type)
- @app.route("/login", methods=["GET", "POST"])
- def login():
- if request.method == "POST":
- submitted_user = request.form.get("username", "")
- submitted_pass = request.form.get("password", "")
- LOGGER.info(
- "ui.login.attempt",
- extra={"event": "ui.login.attempt", "username": submitted_user, "success": submitted_user == username and submitted_pass == password},
- )
- if submitted_user == username and submitted_pass == password:
- login_user(user)
- return redirect(url_for("index"))
- flash("Invalid credentials", "error")
- return render_template("login.html")
-
- @app.route("/logout")
- def logout():
- logout_user()
- return redirect(url_for("login"))
-
def _handle_manifest() -> tuple[Optional[Path], Optional[object]]:
file = request.files.get("consent_file")
if not file or not file.filename:
@@ -214,8 +112,6 @@ def _handle_manifest() -> tuple[Optional[Path], Optional[object]]:
return temp_path, manifest
@app.route("/", methods=["GET", "POST"])
- @login_required
- @_rbac_required("admin")
def index():
if request.method == "POST":
target = request.form.get("target", "")
@@ -242,7 +138,14 @@ def index():
flash(f"Consent manifest invalid: {exc}", "error")
if consent_path:
consent_path.unlink(missing_ok=True)
- LOGGER.warning("ui.consent.invalid", extra={"event": "ui.consent.invalid", "target": target, "error": str(exc)})
+ LOGGER.warning(
+ "ui.consent.invalid",
+ extra={
+ "event": "ui.consent.invalid",
+ "target": target,
+ "error": str(exc),
+ },
+ )
return render_template("index.html")
try:
@@ -269,10 +172,19 @@ def index():
flash(str(exc), "error")
if consent_path:
consent_path.unlink(missing_ok=True)
- LOGGER.error("ui.scan.failed", extra={"event": "ui.scan.failed", "target": target, "error": str(exc)})
+ LOGGER.error(
+ "ui.scan.failed",
+ extra={
+ "event": "ui.scan.failed",
+ "target": target,
+ "error": str(exc),
+ },
+ )
return render_template("index.html")
- persisted = persist_report(report, consent_source=consent_path, sign=False)
+ persisted = persist_report(
+ report, consent_source=consent_path, sign=False
+ )
if consent_path:
consent_path.unlink(missing_ok=True)
LOGGER.info(
@@ -288,8 +200,6 @@ def index():
return render_template("index.html")
@app.route("/reports/")
- @login_required
- @_rbac_required("admin")
def report_detail(report_id: str):
report_dir = ensure_results_dir() / report_id
report_file = report_dir / "report.json"
@@ -301,10 +211,10 @@ def report_detail(report_id: str):
)
@app.route("/results/")
- @login_required
- @_rbac_required("admin")
def download_result(filename: str):
- return send_from_directory(ensure_results_dir(), filename, as_attachment=True)
+ return send_from_directory(
+ ensure_results_dir(), filename, as_attachment=True
+ )
return app
@@ -313,14 +223,14 @@ def main() -> None:
app = create_app()
host = "0.0.0.0" if app.config["PUBLIC_UI"] else "127.0.0.1"
port = int(os.environ.get("DEFAULT_PORT", "5000"))
- LOGGER.warning(
- "UI running with RBAC %s",
- "enabled" if app.config["RBAC_ENABLED"] else "disabled",
- )
if app.config["PUBLIC_UI"]:
LOGGER.warning(
"Public UI mode enabled — ensure reverse proxy protections are in place."
)
+ else:
+ LOGGER.warning(
+ "Running ReconScript UI without authentication. Restrict access appropriately."
+ )
app.run(host=host, port=port, threaded=True)