Skip to content

Application Reference

The application module is the runtime composition root. It bootstraps the database, wires services and pages, and starts the NiceGUI app host.

Main application module

Purpose: startup lifecycle, page registration, and top-level routing.

Copyright 2026 Michael Ryan Hunsaker, M.Ed., Ph.D

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

load_secrets()

Load KEY=VALUE secrets from .secrets into os.environ.

SEC-002: uses partition('=') so values containing '=' (e.g. base64 Fernet keys) are preserved intact. Blank lines and comment lines are skipped. FUN-013: strips whitespace from both key and value independently.

If the secrets file is missing the interactive setup assistant (setup.py) is launched automatically so first-time users never see a raw traceback.

Source code in accessibility_mgr/app.py
def load_secrets():
    """Load KEY=VALUE secrets from .secrets into os.environ.

    SEC-002: uses partition('=') so values containing '=' (e.g. base64 Fernet
    keys) are preserved intact.  Blank lines and comment lines are skipped.
    FUN-013: strips whitespace from both key and value independently.

    If the secrets file is missing the interactive setup assistant
    (setup.py) is launched automatically so first-time users never see a
    raw traceback.
    """
    secrets_path = '.secrets'
    if not os.path.exists(secrets_path):
        import subprocess

        setup_script = _base_path() / 'setup.py'
        if setup_script.exists():
            print("\n  .secrets not found.  Launching setup assistant...\n")
            subprocess.run([sys.executable, str(setup_script)], check=False)
        if not os.path.exists(secrets_path):
            raise FileNotFoundError(
                f"Secrets file '{secrets_path}' not found.  "
                "Run 'python setup.py' to create it."
            )

    with open(secrets_path) as file:
        for line in file:
            stripped = line.strip()
            if not stripped or stripped.startswith('#'):
                continue
            key, sep, value = stripped.partition('=')
            if not sep:
                continue  # malformed line — no '=' found; skip silently
            os.environ[key.strip()] = value.strip()

login_page()

Password-protected login page.

Passwords are verified with PBKDF2-HMAC-SHA-256.

ACCESSMAN_PASSWORD_HASH must be produced by: python -c "import hashlib,os,base64; salt=os.urandom(16); dk=hashlib.pbkdf2_hmac('sha256',b'yourpassword',salt,260000); print(base64.b64encode(salt+dk).decode())" For backwards-compat a 64-hex-char legacy SHA-256 hash is still accepted but triggers a deprecation warning.

When ACCESSMAN_PASSWORD_HASH is unset the app no longer silently

auto-approves access. Instead it blocks login and shows a clear setup warning. Set ACCESSMAN_UNPROTECTED=1 only for offline dev.

FUN-022: Empty-password submissions are rejected before any hashing.

Source code in accessibility_mgr/app.py
@ui.page("/login")
def login_page() -> None:
    """Password-protected login page.

    SEC-001: Passwords are verified with PBKDF2-HMAC-SHA-256.
             ACCESSMAN_PASSWORD_HASH must be produced by:
               python -c "import hashlib,os,base64; salt=os.urandom(16);
                 dk=hashlib.pbkdf2_hmac('sha256',b'yourpassword',salt,260000);
                 print(base64.b64encode(salt+dk).decode())"
             For backwards-compat a 64-hex-char legacy SHA-256 hash is still
             accepted but triggers a deprecation warning.

    FUN-019: When ACCESSMAN_PASSWORD_HASH is unset the app no longer silently
             auto-approves access.  Instead it blocks login and shows a clear
             setup warning.  Set ACCESSMAN_UNPROTECTED=1 only for offline dev.

    FUN-022: Empty-password submissions are rejected before any hashing.
    """
    import base64
    import hashlib
    import hmac

    ui.page_title("Login — " + APP_TITLE)
    _expected_hash = os.getenv("ACCESSMAN_PASSWORD_HASH", "").strip()
    _unprotected   = os.getenv("ACCESSMAN_UNPROTECTED", "0").lower() in {"1", "true", "yes"}

    if not _expected_hash:
        if _unprotected:
            log.warning(
                "ACCESSMAN_UNPROTECTED=1 — authentication is disabled. "
                "Do NOT use this in production."
            )
            nicegui_app.storage.user["authenticated"] = True
            ui.navigate.to("/")
        else:
            # FUN-019: no silent auto-approve
            with ui.column().classes(
                "items-center justify-center w-full min-h-screen bg-slate-100"
            ), ui.card().classes("p-8 gap-4 w-96 shadow-xl rounded-2xl border-red-300"):
                ui.label("⚠ No Password Configured").classes(
                    "text-lg font-bold text-red-700 text-center"
                )
                ui.label(
                    "Set ACCESSMAN_PASSWORD_HASH in your .secrets file to enable login. "
                    "For offline dev only, set ACCESSMAN_UNPROTECTED=1."
                ).classes("text-sm text-slate-600 text-center")
        return

    if _is_authenticated():
        ui.navigate.to("/")
        return

    def _verify(candidate: str) -> bool:
        """SEC-001: verify against PBKDF2 hash, with legacy SHA-256 fallback."""
        if len(_expected_hash) == 64 and all(c in "0123456789abcdef" for c in _expected_hash):
            # Legacy plain SHA-256 (64 hex chars) — accept but warn
            log.warning(
                "ACCESSMAN_PASSWORD_HASH is a plain SHA-256 hex digest. "
                "Regenerate it with PBKDF2 — see the login_page docstring."
            )
            entered = hashlib.sha256(candidate.encode()).hexdigest()
            return hmac.compare_digest(entered, _expected_hash)
        # PBKDF2-HMAC-SHA-256: hash = base64(salt[16] || dk[32])
        try:
            raw  = base64.b64decode(_expected_hash)
            salt = raw[:16]
            stored_dk = raw[16:]
            dk = hashlib.pbkdf2_hmac("sha256", candidate.encode(), salt, 260000)
            return hmac.compare_digest(dk, stored_dk)
        except Exception:
            return False

    with ui.column().classes("items-center justify-center w-full min-h-screen bg-slate-100"):
        with ui.card().classes("p-8 gap-4 w-80 shadow-xl rounded-2xl"):
            ui.label(APP_TITLE).classes("text-base font-bold text-slate-700 text-center")
            ui.label("Sign in to continue").classes("text-sm text-slate-400 text-center mb-2")
            pw = ui.input("Password", password=True, password_toggle_button=True).classes(
                "w-full"
            )
            err = ui.label("").classes("text-red-500 text-xs")

            def _login() -> None:
                # FUN-022: reject empty passwords before hashing
                if not pw.value:
                    err.set_text("Password cannot be empty")
                    return
                if _verify(pw.value):
                    nicegui_app.storage.user["authenticated"] = True
                    ui.navigate.to("/")
                else:
                    err.set_text("Incorrect password")
                    pw.set_value("")

            ui.button("Sign In", on_click=_login).classes("w-full bg-blue-600 text-white")
            pw.on("keydown.enter", lambda: _login())

main()

Console-script entry point for uv run AccessMan.

Source code in accessibility_mgr/app.py
def main() -> None:
    """Console-script entry point for ``uv run AccessMan``."""
    favicon_path = _base_path() / "resources" / "icons" / "favicon.svg"
    load_secrets()
    storage_secret = os.getenv('STORAGE_SECRET')

    if not storage_secret:
        raise ValueError("Storage secret is missing or empty.")

    ui.run(
        title=APP_TITLE,
        reload=False,
        favicon=str(favicon_path),
        show=False,
        port=8765,
        storage_secret=storage_secret,
    )