soulsync/core/security
BoulderBadgeDad 5b52d579c5 Login mode: enforce "every profile has a password" at every write-point (no gaps)
Invariant: while security.require_login is on, every profile must have a login
password or it's locked out. Previously only the admin's own anti-lockout existed,
so members could be stranded (created without a password, or login flipped on while
passwordless members existed). Closed all the write-points:

core/security/login_provisioning.py (pure policy, single source of truth):
- members_without_password(profiles) — non-admin profiles that can't sign in
- create_needs_password(require_login) / removing_password_strands(require_login)

Wired into web_server:
- create_profile: while login is on, a new member must be given a password (400
  otherwise) and it's set on creation.
- enable-login (settings save): refuses to turn login on while any member lacks a
  password — lists them — same shape as the existing admin anti-lockout.
- set-password: refuses to CLEAR a password while login is on (would strand them).

UI: Create Profile form gains a login-password field (alongside the optional PIN);
the Manage Profiles per-member password button (prior commit) covers existing
members + changes.

Tests: pure policy seam + endpoint enforcement (create blocked w/o password when
on, allowed w/ password, no friction when off, clear blocked when on). 442
profile/settings/auth tests green; ruff clean.
2026-06-11 19:48:50 -07:00
..
__init__.py Security: enforce the launch PIN server-side, not just a client overlay (#832) 2026-06-09 22:19:14 -07:00
auth_proxy.py Security: trust a forward-auth proxy user header (Tier 3) 2026-06-10 20:57:48 -07:00
launch_lock.py Fix: launch PIN re-triggered the first-run setup wizard every visit (#842) 2026-06-10 11:40:47 -07:00
login_gate.py Login recovery (DB + backend): security question to reset a forgotten password 2026-06-10 22:24:54 -07:00
login_provisioning.py Login mode: enforce "every profile has a password" at every write-point (no gaps) 2026-06-11 19:48:50 -07:00
rate_limit.py Security: brute-force limiter on the launch-PIN unlock (Tier 2) 2026-06-10 20:47:46 -07:00
reverse_proxy.py Security: add gated security headers in reverse-proxy mode (Tier 2) 2026-06-10 20:48:51 -07:00
ws_gate.py #852: gate the WebSocket handshake — close the launch-PIN/login bypass 2026-06-11 13:27:03 -07:00