Core: - paths.py: centralized ~/.soosef/ path constants - config.py: JSON config loader with dataclass defaults - exceptions.py: SoosefError hierarchy - cli.py: unified Click CLI wrapping stegasoo + verisoo + native commands Keystore: - manager.py: unified key management (Ed25519 identity + channel keys) - models.py: IdentityInfo, KeystoreStatus dataclasses - export.py: encrypted key bundle export/import for USB transfer Fieldkit: - killswitch.py: ordered emergency data destruction (keys first) - deadman.py: dead man's switch with check-in timer - tamper.py: SHA-256 file integrity baseline + checking - usb_monitor.py: pyudev USB whitelist enforcement - geofence.py: haversine-based GPS boundary checking Web frontend (Flask app factory + blueprints): - app.py: create_app() factory with context processor - blueprints: stego, attest, fieldkit, keys, admin - templates: base.html (dark theme, unified nav), dashboard, all section pages - static: CSS, favicon Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
5.6 KiB
Python
172 lines
5.6 KiB
Python
"""
|
|
SooSeF Web Frontend
|
|
|
|
Flask application factory that unifies Stegasoo (steganography) and Verisoo
|
|
(provenance attestation) into a single web UI with fieldkit security features.
|
|
|
|
Built on Stegasoo's production-grade web UI patterns:
|
|
- Subprocess isolation for crash-safe stegasoo operations
|
|
- Async jobs with progress polling for large images
|
|
- Context processors for global template variables
|
|
- File-based temp storage with auto-expiry
|
|
|
|
BLUEPRINT STRUCTURE
|
|
===================
|
|
|
|
/ → index (dashboard)
|
|
/login, /logout → auth (adapted from stegasoo)
|
|
/setup → first-run wizard
|
|
|
|
/encode, /decode, /generate, /tools → stego blueprint
|
|
/attest, /verify → attest blueprint
|
|
/fieldkit/* → fieldkit blueprint
|
|
/keys/* → keys blueprint
|
|
/admin/* → admin blueprint
|
|
"""
|
|
|
|
import os
|
|
import secrets
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from flask import Flask, redirect, render_template, url_for
|
|
|
|
import soosef
|
|
from soosef.config import SoosefConfig
|
|
from soosef.paths import AUTH_DB, INSTANCE_DIR, SECRET_KEY_FILE, TEMP_DIR, ensure_dirs
|
|
|
|
# Suppress numpy/scipy warnings in subprocesses
|
|
os.environ["NUMPY_MADVISE_HUGEPAGE"] = "0"
|
|
os.environ["OMP_NUM_THREADS"] = "1"
|
|
|
|
# Maximum upload size (50 MB default)
|
|
MAX_FILE_SIZE = 50 * 1024 * 1024
|
|
|
|
|
|
def create_app(config: SoosefConfig | None = None) -> Flask:
|
|
"""Application factory."""
|
|
config = config or SoosefConfig.load()
|
|
ensure_dirs()
|
|
|
|
app = Flask(
|
|
__name__,
|
|
instance_path=str(INSTANCE_DIR),
|
|
template_folder=str(Path(__file__).parent / "templates"),
|
|
static_folder=str(Path(__file__).parent / "static"),
|
|
)
|
|
|
|
app.config["MAX_CONTENT_LENGTH"] = config.max_upload_mb * 1024 * 1024
|
|
app.config["AUTH_ENABLED"] = config.auth_enabled
|
|
app.config["HTTPS_ENABLED"] = config.https_enabled
|
|
app.config["SOOSEF_CONFIG"] = config
|
|
|
|
# Persist secret key so sessions survive restarts
|
|
_load_secret_key(app)
|
|
|
|
# ── Register blueprints ───────────────────────────────────────
|
|
|
|
from frontends.web.blueprints.stego import bp as stego_bp
|
|
from frontends.web.blueprints.attest import bp as attest_bp
|
|
from frontends.web.blueprints.fieldkit import bp as fieldkit_bp
|
|
from frontends.web.blueprints.keys import bp as keys_bp
|
|
from frontends.web.blueprints.admin import bp as admin_bp
|
|
|
|
app.register_blueprint(stego_bp)
|
|
app.register_blueprint(attest_bp)
|
|
app.register_blueprint(fieldkit_bp)
|
|
app.register_blueprint(keys_bp)
|
|
app.register_blueprint(admin_bp)
|
|
|
|
# ── Context processor (injected into ALL templates) ───────────
|
|
|
|
@app.context_processor
|
|
def inject_globals():
|
|
from soosef.keystore import KeystoreManager
|
|
|
|
ks = KeystoreManager()
|
|
ks_status = ks.status()
|
|
|
|
# Check fieldkit alert level
|
|
fieldkit_status = "ok"
|
|
if config.deadman_enabled:
|
|
from soosef.fieldkit.deadman import DeadmanSwitch
|
|
|
|
dm = DeadmanSwitch()
|
|
if dm.should_fire():
|
|
fieldkit_status = "alarm"
|
|
elif dm.is_overdue():
|
|
fieldkit_status = "warn"
|
|
|
|
# Check stegasoo capabilities
|
|
try:
|
|
from stegasoo import has_dct_support, HAS_AUDIO_SUPPORT
|
|
|
|
has_dct = has_dct_support()
|
|
has_audio = HAS_AUDIO_SUPPORT
|
|
except ImportError:
|
|
has_dct = False
|
|
has_audio = False
|
|
|
|
# Check verisoo availability
|
|
try:
|
|
import verisoo # noqa: F401
|
|
|
|
has_verisoo = True
|
|
except ImportError:
|
|
has_verisoo = False
|
|
|
|
return {
|
|
"version": soosef.__version__,
|
|
"has_dct": has_dct,
|
|
"has_audio": has_audio,
|
|
"has_verisoo": has_verisoo,
|
|
"has_fieldkit": config.killswitch_enabled or config.deadman_enabled,
|
|
"fieldkit_status": fieldkit_status,
|
|
"channel_configured": ks_status.has_channel_key,
|
|
"channel_fingerprint": ks_status.channel_fingerprint or "",
|
|
"identity_configured": ks_status.has_identity,
|
|
"identity_fingerprint": ks_status.identity_fingerprint or "",
|
|
"auth_enabled": app.config["AUTH_ENABLED"],
|
|
"is_authenticated": _is_authenticated(),
|
|
"is_admin": _is_admin(),
|
|
"username": _get_username(),
|
|
}
|
|
|
|
# ── Root routes ───────────────────────────────────────────────
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html")
|
|
|
|
return app
|
|
|
|
|
|
def _load_secret_key(app: Flask) -> None:
|
|
"""Load or generate persistent secret key for Flask sessions."""
|
|
SECRET_KEY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
if SECRET_KEY_FILE.exists():
|
|
app.secret_key = SECRET_KEY_FILE.read_bytes()
|
|
else:
|
|
key = secrets.token_bytes(32)
|
|
SECRET_KEY_FILE.write_bytes(key)
|
|
SECRET_KEY_FILE.chmod(0o600)
|
|
app.secret_key = key
|
|
|
|
|
|
def _is_authenticated() -> bool:
|
|
"""Check if current request has an authenticated session."""
|
|
# TODO: Wire up auth.py from stegasoo
|
|
return True
|
|
|
|
|
|
def _is_admin() -> bool:
|
|
"""Check if current user is an admin."""
|
|
# TODO: Wire up auth.py from stegasoo
|
|
return True
|
|
|
|
|
|
def _get_username() -> str:
|
|
"""Get current user's username."""
|
|
# TODO: Wire up auth.py from stegasoo
|
|
return "admin"
|