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,
)
|